LINQ ไปยังเอนทิตีไม่รู้จักเมธอด 'System.String Format (System.String, System.Object, System.Object)'


88

ฉันมีแบบสอบถาม linq นี้:

private void GetReceivedInvoiceTasks(User user, List<Task> tasks)
{
    var areaIds = user.Areas.Select(x => x.AreaId).ToArray();

    var taskList = from i in _db.Invoices
                   join a in _db.Areas on i.AreaId equals a.AreaId
                   where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
                   select new Task {
                       LinkText = string.Format(Invoice {0} has been received from {1}, i.InvoiceNumber, i.Organisation.Name),
                       Link = Views.Edit
                   };
}

มันมีปัญหาแม้ว่า ฉันกำลังพยายามสร้างงาน สำหรับงานใหม่แต่ละครั้งเมื่อฉันตั้งค่าข้อความลิงก์เป็นสตริงคงที่เช่น "สวัสดี" ก็ใช้ได้ อย่างไรก็ตามข้างต้นฉันกำลังพยายามสร้างลิงก์คุณสมบัติโดยใช้คุณสมบัติของใบแจ้งหนี้

ฉันได้รับข้อผิดพลาดนี้:

base {System.SystemException} = {"LINQ เป็นเอนทิตีไม่รู้จักเมธอด 'System.String Format (System.String, System.Object, System.Object)' และวิธีนี้ไม่สามารถแปลเป็นนิพจน์ที่เก็บได้" }

ใครทราบสาเหตุ? ใครทราบวิธีอื่นในการทำเช่นนี้เพื่อให้ได้ผล?


ใช่พลาดครั้งแรก
AnonyMouse

คำตอบ:


148

Entity Framework พยายามเรียกใช้การฉายภาพของคุณในด้าน SQL ซึ่งไม่มีสิ่งใดเทียบเท่ากับstring.Format. ใช้AsEnumerable()บังคับให้ประเมินส่วนนั้นด้วย Linq to Objects

จากคำตอบก่อนหน้านี้ฉันได้ให้คุณฉันจะปรับโครงสร้างคำถามของคุณเป็นดังนี้:

int statusReceived = (int)InvoiceStatuses.Received;
var areaIds = user.Areas.Select(x=> x.AreaId).ToArray();

var taskList = (from i in _db.Invoices
               where i.Status == statusReceived && areaIds.Contains(i.AreaId)
               select i)
               .AsEnumerable()
               .Select( x => new Task()
               {
                  LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.Organisation.Name),
                  Link = Views.Edit
                });

นอกจากนี้ฉันเห็นว่าคุณใช้เอนทิตีที่เกี่ยวข้องในแบบสอบถาม ( Organisation.Name) ตรวจสอบให้แน่ใจว่าคุณได้เพิ่มความเหมาะสมIncludeลงในแบบสอบถามของคุณหรือกำหนดคุณสมบัติเหล่านั้นโดยเฉพาะเพื่อใช้ในภายหลังเช่น:

var taskList = (from i in _db.Invoices
               where i.Status == statusReceived && areaIds.Contains(i.AreaId)
               select new { i.InvoiceNumber, OrganisationName = i.Organisation.Name})
               .AsEnumerable()
               .Select( x => new Task()
               {
                  LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.OrganisationName),
                  Link = Views.Edit
                });

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

3
ฉันขอแนะนำให้เลือกประเภทที่ไม่ระบุตัวตนที่มีเพียง InvoiceNumber และ Organisation ที่ต้องการชื่อในนั้น ถ้าเอนทิตีใบแจ้งหนี้มีขนาดใหญ่ตัวเลือก i พร้อม AsEnumerable ที่ตามมาจะดึงกลับทุกคอลัมน์แม้ว่าคุณจะใช้เพียงสองคอลัมน์
Devin

1
@ เดวิน: ใช่ฉันเห็นด้วย - อันที่จริงนั่นคือสิ่งที่ตัวอย่างแบบสอบถามที่สองกำลังทำอยู่
BrokenGlass

