web-dev-qa-db-ja.com

関連モデル(EFコア)にLinqKit PredicateBuilderを使用する

LinqKitのPredicateBuilderを使用して、関連するモデルの_.Any_メソッドに述語を渡したい。

だから私は述語を作りたい:

_var castCondition = PredicateBuilder.New<CastInfo>(true);

if (movies != null && movies.Length > 0)
{
    castCondition = castCondition.And(c => movies.Contains(c.MovieId));
}
if (roleType > 0)
{
    castCondition = castCondition.And(c => c.RoleId == roleType);
}
_

次に、それを使用して、述語のモデルに関連するモデルをフィルタリングします。

_IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();
_

しかし、これはSystem.NotSupportedException: Could not parse expression 'n.CastInfo.Any(Convert(__castCondition_0, Func``2))': The given arguments did not match the expected arguments: Object of type 'System.Linq.Expressions.UnaryExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.を引き起こします

私は 同様の質問 を見ました、そしてそこでの答えは_.Compile_を使うことを提案します。または もう1つの質問 追加の述語を作成します。

だから私は余分な述語を使おうとしました

_var tp = PredicateBuilder.New<Name>(true);
tp = tp.And(n => n.CastInfo.Any(castCondition.Compile()));
IQueryable<Name> result = _context.Name.AsExpandable().Where(tp);
_

または直接コンパイルを使用します

_IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition.Compile()));
_

しかし、コンパイルについてエラーがあります:System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(__Compile_0)'

では、PredicateBuilderの結果を変換してAnyに渡すことは可能ですか?

注:式を組み合わせて目的の動作を構築できましたが、追加の変数が必要になるのは好きではありません。

_System.Linq.Expressions.Expression<Func<CastInfo,bool>> castExpression = (c => true);
if (movies != null && movies.Length > 0)
{
    castExpression = (c => movies.Contains(c.MovieId));
}
if (roleType > 0)
{
    var existingExpression = castExpression;
    castExpression = c => existingExpression.Invoke(c) && c.RoleId == roleType;
}
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castExpression.Compile()));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();
_

だから私はビルダーについて何かが恋しいと思います。

バージョンに関する更新:dotnet core2.0とLinqKit.Microsoft.EntityFrameworkCore1.1.10を使用しています

9
Igor

コードを見ると、castCondition変数のタイプは_Expression<Func<CastInfo, bool>>_であると想定されます(以前のバージョンのPredicateBuilderと同様)。

しかし、その場合、n.CastInfo.Any(castCondition)はコンパイルすらすべきではありません(CastInfoがコレクションナビゲーションプロパティであると仮定すると、コンパイラは_Enumerable.Any_をヒットします。これは_Func<CastInfo, bool>_を期待します。 、_Expression<Func<CastInfo, bool>>_ではありません)。では、ここで何が起こっているのでしょうか。

私の意見では、これはC#の暗黙的な演算子の乱用の良い例です。 _PredicateBuilder.New<T>_メソッドは、実際には _ExpressionStarter<T>_ というクラスを返します。このクラスには、Expressionをエミュレートする多くのメソッドがありますが、さらに重要なことに、があります。/implicit_Expression<Func<T, bool>>_および_Func<CastInfo, bool>_への変換。後者では、そのクラスをそれぞれのラムダ関数/式の代わりにトップレベルのEnumerable/Queryableメソッドに使用できます。ただし、あなたの場合のように式ツリー内で使用すると、コンパイル時エラーも防止されます。コンパイラーはn.CastInfo.Any((Func<CastInfo, bool>)castCondition)のようなものを出力しますが、これはもちろん実行時に例外を引き起こします。

LinqKit AsExpandableメソッドの全体的な考え方は、カスタムInvoke拡張メソッドを介して式を「呼び出す」ことを許可することです。これは、式ツリーで「拡張」されます。したがって、最初に戻って、変数タイプが_Expression<Func<CastInfo, bool>>_の場合、使用目的は次のとおりです。

__context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));
_

しかし、先に説明した理由により、これはコンパイルされません。したがって、最初にクエリの_Expression<Func<T, bool>_outsideに変換する必要があります。

_Expression<Func<CastInfo, bool>> castPredicate = castCondition;
_

その後、

__context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castPredicate.Invoke(c)));
_

または

__context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile()));
_

コンパイラに式の型を推測させるには、次のようなカスタム拡張メソッドを作成します。

_using System;
using System.Linq.Expressions;

namespace LinqKit
{
    public static class Extensions
    {
        public static Expression<Func<T, bool>> ToExpression<T>(this ExpressionStarter<T> expr) => expr;
    }
}
_

その後、単に使用します

_var castPredicate = castCondition.ToExpression();
_

それでもクエリのoutsideを実行する必要があります。つまり、次のようにnotが機能します。

__context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c)));
_
7
Ivan Stoev