20

Linqクエリの結果を取得した後は、常に満足しているとは限りません。私がそこにいることを期待していたが、そうではなかったという結果があるかもしれません。たとえば、私のクライアントは、顧客が顧客リストに含まれていることを期待していましたが、そうではありませんでした。私ではなく、「おい、私の顧客はどこ?」と言っているのは私のクライアントです。私は男です、そして男であり続けるために、私は私のクライアントに理由を与えなければなりません。

特定のオブジェクトインスタンスとLinqクエリを取得し、クエリ内のどの式がそのインスタンスを除外したかを判断する簡単な方法はありますか?

編集OK、これがより良い例です

出力は次のようになります。

あなたの顧客は2つの理由で除外されました:

顧客のFirstNameはCarlですが、Danielである必要があります

顧客の年齢は18歳ですが、20歳を超える必要があります

    public class Customer
    {
        public string FirstName { get; set; }
        public int Age { get; set; }
    }

    [Test]
    public void Dude_wheres_my_object_test1()
    {
        var daniel = new Customer { FirstName = "Daniel", Age = 41 };
        var carl =  new Customer {  FirstName = "Carl", Age= 18 };
        var Customers = new List<Customer>() { daniel, carl };

        // AsQueryable() to convert IEnumerable<T> to IQueryable<T> in 
        //the case of LinqtoObjects - only needed for this test, not 
        //production code where queies written for LinqToSql etc normally 
        //return IQueryable<T>
        var query = from c in Customers.AsQueryable()
                    where c.Age > 20
                    where c.FirstName == "Daniel"
                    select c;
        //query would return Daniel as you'd expect, but not executed here.

        //However I want to explain why Carl was not in the results

        string[] r = DudeWheresMyObject(query, carl);
        Assert.AreEqual("Age is 18 but it should be > 20", r[0]);
        Assert.AreEqual("FirstName is Carl but it should be Daniel", r[1]);


        //Should even work for a Customer who is not 
        //in the original Customers collection...
        var ficticiousCustomer = new Customer { FirstName = "Other", Age = 19};
        string[] r2= DudeWheresMyObject(query, 
                                         ficticiousCustomer);
        Assert.AreEqual("Age is 19 but it should be > 20", r2[0]);
        Assert.AreEqual("FirstName is Other but it should be Daniel", r2[1]);
    }

    public string[] DudeWheresMyObject<T>(IQueryable<T> query, T instance)
    {
        //Do something here with the query.Expression and the instance

    }

まず第一に、私がいくつかの派手なFluentフレームワークを書き込もうとする前に、誰かがこれをすでに行ったことがありますか?

これまで、式ツリーをナビゲートし、オブジェクトのみを含むIQueryableに対して各ブランチを実行することを検討してきました。今は生の表現ツリーを使った経験があまりないので、落とし穴を提案したり、行き止まりなのか、その理由を説明したりしてほしいと思います。

私はこれから生じるものはすべて次のようになるのではないかと心配しています。

  • 再利用可能-同じクラスのオブジェクトを返すLinqクエリと比較して、すべてのオブジェクトに適用できる必要があります。
  • 元のクエリのパフォーマンスには影響しません(これは標準のLinqである必要があります)。
  • Linq実装に依存しない必要があります。
  • 欠落しているインスタンスに複数のプロパティ値が設定されていて、結果から除外されている場合は、それらすべての理由を報告する必要があります。

編集 私は、クエリのさまざまな順列を使用してデータベースに対してLinqToSqlを複数回実行し続け、結果を比較することを提案していません。むしろ、単一のインスタンスを取得して、それを式ツリーと比較する方法を探しています(クエリを直接再度実行する必要はありません)。

また、他の人がこれが役立つかどうかを示したいと思います。もしそうなら、私はそれを解決するためにオープンソースプロジェクトを開始することを検討します。

4

5 に答える 5

2

クエリをlinq-to-objectsとして再作成し、linq-to-sql / entity /whateverとlinq-to-objectsの微妙な違いに対処し、一部のプロバイダーが機能しないことを受け入れる必要があると思います。現実的に。

あなたはあなたが見つけたいあなたのオブジェクトをメモリIEnumerable<T>か何かの中に持っています。

どういうわけか式ツリーを歩き、葉を切り取る必要があるので、次のように言います。

where obj.foo == true && obj.bar == "yes"

あなたはそれを理解しなければならず、obj.foo == trueそれobj.bar == "yes"は葉であり、そこから始めなければなりません。これは、式ツリーの一種の深さ優先探索になります。

