LINQ to Entities รองรับเฉพาะการหล่อ EDM แบบดั้งเดิมหรือชนิดการนับด้วยอินเทอร์เฟซ IEntity


96

ฉันมีวิธีการขยายทั่วไปดังต่อไปนี้:

public static T GetById<T>(this IQueryable<T> collection, Guid id) 
    where T : IEntity
{
    Expression<Func<T, bool>> predicate = e => e.Id == id;

    T entity;

    // Allow reporting more descriptive error messages.
    try
    {
        entity = collection.SingleOrDefault(predicate);
    }
    catch (Exception ex)
    {
        throw new InvalidOperationException(string.Format(
            "There was an error retrieving an {0} with id {1}. {2}",
            typeof(T).Name, id, ex.Message), ex);
    }

    if (entity == null)
    {
        throw new KeyNotFoundException(string.Format(
            "{0} with id {1} was not found.",
            typeof(T).Name, id));
    }

    return entity;
}

น่าเสียดายที่ Entity Framework ไม่ทราบวิธีจัดการกับไฟล์ predicateเนื่องจาก C # แปลงเพรดิเคตเป็นดังต่อไปนี้:

e => ((IEntity)e).Id == id

Entity Framework แสดงข้อยกเว้นต่อไปนี้:

ไม่สามารถแคสต์ประเภท 'IEntity' เพื่อพิมพ์ 'SomeEntity' ได้ LINQ to Entities รองรับเฉพาะการหล่อ EDM แบบดั้งเดิมหรือประเภทการแจงนับ

เราจะทำให้ Entity Framework ทำงานร่วมกับIEntityอินเทอร์เฟซของเราได้อย่างไร?

คำตอบ:


189

ฉันสามารถแก้ไขปัญหานี้ได้โดยเพิ่มclassข้อ จำกัด ประเภททั่วไปในวิธีการขยาย ฉันไม่แน่ใจว่าทำไมมันถึงได้ผล

public static T GetById<T>(this IQueryable<T> collection, Guid id)
    where T : class, IEntity
{
    //...
}

6
ใช้ได้ผลกับฉันด้วย! ฉันอยากจะให้ใครสักคนสามารถอธิบายเรื่องนี้ได้ #linqblackmagic
berko

คุณช่วยอธิบายได้ไหมว่าคุณเพิ่มข้อ จำกัด นี้ได้อย่างไร
yrahman

5
ฉันเดาว่าประเภทคลาสถูกใช้มากกว่าประเภทอินเทอร์เฟซ EF ไม่รู้เกี่ยวกับประเภทอินเทอร์เฟซจึงไม่สามารถแปลงเป็น SQL ได้ ด้วยข้อ จำกัด ของคลาสประเภทที่อนุมานคือประเภท DbSet <T> ซึ่ง EF รู้ว่าต้องทำอย่างไร
jwize

2
สมบูรณ์แบบมันยอดเยี่ยมมากที่สามารถดำเนินการสืบค้นที่ใช้อินเทอร์เฟซและยังคงรักษาคอลเล็กชันไว้อย่าง IQueryable ค่อนข้างน่ารำคาญเล็กน้อย แต่โดยพื้นฐานแล้วไม่มีทางที่จะคิดแก้ไขปัญหานี้ได้โดยไม่รู้ถึงการทำงานภายในของ EF
Anders

สิ่งที่คุณเห็นที่นี่คือข้อ จำกัด ด้านเวลาของคอมไพเลอร์ซึ่งช่วยให้คอมไพเลอร์ C # ตรวจสอบได้ว่า T เป็นประเภท IEntity ภายในวิธีการดังนั้นจึงสามารถระบุได้ว่าการใช้งาน IEntity "stuff" ใด ๆ นั้นถูกต้องตามเวลาคอมไพล์รหัส MSIL ที่สร้างขึ้น จะดำเนินการตรวจสอบนี้ให้คุณโดยอัตโนมัติก่อนการโทร เพื่อความชัดเจนการเพิ่ม "class" เป็นข้อ จำกัด ประเภทที่นี่จะช่วยให้ collection.FirstOrDefault () ทำงานได้อย่างถูกต้องเนื่องจากอาจส่งคืนอินสแตนซ์ใหม่ของ T ที่เรียก ctor เริ่มต้นบนประเภทตามคลาส
สงคราม

64

คำอธิบายเพิ่มเติมบางประการเกี่ยวกับclass"การแก้ไข"

