LINQ เป็นเอนทิตีไม่รู้จักเมธอด 'System.String ToString ()' และวิธีนี้ไม่สามารถแปลเป็นนิพจน์ร้านค้าได้


126

ฉันกำลังย้ายข้อมูลบางอย่างจากเซิร์ฟเวอร์ mysql หนึ่งไปยังเซิร์ฟเวอร์ sql แต่ฉันไม่สามารถหาวิธีทำให้รหัสนี้ทำงานได้:

using (var context = new Context())
{
    ...

    foreach (var item in collection)
    {
        IQueryable<entity> pages = from p in context.pages
                                   where  p.Serial == item.Key.ToString()
                                   select p;
        foreach (var page in pages)
        {
            DataManager.AddPageToDocument(page, item.Value);
        }
    }

    Console.WriteLine("Done!");
    Console.Read();
}

เมื่อเข้าสู่วินาทีforeach (var page in pages)จะแสดงข้อยกเว้นว่า:

LINQ เป็นเอนทิตีไม่รู้จักเมธอด 'System.String ToString ()' และวิธีนี้ไม่สามารถแปลเป็นนิพจน์ร้านค้าได้

ใครรู้ว่าทำไมถึงเกิดขึ้น?


คำตอบ:


134

เพียงบันทึกสตริงเป็นตัวแปร temp จากนั้นใช้ในนิพจน์ของคุณ:

var strItem = item.Key.ToString();

IQueryable<entity> pages = from p in context.pages
                           where  p.Serial == strItem
                           select p;

ปัญหาเกิดขึ้นเนื่องจากToString()ไม่ได้ดำเนินการจริงๆมันถูกเปลี่ยนเป็นMethodGroupจากนั้นแยกวิเคราะห์และแปลเป็น SQL เนื่องจากไม่มีสิ่งที่ToString()เทียบเท่ากันนิพจน์จึงล้มเหลว

บันทึก:

อย่าลืมตรวจสอบคำตอบของ Alexเกี่ยวกับSqlFunctionsคลาสตัวช่วยที่เพิ่มเข้ามาในภายหลังด้วย ในหลายกรณีสามารถขจัดความต้องการตัวแปรชั่วคราวได้


14
จะเกิดอะไรขึ้นถ้า ToString () ของฉันถูกนำไปใช้ทางด้านซ้ายของความเท่าเทียมกัน? egpSerial ToString () = item.
dotNET

3
@dotNet นั่นจะยังคงล้มเหลวเนื่องจากสิ่งทั้งหมดกลายเป็น Expression ซึ่ง Entity Framework พยายามเปลี่ยนเป็น SQL ที่ถูกต้อง มีวิธีการบางอย่างที่รู้วิธีจัดการ แต่ToString()ไม่ใช่วิธีใดวิธีหนึ่ง
Josh

7
@ จอช: ฉันเข้าใจว่ามันจะล้มเหลว สิ่งที่ฉันขอคือการแก้ปัญหาของสถานการณ์นั้นเนื่องจากไม่สามารถใช้วิธีแก้ปัญหาข้างต้นได้อย่างชัดเจน
dotNET

3
@ Josh: ฉันกำลังดิ้นรนกับสถานการณ์เช่นนี้ สมมติว่าคอลัมน์ OrderNumber ของฉันเป็น int แต่ผู้ใช้ของฉันต้องการกรองรายการ OrderNumbers ในขณะที่เขาพิมพ์หากเขาพิมพ์ 143 ในช่องค้นหาเขาต้องการเฉพาะระเบียนที่มี OrderNumber LIKE '% 143%' . ฉันไม่จำเป็นต้องทำ ToString () ในคอลัมน์ OrderNumber เพื่อให้บรรลุหรือไม่
dotNET

5
@dotNET นี่เป็นหนึ่งในสถานการณ์ที่ ORM ตกลงบนใบหน้า ฉันคิดว่ามันก็โอเคในสถานการณ์เหล่านั้นที่จะดร็อปลงใน SQL แบบตรงผ่านExecuteQueryหรือโดยใช้ Entity SQL ด้วยObjectQuery<T>
Josh

69

ดังที่คนอื่นตอบสิ่งนี้หยุดชะงักเนื่องจาก. ToString ล้มเหลวในการแปลเป็น SQL ที่เกี่ยวข้องระหว่างทางเข้าสู่ฐานข้อมูล

อย่างไรก็ตาม Microsoft มีคลาส SqlFunctionsซึ่งเป็นชุดของวิธีการที่สามารถใช้ในสถานการณ์เช่นนี้

สำหรับกรณีนี้สิ่งที่คุณกำลังมองหาอยู่ที่นี่คือSqlFunctions.StringConvert :

from p in context.pages
where  p.Serial == SqlFunctions.StringConvert((double)item.Key.Id)
select p;

ดีเมื่อการแก้ปัญหาด้วยตัวแปรชั่วคราวไม่เป็นที่ต้องการไม่ว่าด้วยเหตุผลใดก็ตาม