したがって、それらの葉のみを持つオブジェクトクエリへのlinqを構築します。オブジェクトが結果に含まれているかどうかを確認します。そうでない場合は、除外される理由を見つけました。そうでない場合は、ツリーを上に移動します(つまり、whereクエリにさらに句を含め、オブジェクトが結果から消えるまで元の句に近づけます)。

私が見ているように、難しい部分は、元のlinq to'whatever'とオブジェクトへのリンクの違いを処理し、where clausを分割する場所を見つけ、結合のようなものを処理し、それを除外することもでき、SqlMethods.Likeそのようなものを処理することです。オブジェクトへのlinqでは機能しません。

于 2012-10-14T19:44:18.317 に答える
2

結果を除外するものを1回限り探索する場合、 LINQPadDumpの方法に勝るものはありません。これは、実際の動作を示すサンプルの1つからの抜粋です。

// Dump returns exactly what it was given, so you can sneakily inject
// a Dump (or even many Dumps) *within* an expression. This is useful
// for monitoring a query as it progresses:

new[] { 11, 5, 17, 7, 13 }  .Dump ("Prime numbers")
.Where (n => n > 10)        .Dump ("Prime numbers > 10")
.OrderBy (n => n)           .Dump ("Prime numbers > 10 sorted")
.Select (n => n * 10)       .Dump ("Prime numbers > 10 sorted, times 10!");

これにより、適切にフォーマットされた結果のテーブルが得られます。

LINQPadの結果

于 2012-10-14T23:07:29.223 に答える
0

これはちょっとトリッキーなものです。たとえば、あなたの例から、いつでも何かをコーディングして詳細をチェックし、「用語xを検索しましたが、返されたオブジェクトは用語xにありませんでした」と報告できます。

しかし、他の人が示唆しているように、これは「return me x」の行に沿っており、コードで「x.property = y」のクエリを実行し、不一致を報告します。

これに続いて、不一致のリストを生成するために、元のオブジェクトに最初に含める(または遅延を介して拡張する)必要があるため、クエリまたはオブジェクトグラフがかなり大きくなることが問題になると思います含めるための読み込み)一致するかどうかを判断するための多くの順列。

これは、オブジェクトから開始し、条件に基づいてサブ選択する最初の場所でクエリを実行するのとは逆のようなものです。選択してから選択的にスーパー選択し、非条件をキャッチします。

これは興味深い問題であり、オブジェクトが返されるかどうかに到達する前に、通常はクライアント側またはコードのいずれかで対処します。しかし、完璧な解決策は、単一の解決策を返し、おそらくリンクの関連付けを調べることだと思います。リンクは、一般的な「これらのいずれかを持っていません」タイプの理由を見つけるのはそれほど難しいことではありませんが、「私はこのリンクを持っていますが、そのリンクではありません」という応答を与えるのは難しいでしょう。

フィールドと検索語を受け取り、一致しない場合は適切なメッセージを返す、何らかの形式の述語ビルダーに基づくメソッドを提供する必要があるかもしれません。私の考えでは、2つのわずかに異なる問題のように思われます。

今少しとりとめのないですが、これに対する答えを聞いてみたいです!...

于 2012-10-14T19:57:28.313 に答える
0

楽しい表現ハッキングで、セット内の各アイテムの評価の各段階の結果を見ることができます。resultブレークポイントに到達した後にローカルを調べて、評価の結果を確認します。.Where(x => x.IsIncludedInResult).Select(x => x.EvaluationTarget)評価の結果を実際に使用するには、レポートが生成される行に追加するだけです。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

namespace ConsoleApplication4
{
    [DebuggerDisplay("{Condition} on {EvaluationTarget} is {EvaluationResult}")]
    public class ReportItem<T>
    {
        public string Condition { get; private set; }

        public IEnumerable<ReportItem<T>> NestedReports { get; private set; }

        public object EvaluationResult { get; private set; }

        public T EvaluationTarget { get; private set; }

        public ReportItem(Expression condition, IEnumerable<ReportItem<T>> nestedReports, T evaluationTarget, object evaluationResult)
        {
            Condition = condition.ToString();
            NestedReports = nestedReports;
            EvaluationTarget = evaluationTarget;
            EvaluationResult = evaluationResult;
        }

        public override string ToString()
        {
            return string.Format("{0} on {1} is {2}", Condition, EvaluationTarget, EvaluationResult);
        }
    }

    [DebuggerDisplay("Included: {IsIncludedInResult} \n{Summary}")]
    public class Report<T>
    {
        public ReportItem<T> Contents { get; private set; }

        public T EvaluationTarget { get; private set; }

        public Report(T source, Expression<Func<T, bool>> predicate)
        {
            EvaluationTarget = source;

            IsIncludedInResult = predicate.Compile()(source);

            Contents = Recurse(predicate.Parameters.Single(), predicate.Body, source);
        }

