13

私は、毎日実行される自動化されたジョブに関する情報(ジョブの名前、実行時間、結果など)をログに記録する、システムの一部のデータレイヤーを作成中です。

Entity Frameworkを使用してデータベースと通信していますが、これらの詳細を上位レベルのモジュールから隠そうとしているため、エンティティオブジェクト自体を公開したくありません。

ただし、仕事の情報を検索するために使用する基準で、インターフェイスを非常に柔軟にしたいと思います。たとえば、ユーザーインターフェースでは、ユーザーが「午前10時から午前11時の間に実行された「hello」という名前のすべてのジョブが失敗した」などの複雑なクエリを実行できるようにする必要があります。Expression明らかに、これは動的に構築されたツリーの仕事のように見えます。

したがって、データレイヤー(リポジトリ)で実行できるようにするのは、型のLINQ式Expression<Func<string, DateTime, ResultCode, long, bool>>(ラムダ式)を受け入れ、そのラムダを、EntityFrameworkが句ObjectContext内のフィルターとして使用できる式に変換することです。 Where()

一言で言えば、私はタイプのラムダ式をに変換しようとしExpression<Func<string, DateTime, ResultCode, long, bool>>ています。Expression<Func<svc_JobAudit, bool>>ここsvc_JobAuditで、はジョブ情報が格納されているテーブルに対応するEntityFrameworkデータオブジェクトです。(最初のデリゲートの4つのパラメーターは、ジョブの名前、実行時、結果、およびMSでの所要時間にそれぞれ対応します)

ExpressionVisitorレンガの壁にぶつかってInvalidOperationException次のエラーメッセージが表示されるまで、クラスの使用は非常に順調に進んでいました。

'VisitLambda'から呼び出された場合、タイプ'System.Linq.Expressions.ParameterExpression'のノードを書き換えると、同じタイプのnull以外の値が返される必要があります。または、「VisitLambda」をオーバーライドして、このタイプの子を訪問しないように変更します。

私は完全に困惑しています。パラメータを参照する式ノードをプロパティを参照するノードに変換できないのはなぜですか?これについて別の方法はありますか?

サンプルコードは次のとおりです。

namespace ExpressionTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<string, DateTime, ResultCode, long, bool>> expression = (myString, myDateTime, myResultCode, myTimeSpan) => myResultCode == ResultCode.Failed && myString == "hello";
            var result = ConvertExpression(expression);
        }

        private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, ResultCode, long, bool>> expression)
        {
            var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(new ReplaceVisitor().Modify(expression), Expression.Parameter(typeof(svc_JobAudit)));
            return newExpression;
        }
    }

    class ReplaceVisitor : ExpressionVisitor
    {
        public Expression Modify(Expression expression)
        {
            return Visit(expression);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (node.Type == typeof(string))
            {
                return Expression.Property(Expression.Parameter(typeof(svc_JobAudit)), "JobName");
            }
            return node;
        }
    }
}
4

2 に答える 2

8

問題は次の 2 つでした。

  • Lambda 式の型にアクセスする方法を誤解していました。新しいデリゲートに一致する新しいラムダを返すのではなく、古いデリゲートに一致するラムダをまだ返していました。

  • 新しいParameterExpressionインスタンスへの参照を保持する必要がありましたが、これは行っていませんでした。

新しいコードは次のようになります (ビジターがParameterExpression一致する Entity Framework データ オブジェクトへの参照を受け入れるようになったことに注目してください)。

class Program
{
    const string conString = @"myDB";

    static void Main(string[] args)
    {
        Expression<Func<string, DateTime, byte, long, bool>> expression = (jobName, ranAt, resultCode, elapsed) => jobName == "Email Notifications" && resultCode == (byte)ResultCode.Failed;
        var criteria = ConvertExpression(expression);

        using (MyDataContext dataContext = new MyDataContext(conString))
        {
            List<svc_JobAudit> jobs = dataContext.svc_JobAudit.Where(criteria).ToList();
        }
    }

    private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, byte, long, bool>> expression)
    {
        var jobAuditParameter = Expression.Parameter(typeof(svc_JobAudit), "jobAudit");
        var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(
            new ReplaceVisitor()
               .Modify(expression.Body, jobAuditParameter), jobAuditParameter);
        return newExpression;
    }
}

class ReplaceVisitor : ExpressionVisitor
{
    private ParameterExpression parameter;

    public Expression Modify(Expression expression, ParameterExpression parameter)
    {
        this.parameter = parameter;
        return Visit(expression);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        return Expression.Lambda<Func<svc_JobAudit, bool>>(Visit(node.Body), Expression.Parameter(typeof(svc_JobAudit)));
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == typeof(string))
        {
            return Expression.Property(parameter, "JobName");
        }
        else if (node.Type == typeof(DateTime))
        {
            return Expression.Property(parameter, "RanAt");
        }
        else if (node.Type == typeof(byte))
        {
            return Expression.Property(parameter, "Result");
        }
        else if (node.Type == typeof(long))
        {
            return Expression.Property(parameter, "Elapsed");
        }
        throw new InvalidOperationException();
    }
}
于 2012-06-22T21:56:32.647 に答える
5

受け入れられた答えは、いくつかの特定のタイプに「ハードコード」されています。これは、他の式 (ラムダ、定数など) の代わりにパラメーターを使用できるよりも一般的な式リライターです。ラムダ式の場合、式のシグネチャを変更して、置換された値に必要なパラメーターを組み込む必要があります。

public class ExpressionParameterSubstitute : System.Linq.Expressions.ExpressionVisitor
{
    private readonly ParameterExpression from;
    private readonly Expression to;
    public ExpressionParameterSubstitute(ParameterExpression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        if (node.Parameters.All(p => p != this.from))
            return node;

        // We need to replace the `from` parameter, but in its place we need the `to` parameter(s)
        // e.g. F<DateTime,Bool> subst F<Source,DateTime> => F<Source,bool>
        // e.g. F<DateTime,Bool> subst F<Source1,Source2,DateTime> => F<Source1,Source2,bool>

        var toLambda = to as LambdaExpression;
        var substituteParameters = toLambda?.Parameters ?? Enumerable.Empty<ParameterExpression>();

        ReadOnlyCollection<ParameterExpression> substitutedParameters
            = new ReadOnlyCollection<ParameterExpression>(node.Parameters
                .SelectMany(p => p == this.from ? substituteParameters : Enumerable.Repeat(p, 1) )
                .ToList());

        var updatedBody = this.Visit(node.Body);        // which will convert parameters to 'to'
        return Expression.Lambda(updatedBody, substitutedParameters);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        var toLambda = to as LambdaExpression;
        if (node == from) return toLambda?.Body ?? to;
        return base.VisitParameter(node);
    }
}
于 2016-04-06T20:56:45.640 に答える