15

IQueryableมาจากIEnumerableความคล้ายคลึงกันหลักคือเมื่อคุณสร้างแบบสอบถามของคุณจะถูกโพสต์ไปยังเครื่องมือฐานข้อมูลในภาษาของมันช่วงเวลาสั้น ๆ คือที่ที่คุณบอกให้ C # จัดการข้อมูลบนเซิร์ฟเวอร์ (ไม่ใช่ฝั่งไคลเอ็นต์) หรือเพื่อบอกให้ SQL จัดการ ข้อมูล.

โดยพื้นฐานแล้วเมื่อคุณพูดว่าIEnumerable.ToString()C # ได้รับการรวบรวมข้อมูลและเรียกToString()ใช้วัตถุ แต่เมื่อคุณพูดว่าIQueryable.ToString()C # บอกให้ SQL เรียกToString()ใช้อ็อบเจ็กต์ แต่ไม่มีวิธีการดังกล่าวใน SQL

ข้อเสียเปรียบคือเมื่อคุณจัดการข้อมูลใน C # คอลเลกชันทั้งหมดที่คุณกำลังมองหาจะต้องสร้างขึ้นในหน่วยความจำก่อนที่ C # จะใช้ตัวกรอง

วิธีที่มีประสิทธิภาพที่สุดคือการสร้างแบบสอบถามเช่นเดียวIQueryableกับตัวกรองทั้งหมดที่คุณสามารถใช้ได้

จากนั้นสร้างขึ้นในหน่วยความจำและทำการจัดรูปแบบข้อมูลใน C #

IQueryable<Customer> dataQuery = Customers.Where(c => c.ID < 100 && c.ZIP == 12345 && c.Name == "John Doe");

 var inMemCollection = dataQuery.AsEnumerable().Select(c => new
                                                  {
                                                     c.ID
                                                     c.Name,
                                                     c.ZIP,
                                                     c.DateRegisterred.ToString("dd,MMM,yyyy")
                                                   });

3

ในขณะที่ SQL ไม่ทราบว่าจะทำอย่างไรกับstring.Formatมันสามารถทำการเชื่อมต่อสตริง

หากคุณเรียกใช้รหัสต่อไปนี้คุณควรได้รับข้อมูลที่คุณต้องการ

var taskList = from i in _db.Invoices
               join a in _db.Areas on i.AreaId equals a.AreaId
               where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
               select new Task {
                   LinkText = "Invoice " + i.InvoiceNumber + "has been received from " + i.Organisation.Name),
                   Link = Views.Edit
               };

เมื่อคุณดำเนินการค้นหาจริงสิ่งนี้ควรเร็วกว่าการใช้เล็กน้อยAsEnumerable(อย่างน้อยนั่นคือสิ่งที่ฉันพบในโค้ดของฉันเองหลังจากที่มีข้อผิดพลาดเดิมเหมือนกับคุณ) หากคุณกำลังทำสิ่งที่ซับซ้อนมากขึ้นด้วย C # คุณจะยังคงต้องใช้AsEnumerableแม้ว่า


2
ไม่แน่ใจว่าเหตุใด Linq จึงไม่สามารถปรับใช้กับฟังก์ชันFORMATMESSAGE docs.microsoft.com/en-us/sql/t-sql/functions/… จนกว่าจะถึงเวลานั้นของคุณคือทางออก (โดยไม่ต้องบังคับให้เป็นรูปธรรม)
MemeDeveloper

2
ขึ้นอยู่กับโครงสร้างฐานข้อมูลและจำนวนคอลัมน์ที่เกี่ยวข้องการใช้วิธีนี้แทนAsEnumerable()จะมีประสิทธิภาพมากกว่าอย่างมาก หลีกเลี่ยงAsEnumerable()และToList()จนกว่าคุณจะต้องการนำผลลัพธ์ทั้งหมดเข้าสู่หน่วยความจำจริงๆ
Chris Schaller
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.