        private object Evaluate(Expression expression, ParameterExpression parameter, T source)
        {
            var expr = Expression.Lambda(expression, parameter);
            var @delegate = expr.Compile();
            var value = @delegate.DynamicInvoke(source);
            return value;
        }

        private ReportItem<T> Recurse(ParameterExpression parameter, Expression sourceExpression, T source)
        {
            var constantExpression = sourceExpression as ConstantExpression;

            if(constantExpression != null)
            {
                return new ReportItem<T>(sourceExpression, null, source, Evaluate(constantExpression, parameter, source));
            }

            var unaryExpression = sourceExpression as UnaryExpression;

            if(unaryExpression != null)
            {
                var content = Recurse(parameter, unaryExpression.Operand, source);
                var result = Evaluate(sourceExpression, parameter, source);
                return new ReportItem<T>(sourceExpression, new[]{content}, source, result);
            }

            var binaryExpression = sourceExpression as BinaryExpression;

            if(binaryExpression != null)
            {
                var left = Recurse(parameter, binaryExpression.Left, source);
                var right = Recurse(parameter, binaryExpression.Right, source);
                var item = new ReportItem<T>(sourceExpression, new[] {left, right}, source, Evaluate(sourceExpression, parameter, source));
                return item;
            }

            var methodCallExpression = sourceExpression as MethodCallExpression;

            if(methodCallExpression != null)
            {
                var args = methodCallExpression.Arguments.Select(x => Evaluate(x, parameter, source)).ToArray();
                var result = methodCallExpression.Method.Invoke(Expression.Lambda(methodCallExpression.Object, parameter).Compile().DynamicInvoke(source), args);
                return new ReportItem<T>(sourceExpression, null, source, result);
            }

            throw new Exception("Unhandled expression type " + sourceExpression.NodeType + " encountered");
        }

        public bool IsIncludedInResult { get; private set; }

        public string Summary
        {
            get { return Contents.ToString(); }
        }

        public override string ToString()
        {
            return Summary;
        }
    }

    public static class PredicateRunner
    {
        public static IEnumerable<Report<T>> Report<T>(this IEnumerable<T> set, Expression<Func<T, bool>> predicate)
        {
            return set.Select(x => new Report<T>(x, predicate));
        }
    }

    class MyItem
    {
        public string Name { get; set; }

        public int Value { get; set; }

        public override int GetHashCode()
        {
            return Value % 2;
        }

        public override string ToString()
        {
            return string.Format("Name: \"{0}\" Value: {1}", Name, Value);
        }
    }

    class Program
    {
        static void Main()
        {
            var items = new MyItem[3];
            items[0] = new MyItem
            {
                Name = "Hello",
                Value = 1
            };

            items[1] = new MyItem
            {
                Name = "Hello There",
                Value = 2
            };
            items[2] = new MyItem
            {
                Name = "There",
                Value = 3
            };

            var result = items.Report(x => !x.Name.Contains("Hello") && x.GetHashCode() == 1).ToList();
            Debugger.Break();
        }
    }
}
于 2012-10-15T00:26:21.333 に答える
-1

私はあなたの言うことに従うと思います。選択基準があるクエリとないクエリの2つのクエリを実行し、Linq Exceptを実行して除外されたアイテムを特定し、そのリストをたどって、除外された基準を特定します。

私はそれを行うためのより良い方法を本当に考えることはできません。

このようなもの:

var a = db.Trades.Where(z => z.user == x && z.date == y);

var b = a.Where(z => z.TradeCurrency != null && z.TradeUnderlying.Index != null);

var c = a.Except(b);

List<string> reasons;
foreach(var d in c) {
    if (d.TradeCurrency == null)
        // add reason
    ... etc..
}

これにより、単一のクエリ(複数のサブクエリが含まれる)が実行され、(非常に大きくなる可能性のあるすべての結果を返そうとするのではなく)除外された結果のみが返されます。もちろん、除外されたレコードが100万件あり、含まれているレコードがわずかしかない場合を除きます。

私が考えることができない方法と比較して、これがどれほど効率的かはわかりません。

編集:

Linqクエリは、それを実現する操作を呼び出すまで実行されないことを忘れていると思います。この例では、ここにいくつかのlinqクエリオブジェクトがありますが、データベースは1回だけヒットします。式ツリーは、クエリを実行せずに変更されます。

したがって、この例では、foreach()が発生すると、単一のデータベースクエリ(複数のサブクエリを含む)が実行されます。

于 2012-10-14T19:26:41.750 に答える