Подтвердить что ты не робот

Чувак, где моя цель? или, почему Linq не возвращает мой объект?

Как только у меня появятся результаты моего запроса Linq, я не всегда счастлив. Может быть, результат, который я ожидал быть там, но не был. Например, мой клиент ожидал, что клиент был в списке клиентов, но это не так. Это мой клиент, говорящий "Чувак, где мой клиент?", А не я. Я - Чувак, и, чтобы остаться чуваком, я должен отдать моему клиенту причину.

Есть ли простой способ взять экземпляр данного объекта и запрос Linq и определить, какие выражения в запросе исключили этот экземпляр?

Изменить Хорошо, вот лучший пример

Выход должен быть чем-то рядом:

Ваш Клиент был исключен по двум причинам:

Клиент 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

    }

Прежде всего, прежде чем я попытаюсь написать какую-нибудь фэнтезийную флюидную фреймворк, Кто-нибудь уже сделал это?

До сих пор я рассматривал возможность навигации по дереву выражений и выполнению каждой ветки по отношению к IQueryable, которая содержит только мой объект. Теперь у меня нет большого опыта, использующего необработанные деревья выражений, поэтому мне бы хотелось, чтобы те, кто должен был предложить любые подводные камни, или даже объясняли, является ли это тупиком и почему.

Я беспокоюсь, что все, что вытекает из этого, должно:

  • Быть многоразовым - должно быть применимо к любому объекту, сравниваемому с запросом Linq, возвращающим объекты того же класса.
  • Не влияет на производительность исходного запроса (это должен быть только стандартный Linq).
  • Должен быть агностик Linq-реализации.
  • Если в отсутствующем экземпляре есть несколько значений свойств, исключающих его из результатов, то следует сообщить обо всех этих причинах.

Edit Я не предлагаю, чтобы я продолжал выполнять LinqToSql против базы данных несколько раз с различными перестановками запроса и сравнения результатов. Скорее, я ищу способ взять один экземпляр и сравнить его с деревом выражений (без выполнения запроса напрямую)

Кроме того, я хотел бы указать, могут ли другие найти это полезным. Если это так, я бы рассмотрел возможность запуска проекта с открытым исходным кодом для его решения.

4b9b3361

Ответ 1

Я думаю, вам нужно будет повторно создать запрос как linq-to-objects и обработать тонкие различия между linq-to-sql/entities/whatever и linq-to-objects, признав, что некоторые провайдеры только что выиграли работайте реалистично.

У вас есть объект, который вы хотите найти в памяти IEnumerable<T> или что-то в этом роде.

Вам нужно будет каким-то образом проложить дерево выражений и вырезать листья, так что скажите, что у вас было:

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

вам нужно выяснить, что obj.foo == true и obj.bar == "yes" - это листья и начинаются там. Это будет своего рода поиск по глубине дерева выражений.

Итак, создайте linq для объектов запросов, у которых были только эти листья. Посмотрите, включен ли объект в результаты. Если нет, то мы выяснили, почему он исключен, если потом не поднимитесь на дерево (т.е. Сделайте запрос, включающий в себя дополнительные предложения, приблизившись к orignal до тех пор, пока объект не исчезнет из результатов).

Как я вижу, жесткие части будут обрабатывать различия между оригинальным linq на "что угодно" и ссылку на объекты, выясняя, где разделить клаузулы, занимаясь такими вещами, как объединения, которые также могут исключать вещи и иметь дело с вещами например SqlMethods.Like, которые не работают в linq для объектов.

Ответ 2

Для одноразового исследования того, что отфильтровывает результат, трудно выполнить метод Dump в LINQPad. Здесь выдержка из одного из их образцов, которые показывают это в действии:

// 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 results

Ответ 3

Это довольно сложно, как в вашем примере, вы всегда можете что-то кодировать, чтобы проверить специфику и сообщить "Я искал термин x, а возвращаемый объект не был в терминах x".

Я бы хотел, хотя, как предполагали другие, что это было бы по строкам для "return me x", а затем в коде, запустите запрос для "x where x.property = y" и сообщите не совпадения.

После этого я бы предположил, что проблема заключается в том, что для создания списка не совпадений ваш граф запроса или объекта станет довольно массивным, поскольку вам понадобится исходный объект, который изначально включает (или должен быть расширены с помощью ленивой загрузки, чтобы включить) многие перестановки для определения совпадений или нет.

Это своего рода инверсия для запуска запроса на первом месте, где вы должны начинать с выбора объекта и подкаталога на основе условий, вы должны выбрать, а затем выборочно выбирать супер, перехватывать не-условия.

Это интересная проблема, и я обычно обращаюсь к стороне клиента или коду, прежде чем перейти к точке, где и объект был возвращен или нет. Но я думаю, что идеальным решением было бы вернуть единое решение и, возможно, изучить его ассоциации для ссылок. Ссылки не будут слишком сложными, чтобы найти общую тему "У меня нет одной из этих", но дать ссылку "Мне эта ссылка, а не эта ссылка" будет сложнее.

Вам может потребоваться предоставить метод, основанный на некоторой форме построителя предикатов, который взял поле и поисковый запрос и вернул соответствующее сообщение, если все не совпало. На мой взгляд, это похоже на две несколько разные проблемы.

Слегка бессвязно, но было бы любопытно услышать любые ответы на это!...

Ответ 4

С помощью какого-то веселого взлома выражения вы можете увидеть результаты каждого этапа оценки для каждого элемента в наборе. Осмотрите локальный 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();
        }
    }
}

Ответ 5

Я думаю, что я следую тому, что вы имеете в виду. То, что я хотел бы сделать, это выполнить два запроса: один с критериями выбора и один без него, затем выполнить 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..
}

Это будет выполнять один запрос (который будет иметь несколько подзапросов) и возвращать только те результаты, которые были исключены (вместо того, чтобы пытаться вернуть все результаты, которые могут быть довольно большими). Если, конечно, у вас нет миллиона исключенных записей и только несколько включенных.

Не уверен, насколько это эффективно, хотя по сравнению с тем, о чем я не могу думать.

EDIT:

Я думаю, вы забываете, что запросы Linq не выполняются, пока вы не вызовете операцию, которая их реализует. В этом примере база данных удаляется только один раз, хотя здесь есть несколько объектов запроса linq. Дерево выражений изменяется без выполнения запросов.

Итак, в этом примере, когда происходит foreach(), выполняется один запрос базы данных (с несколькими подзапросами).