เช่นเดียวกับ SqlFunctions คุณยังมีEntityFunctions (ที่มี EF6 ล้าสมัยโดยDbFunctions ) ซึ่งมีชุดฟังก์ชันที่แตกต่างกันซึ่งเป็นแหล่งข้อมูลที่ไม่เชื่อเรื่องพระเจ้า (ไม่ จำกัด เฉพาะเช่น SQL)


4
พวกเขาเพิ่มคลาส SqlFunctions ใน. NET 4 และฉันเพิ่งเรียนรู้เกี่ยวกับเรื่องนี้? การค้นหาที่ยอดเยี่ยม
James Skemp

24

ปัญหาคือคุณกำลังเรียก ToString ในแบบสอบถาม LINQ ถึงเอนทิตี นั่นหมายความว่าโปรแกรมแยกวิเคราะห์กำลังพยายามแปลงการเรียก ToString เป็น SQL ที่เทียบเท่า (ซึ่งเป็นไปไม่ได้ ... ดังนั้นจึงเป็นข้อยกเว้น)

สิ่งที่คุณต้องทำคือย้ายการโทร ToString ไปยังบรรทัดแยก:

var keyString = item.Key.ToString();

var pages = from p in context.entities
            where p.Serial == keyString
            select p;

9

มีปัญหาที่คล้ายกัน แก้ไขได้โดยการเรียก ToList () ในคอลเลกชันเอนทิตีและสอบถามรายการ หากคอลเล็กชันมีขนาดเล็กนี่คือตัวเลือก

IQueryable<entity> pages = context.pages.ToList().Where(p=>p.serial == item.Key.ToString())

หวังว่านี่จะช่วยได้


43
โปรดทราบว่านี่จะดึงเอนทิตีของเพจทั้งหมดจากฐานข้อมูลและทำการกรองฝั่งไคลเอ็นต์แทน db .. โดยปกติจะไม่ใช่สิ่งที่ดี
lambinator

3
เป็นความจริงวิธีนี้จะไม่มีประสิทธิภาพสำหรับตารางใด ๆ ที่มีมากกว่าหนึ่งระเบียนซึ่งหมายถึงตารางทั้งหมดที่มีอยู่ :-) อย่างไรก็ตามคำตอบนี้ช่วยฉันได้ในวันนี้เพราะฉันกำลังทำ. Select การฉายภาพที่รวม toString () ดังนั้นการเรียก. ToList () ก่อนที่มือจะไม่มีการลงโทษประสิทธิภาพสำหรับฉันและการโทร. ToList () อนุญาตให้ฉันใช้. toString () formatting and my.Select statement ...
Nathan Prather

6

เปลี่ยนเป็นแบบนี้และควรใช้งานได้:

var key = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
                           where  p.Serial == key
                           select p;

สาเหตุที่ไม่เกิดข้อยกเว้นในบรรทัดที่มีการประกาศแบบสอบถาม LINQ แต่ในบรรทัดของforeachคุณสมบัติการดำเนินการที่เลื่อนออกไปนั่นคือคิวรี LINQ จะไม่ถูกดำเนินการจนกว่าคุณจะพยายามเข้าถึงผลลัพธ์ และสิ่งนี้เกิดขึ้นในforeachไม่ช้า


6

ส่งตารางไปที่Enumerableแล้วคุณเรียกวิธี LINQ โดยใช้ToString()วิธีการภายใน:

    var example = contex.table_name.AsEnumerable()
.Select(x => new {Date = x.date.ToString("M/d/yyyy")...)

แต่ระวังเมื่อคุณเรียกAsEnumerableหรือToListวิธีการเพราะคุณจะขอข้อมูลทั้งหมดจากเอนทิตีทั้งหมดก่อนวิธีนี้ ในกรณีของฉันด้านบนฉันอ่านtable_nameแถวทั้งหมดตามคำขอเดียว


6
ปกติไม่ใช่ทางเลือกที่ดี AsEnumerable () ใส่ข้อมูลทั้งหมดในหน่วยความจำคุณสามารถดูข้อมูลเพิ่มเติมได้ที่นี่: stackoverflow.com/questions/3311244/…
kavain

5

การอัปเกรดเป็นEntity Framework เวอร์ชัน 6.2.0ใช้ได้ผลสำหรับฉัน

ก่อนหน้านี้ฉันใช้เวอร์ชัน 6.0.0

หวังว่านี่จะช่วยได้


1

ใน MVC สมมติว่าคุณกำลังค้นหาบันทึกตามความต้องการหรือข้อมูลของคุณ มันทำงานอย่างถูกต้อง

[HttpPost]
[ActionName("Index")]
public ActionResult SearchRecord(FormCollection formcollection)
{       
    EmployeeContext employeeContext = new EmployeeContext();

    string searchby=formcollection["SearchBy"];
    string value=formcollection["Value"];

    if (formcollection["SearchBy"] == "Gender")
    {
        List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Gender == value).ToList();
        return View("Index", emplist);
    }
    else
    {
        List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Name == value).ToList();
        return View("Index", emplist);
    }         
}

