Entity Framework, LINQ: ไม่สามารถใช้ Any กับ StartsWith?

Sep 04 2020

ฉันมีแบบสอบถาม Entity Framework LINQ ง่ายๆ เป้าหมายคือค้นหาผู้ให้บริการทั้งหมดที่ขึ้นต้นด้วย A, B หรือ C:

var letters = new List<string>() { "A", "B", "C" }; // Dynamic, can be many
var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Any(val => c.Name.StartsWith(val)))
    .ToList();

ฉันเข้าใจ

System.InvalidOperationException: 'The LINQ expression' DbSet .Where (c => __letters_0 .Any (val => val == "" || c.Name! = null && val! = null && c.Name.StartsWith (val) )) 'ไม่สามารถแปลได้ เขียนแบบสอบถามใหม่ในรูปแบบที่สามารถแปลได้หรือเปลี่ยนไปใช้การประเมินผลไคลเอ็นต์อย่างชัดเจนโดยการแทรกการเรียกไปยัง AsEnumerable (), AsAsyncEnumerable (), ToList () หรือ ToListAsync () ดูhttps://go.microsoft.com/fwlink/?linkid=2101038 สำหรับข้อมูลเพิ่มเติม.'

ไม่มีทางทำได้หรือ?

คำตอบ

2 GertArnold Sep 08 2020 at 18:46

ใน Entity Framework 6 แบบสอบถามนี้จะทำงานได้ดี EF6 รองรับการแปล SQL StartsWithและรองรับการใช้ลำดับโลคัล ( letters) ในนิพจน์แบบสอบถาม

EF core 3 (เวอร์ชันในคำถามตามที่ระบุข้อความยกเว้น) ยังรองรับการแปล SQL ของStartsWith. ปัญหาที่นี่ (ซึ่งคำตอบอื่นขาดไปโดยสิ้นเชิง) คือลำดับโลคัลถูกใช้ในลักษณะที่ไม่รองรับ แบบสอบถามเช่น ...

var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Contains(c.Name))
    .ToList();

... จะได้รับการสนับสนุนเพราะlettersสามารถแปลเป็นINอนุประโยคได้ แต่แน่นอนว่าเป็นแบบสอบถามที่แตกต่างกันอย่างสิ้นเชิง

การletters.Anyใช้ EF ต้องแปลงlettersเป็น "บางสิ่ง" ที่สามารถเข้าร่วมกับ SQL ได้ EF6 ทำได้โดยสร้างชุดผลลัพธ์ในแบบสอบถาม SQL:

    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  (SELECT 
            N'A' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            N'B' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]
        UNION ALL
            SELECT 
            N'C' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2]
        WHERE ( CAST(CHARINDEX([UnionAll2].[C1], [Extent1].[Name]) AS int)) = 1

ซึ่งใช้งานได้ แต่ไม่สามารถปรับขนาดได้เลย EF core 3 ไม่รองรับและไม่มีวิธีแก้ไขง่ายๆตามที่แนะนำในคำตอบอื่น ๆ

วิธีแก้ไขที่เป็นไปได้คือการสร้างเพรดิเคตด้วยเพรดิเคต||(Or) เพรดิเคตตัวอย่างเช่น:

var pred = letters.Aggregate(PredicateBuilder.False<Carrier>(), 
    (p,x) => p = p.Or(c => c.Name.StartsWith(x)));
var results = db.Carriers.AsNoTracking()
    .Where(pred)
    .ToList();

ในกรณีที่PredicateBuilderเป็นผู้สร้างคำกริยาเช่น Linqkit หรืออย่างใดอย่างหนึ่ง แต่วิธีนี้ไม่สามารถปรับขนาดได้เช่นกัน EF สร้างพารามิเตอร์สำหรับแต่ละรายการในlettersดังนั้นคุณอาจถึงเกณฑ์ 2100 พารามิเตอร์ใน Sql Server

1 ArsenKhachaturyan Sep 04 2020 at 19:47

Entity Framework ไม่อนุญาตให้ใช้เพรดิเคตที่กำหนดเอง นั่นเป็นสาเหตุที่ทำให้ข้อความค้นหาของคุณล้มเหลว:

... ไม่สามารถแปลได้ เขียนแบบสอบถามใหม่ในรูปแบบที่สามารถแปลได้ ...

ดังนั้นนี่อาจหมายความว่าเวอร์ชัน EF ของคุณไม่รองรับStartsWithหรือมีชุดค่าผสมบางอย่างเช่นStartsWithและAnyไม่สามารถแปลได้

วิธีแก้ปัญหาอาจเป็น:

  1. ขั้นแรกคุณอาจกรองแบบสอบถามของคุณด้วยวิธีที่ใกล้เคียงที่สุดโดยใช้Containsวิธีการ
var results = students.Where(s => letters.Any(l => s.name.Contains(l)));

นี่คือตัวอย่างที่คล้ายกันที่ฉันเขียนสำหรับซอ. NET


  1. ต่อไปในกรณีที่คุณยังต้องการใช้StartsWithแต่ EF ของคุณไม่รองรับคุณสามารถแปลงแบบสอบถามของคุณเป็นคอลเล็กชัน IN-MEMORY:
var results = filter_by_contains_query
              .ToList() // convert filtered result to in-memory collection
              .Where(i => letters.Any(l => i.StudentName.StartsWith(l)));

ที่filter_by_contains_queryเป็นแบบสอบถามจากส่วนที่ 1


มากกว่า:

  • เกี่ยวกับ EF สนับสนุนวิธีการตรวจสอบที่นี่
  • การสนทนารอบStartsWith, EndsWithและContains ที่นี่