LINQ to Entitiesは、IEntityインターフェイスを使用したEDMプリミティブまたは列挙型のキャストのみをサポートします


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

残念ながらpredicate、C#が述語を次のように変換したため、Entity Frameworkはその処理方法を認識していません。

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

Entity Frameworkは次の例外をスローします。

タイプ 'IEntity'をタイプ 'SomeEntity'にキャストできません。LINQ to Entitiesは、EDMプリミティブまたは列挙型のキャストのみをサポートしています。

Entity FrameworkをIEntityインターフェイスと連携させるにはどうすればよいですか?

回答:


188

class拡張メソッドにジェネリック型制約を追加することで、これを解決できました。なぜそれが機能するのかはわかりません。

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

6
私もうまくいきます!私は誰かがこれを説明できるようになりたいです。#linqblackmagic
berko

この制約をどのように追加したのか説明してください
yrahman

5
私の推測では、インターフェイス型ではなくクラス型が使用されています。EFはインターフェイスの種類を認識していないため、SQLに変換できません。クラス制約の場合、推論される型は、EFが何を行うかを知っているDbSet <T>型です。
jwize 2015年

1
完璧です。インターフェイスベースのクエリを実行しながら、コレクションをIQueryableとして維持できることは素晴らしいことです。しかし、EFの内部の仕組みを知らないと、この修正を基本的に考える方法がないので、少し迷惑です。
Anders

ここに表示されているのは、C#コンパイラがTがメソッド内のIEntity型であることを確認できるコンパイラ時間制約であり、コンパイル時にMSILコードが生成したように、IEntity "stuff"の使用が有効であると判断できます。通話の前にこのチェックが自動的に実行されます。明確にするために、ここで「クラス」を型制約として追加すると、collection.FirstOrDefault()が正しく実行され、クラスベースの型でデフォルトのctorを呼び出すTの新しいインスタンスが返される可能性があります。
戦争

64

class「修正」に関するいくつかの追加説明。

この答えは、2つの異なる式を示していwhere T: classます。一方は制約あり、もう一方は制約なしです。class制約がなければ、次のようになります。

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

そして制約付き:

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

これら2つの式は、エンティティフレームワークによって異なる方法で処理されます。EF 6のソースを見ると、例外がここにあることがわかりValidateAndAdjustCastTypes()ます。を参照してください

EF IEntityはドメインモデルの世界に意味のあるものにキャストしようとしますが、失敗するため、例外がスローされます。

class制約のある式にはConvert()演算子が含まれていません。キャストは試行されず、すべて正常です。

それはまだ未解決の問題のままです、なぜLINQは異なる表現を構築するのですか?いくつかのC#ウィザードがこれを説明できることを願っています。


1
説明ありがとう。
ジェイスレア

9
@JonSkeetがここでC#ウィザードを呼び出そうとしました。どこにいますか?
Nick N.

23

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

    ...
}

柔軟性のないもう1つのアプローチは、次のものを利用することです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を返す拡張関数を作成しようとしましたが、フィルター基準は基本クラスに基づいていました。

私は最終的に、私の拡張メソッドが.Select(e => e as T)を呼び出すソリューションを見つけました。ここで、Tは子クラスで、eは基本クラスです。

詳細はこちら: EFの基本クラスを使用してIQueryable <T>拡張を作成する

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.