2
เพื่อแนวทางปฏิบัติที่ดีขึ้นหรือในโค้ดประเภทการใช้งานจริงคุณควรมีเหตุการณ์ของฐานข้อมูลอยู่ในชั้นบริการหรือชั้นข้อมูลและไม่ใช่ในการดำเนินการโดยตรง
TGarrett

0

หากคุณต้องการพิมพ์ToStringในแบบสอบถามของคุณจริงๆคุณสามารถเขียนผู้เยี่ยมชมแผนภูมินิพจน์ที่เขียนการเรียกใหม่ToStringด้วยการเรียกไปยังStringConvertฟังก์ชันที่เหมาะสม :

using System.Linq;
using System.Data.Entity.SqlServer;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using System;

namespace ToStringRewriting {
    class ToStringRewriter : ExpressionVisitor {
        static MethodInfo stringConvertMethodInfo = typeof(SqlFunctions).GetMethods()
                 .Single(x => x.Name == "StringConvert" && x.GetParameters()[0].ParameterType == typeof(decimal?));

        protected override Expression VisitMethodCall(MethodCallExpression node) {
            var method = node.Method;
            if (method.Name=="ToString") {
                if (node.Object.GetType() == typeof(string)) { return node.Object; }
                node = Call(stringConvertMethodInfo, Convert(node.Object, typeof(decimal?));
            }
            return base.VisitMethodCall(node);
        }
    }
    class Person {
        string Name { get; set; }
        long SocialSecurityNumber { get; set; }
    }
    class Program {
        void Main() {
            Expression<Func<Person, Boolean>> expr = x => x.ToString().Length > 1;
            var rewriter = new ToStringRewriter();
            var finalExpression = rewriter.Visit(expr);
            var dcx = new MyDataContext();
            var query = dcx.Persons.Where(finalExpression);

        }
    }
}

ควรใช้ FirstOrDefault ไม่ใช่แค่ First ... หากเป็นคีย์หลักให้ใช้ Find เนื่องจากจะทำงานได้ดีกว่า
TGarrett

@TGarrett การใช้Firstที่นี่เพียงอย่างเดียวคือผลลัพธ์GetMethods()ที่ได้กลับMethodInfo[]มา AFAIK MethodInfo[]ไม่มีFindวิธีการและไม่มีวิธีการขยายดังกล่าว แต่ฉันควรใช้จริงๆSingleเพราะพบวิธีนี้ผ่านการสะท้อนและจะไม่มีข้อผิดพลาดเวลาคอมไพล์หากไม่สามารถแก้ไขวิธีการที่เหมาะสมได้
Zev Spitz

0

ฉันได้รับข้อผิดพลาดเดียวกันในกรณีนี้:

var result = Db.SystemLog
.Where(log =>
    eventTypeValues.Contains(log.EventType)
    && (
        search.Contains(log.Id.ToString())
        || log.Message.Contains(search)
        || log.PayLoad.Contains(search)
        || log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
    )
)
.OrderByDescending(log => log.Id)
.Select(r => r);

หลังจากใช้เวลาในการดีบั๊กนานเกินไปฉันพบว่าข้อผิดพลาดนั้นปรากฏในนิพจน์ลอจิก

บรรทัดแรกsearch.Contains(log.Id.ToString())ทำงานได้ดี แต่บรรทัดสุดท้ายที่เกี่ยวข้องกับอ็อบเจ็กต์ DateTime ทำให้ล้มเหลวอย่างน่าสังเวช:

|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)

ลบบรรทัดที่มีปัญหาและแก้ไขปัญหา

ฉันไม่เข้าใจว่าทำไม แต่ดูเหมือนว่า ToString () เป็นนิพจน์ LINQ สำหรับสตริง แต่ไม่ใช่สำหรับเอนทิตี LINQ สำหรับเอนทิตีเกี่ยวข้องกับแบบสอบถามฐานข้อมูลเช่น SQL และ SQL ไม่มีความคิดของ ToString () ดังนั้นเราจึงไม่สามารถโยน ToString () ลงในประโยค. Where () ได้

แต่บรรทัดแรกทำงานอย่างไร? แทนที่จะเป็น ToString () SQL มีCASTและCONVERTดังนั้นการคาดเดาที่ดีที่สุดของฉันคือ linq สำหรับเอนทิตีใช้สิ่งนั้นในบางกรณีง่ายๆ วัตถุ DateTime ไม่พบว่าง่ายเสมอไป ...


-8

เพียงแค่เปลี่ยนการสืบค้น LINQ เป็น Entity ให้เป็นแบบสอบถาม LINQ to Objects (เช่นเรียก ToArray) เมื่อใดก็ได้ที่คุณต้องการใช้การเรียกเมธอดในแบบสอบถาม LINQ ของคุณ


3
"เมื่อใดก็ตามที่คุณต้องใช้วิธีการโทร" เป็นคำแนะนำที่ไม่ดีเนื่องจากมีบันทึกจำนวนมากอาจเป็นปัญหาใหญ่ คำตอบที่ยอมรับนั้นดีกว่ามากสำหรับสถานการณ์นี้
PeteGO
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.