26

その場で式を組み合わせることができるLinqKitライブラリを使用しています 。

これは、エンティティFramewokデータアクセスレイヤーを作成するための純粋な至福です。複数の式をオプションで再利用および結合できるため、読み取り可能で効率的なコードの両方が可能になります。

次のコードを検討してください。

private static readonly Expression<Func<Message, int, MessageView>> _selectMessageViewExpr =
    ( Message msg, int requestingUserId ) =>
        new MessageView
        {
            MessageID = msg.ID,
            RequestingUserID = requestingUserId,
            Body = ( msg.RootMessage == null ) ? msg.Body : msg.RootMessage.Body,
            Title = ( ( msg.RootMessage == null ) ? msg.Title : msg.RootMessage.Title ) ?? string.Empty
        };

Message投影する式を宣言しますMessageView(わかりやすくするために詳細を削除しました)。

これで、データアクセスコードはこの式を使用して個々のメッセージを取得できます。

var query = CompiledQueryCache.Instance.GetCompiledQuery(
    "GetMessageView",
    () => CompiledQuery.Compile(
        _getMessagesExpr
            .Select( msg => _selectMessageViewExpr.Invoke( msg, userId ) ) // re-use the expression
            .FirstOrDefault( ( MessageView mv, int id ) => mv.MessageID == id )
            .Expand()
        )
    );

まったく同じ式をメッセージリストの取得にも再利用できるため、これはすばらしいことです。

var query = CompiledQueryCache.Instance.GetCompiledQuery(
    "GetMessageViewList",
    () => CompiledQuery.Compile(
        BuildFolderExpr( folder )
            .Select( msg => _selectMessageViewExpr.Invoke( msg, userId ) )
            .OrderBy( mv => mv.DateCreated, SortDirection.Descending )
            .Paging()
            .Expand()
        ),
    folder
    );

ご覧のとおり、射影式はに格納され_selectMessageViewExpr、いくつかの異なるクエリを作成するために使用されます。

ただし、このコードがExpand()呼び出し時にクラッシュするという奇妙なエラーの追跡に多くの時間を費やしました。
エラーは言った:

System.Linq.Expressions.FieldExpressionタイプのオブジェクトをタイプにキャストできませんSystem.Linq.Expressions.LambdaExpression

式が呼び出される前にローカル変数で参照されると、すべてが機能するInvokeことに気付いたのはしばらくしてからです

var selector = _selectMessageViewExpr; // reference the field

var query = CompiledQueryCache.Instance.GetCompiledQuery(
    "GetMessageView",
    () => CompiledQuery.Compile(
        _getMessagesExpr
            .Select( msg => selector.Invoke( msg, userId ) ) // use the variable
            .FirstOrDefault( ( MessageView mv, int id ) => mv.MessageID == id )
            .Expand()
        )
    );

このコードは期待どおりに機能します。

私の質問は:

InvokeLinqKitがフィールドに格納された式を認識しない特定の理由はありますか?それは開発者による単なる省略ですか、それとも式を最初にローカル変数に格納する必要がある重要な理由がありますか?

この質問は、生成されたコードを調べてLinqKitソースを確認することでおそらく答えることができますが、LinqKit開発に関係する誰かがこの質問に答えることができるのではないかと思いました。

ありがとう。

4

2 に答える 2

25

ソースコードをダウンロードして分析してみました。ExpressionExpander定数以外の変数に格納されている式を参照することはできません。別のではなく、でInvoke表されるオブジェクトを参照するためにメソッドが呼び出されているという式が必要です。ConstantExpressionMemberExpression

したがって、クラスのメンバー(プロパティではなくパブリックフィールドでさえ)への参照として再利用可能な式を提供することはできません。ネストメンバーアクセス(object.member1.member2...など)もサポートされていません。

ただし、これは、初期式をトラバースし、サブフィールド値を再帰的に抽出することで修正できます。

クラスTransformExprのメソッドコードをに置き換えましたExpressionExpander

var lambda = Expression.Lambda(input);
object value = lambda.Compile().DynamicInvoke();

if (value is Expression)
    return Visit((Expression)value);
else
    return input;

そしてそれは今動作します。

このソリューションでは、前に述べたすべて(ツリーを再帰的にトラバースする)がExpressionTreeコンパイラーによって実行されます:)

于 2011-10-18T13:18:51.287 に答える
10

マイクアンサーの改良版を作成しました:

if (input == null)
    return input;

var field = input.Member as FieldInfo;
var prope = input.Member as PropertyInfo;
if ((field != null && field.FieldType.IsSubclassOf(typeof(Expression))) ||
    (prope != null && prope.PropertyType.IsSubclassOf(typeof(Expression))))
    return Visit(Expression.Lambda<Func<Expression>>(input).Compile()());

return input;

主な利点は、オーバーヘッドDynamicInvokeが大きい削除と、本当に必要なときにだけ呼び出すことです。Invoke

于 2013-11-15T15:34:24.373 に答える