LINQ to Entities, yalnızca IEntity arabirimiyle EDM ilkel veya numaralandırma türlerinin dökümünü destekler


96

Aşağıdaki genel uzantı yöntemine sahibim:

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;
}

Maalesef Entity Framework, predicateC # koşulu aşağıdakine dönüştürdüğünden beri nasıl işleneceğini bilmiyor :

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

Entity Framework aşağıdaki özel durumu atar:

"IEntity" türü "SomeEntity" yazmak için dönüştürülemiyor. LINQ to Entities, yalnızca EDM ilkel veya numaralandırma türlerinin dökümünü destekler.

Entity Framework'ü IEntityarayüzümüzle nasıl çalıştırabiliriz?

Yanıtlar:


188

classUzantı yöntemine genel tür kısıtlaması ekleyerek bunu çözebildim . Yine de neden işe yaradığından emin değilim.

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

6
Benim için de çalışıyor! Birinin bunu açıklayabilmesini çok isterim. #linqblackmagic
berko

Bu kısıtlamayı nasıl eklediğinizi açıklar mısınız
yrahman

5
Tahminime göre arayüz türü yerine sınıf türü kullanılıyor. EF arabirim türü hakkında bilgi sahibi olmadığı için onu SQL'e dönüştüremez. Sınıf kısıtlaması ile ortaya çıkan tür, EF'nin ne yapacağını bildiği DbSet <T> türüdür.
jwize

2
Mükemmel, Arayüz tabanlı sorgular gerçekleştirebilmek ve koleksiyonu IQueryable olarak sürdürmek harika. EF'in iç işleyişini bilmeden, temelde bu düzeltmeyi düşünmenin bir yolu olmaması biraz can sıkıcı.
Anders

Burada gördüğünüz şey, C # derleyicisinin T'nin yöntem içinde IEntity türünde olduğunu belirlemesine izin veren bir derleyici zaman kısıtlamasıdır, böylece MSIL kodunun oluşturduğu derleme sırasında olduğu gibi IEntity "şeyler" in herhangi bir kullanımının geçerli olduğunu belirleyebilir. aramadan önce bu kontrolü sizin için otomatik olarak gerçekleştirecektir. Açıklığa kavuşturmak gerekirse, buraya bir tür kısıtlaması olarak "sınıf" eklemek, collection.FirstOrDefault () 'nun, muhtemelen sınıf tabanlı bir tür üzerinde varsayılan bir ctor çağıran yeni bir T örneği döndürdüğünden doğru şekilde çalışmasını sağlar.
Savaş

64

class"Düzeltme" ile ilgili bazı ek açıklamalar .

Bu cevap , biri where T: classkısıtlama ile diğeri kısıtlama olmaksızın iki farklı ifade gösterir . classKısıtlama olmadan elimizde:

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

ve kısıtlama ile:

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

Bu iki ifade, varlık çerçevesi tarafından farklı şekilde ele alınır. EF 6 kaynaklarına bakıldığında , istisnanın buradanValidateAndAdjustCastTypes() geldiği görülebilir, bkz .

Olan şu ki, EF IEntity, etki alanı modeli dünyasını anlamlı kılan bir şeye dönüşmeye çalışıyor , ancak bunu başaramıyor, dolayısıyla istisna atılıyor.

classKısıtlı ifade Convert()operatörü içermez , atama denenmez ve her şey yolunda.

Hala açık bir soru var, LINQ neden farklı ifadeler oluşturuyor? Umarım bazı C # sihirbazları bunu açıklayabilir.


1
Açıklama için teşekkürler.
Jace Rhea

9
@JonSkeet birisi burada bir C # sihirbazı çağırmaya çalıştı. Neredesin?
Nick N.

23

Entity Framework bunu kutunun dışında desteklemez, ancak ExpressionVisitorifadeyi çeviren bir kolayca yazılabilir:

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);
    }
}

Yapmanız gereken tek şey, aşağıdaki gibi ziyaretçi ifadesini kullanarak geçirilen yüklemi dönüştürmektir:

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);
    }

    ...
}

Başka bir esnek olmayan yaklaşım şunlardan yararlanmaktır 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

Aynı hatayı aldım ama benzer ama farklı bir problem. IQueryable'ı döndüren bir uzantı işlevi oluşturmaya çalışıyordum, ancak filtre ölçütleri temel sınıfa dayanıyordu.

Sonunda benim uzantı yöntemimin çağırabileceği çözümü buldum. (e => e olarak T) 'yi seçin, burada T alt sınıf ve e temel sınıftır.

tam ayrıntılar burada: EF'te temel sınıfı kullanarak IQueryable <T> uzantısı oluşturun

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.