คำตอบนี้แสดงนิพจน์ที่แตกต่างกันสองนิพจน์หนึ่งกับอีกนิพจน์โดยไม่มีwhere T: classข้อ จำกัด หากไม่มีclassข้อ จำกัด เรามี:

e => e.Id == id // becomes: Convert(e).Id == id

และด้วยข้อ จำกัด :

e => e.Id == id // becomes: e.Id == id

นิพจน์ทั้งสองนี้ได้รับการปฏิบัติที่แตกต่างกันโดยกรอบงานเอนทิตี เมื่อพิจารณาจากแหล่งที่มาของEF 6เราจะพบว่ามีข้อยกเว้นมาจากValidateAndAdjustCastTypes()ที่นี่ดู

สิ่งที่เกิดขึ้นคือ EF พยายามที่จะโยนIEntityลงไปในบางสิ่งที่เหมาะสมกับโลกของโมเดลโดเมนอย่างไรก็ตามมันล้มเหลวในการทำเช่นนั้นดังนั้นข้อยกเว้นจึงถูกโยนทิ้งไป

นิพจน์ที่มี classข้อ จำกัด ไม่มีตัวConvert()ดำเนินการไม่ได้ลองแคสต์และทุกอย่างเรียบร้อยดี

ยังคงเป็นคำถามเปิดอยู่ทำไม LINQ จึงสร้างนิพจน์ที่แตกต่างกัน? ฉันหวังว่าตัวช่วยสร้าง C # บางตัวจะสามารถอธิบายสิ่งนี้ได้


1
ขอบคุณสำหรับคำอธิบาย
Jace Rhea

10
@JonSkeet มีคนพยายามเรียกตัวช่วยสร้าง C # ที่นี่ คุณอยู่ที่ไหน?
Nick N.

24

Entity Framework ไม่สนับสนุนสิ่งนี้นอกกรอบ แต่การExpressionVisitorแปลนิพจน์นั้นเขียนได้ง่าย:

private sealed class EntityCastRemoverVisitor : ExpressionVisitor
{
    public static Expression<Func<T, bool>> Convert<T>(
        Expression<Func<T, bool>> predicate)
    {
        var visitor = new EntityCastRemoverVisitor();

        var visitedExpression = visitor.Visit(predicate);

        return (Expression<Func<T, bool>>)visitedExpression;
    }

    protected override Expression VisitUnary(UnaryExpression node)
    {
        if (node.NodeType == ExpressionType.Convert && node.Type == typeof(IEntity))
        {
            return node.Operand;
        }

        return base.VisitUnary(node);
    }
}

สิ่งเดียวที่คุณต้องทำคือการแปลงค่าที่ส่งผ่านในเพรดิเคตโดยใช้ผู้เยี่ยมชมนิพจน์ดังนี้:

public static T GetById<T>(this IQueryable<T> collection, 
    Expression<Func<T, bool>> predicate, Guid id)
    where T : IEntity
{
    T entity;

    // Add this line!
    predicate = EntityCastRemoverVisitor.Convert(predicate);

    try
    {
        entity = collection.SingleOrDefault(predicate);
    }

    ...
}

อีกวิธีหนึ่งที่ไม่ยืดหยุ่นคือการใช้ประโยชน์จากDbSet<T>.Find:

// NOTE: This is an extension method on DbSet<T> instead of IQueryable<T>
public static T GetById<T>(this DbSet<T> collection, Guid id) 
    where T : class, IEntity
{
    T entity;

    // Allow reporting more descriptive error messages.
    try
    {
        entity = collection.Find(id);
    }

    ...
}

1

ฉันมีข้อผิดพลาดเดียวกัน แต่เป็นปัญหาที่คล้ายกัน แต่แตกต่างกัน ฉันพยายามสร้างฟังก์ชันส่วนขยายที่คืนค่า IQueryable แต่เกณฑ์การกรองขึ้นอยู่กับคลาสพื้นฐาน

ในที่สุดฉันก็พบวิธีแก้ปัญหาซึ่งสำหรับวิธีการขยายของฉันในการเรียกใช้เลือก (e => e เป็น T) โดยที่ T คือคลาสลูกและ e คือคลาสฐาน

รายละเอียดทั้งหมดอยู่ที่นี่: สร้างส่วนขยาย IQueryable <T> โดยใช้คลาสพื้นฐานใน EF

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