Как сохранить Linq2SQL OR между условиями? - PullRequest
2 голосов
/ 20 сентября 2009

Допустим, нам нужно выбрать два набора из таблицы: «Вещи»

var GradeA = db.Things.Where(t=>  condition1);
var GradeB = db.Things.Where(t=> !condition1 && condition2);
var DesiredList = GradeA.union(GradeB);

альтернативно, нам нужно написать одно утверждение, чтобы избежать union стоимости:

var DesiredList = db.Things.Where(t=> condtion1 || (!condition1 && condition2));

проблема в том, что оптимизатор запросов, похоже, обрезает выражение до условие2 * только 1010 *.

Как сохранить приоритет между условие1 и условие2

пример из реальной жизни:

/// <summary>
/// Gets only first BookTag for each tag word, chooses the one of this user (if exists).
/// </summary>
/// <param name="book"></param>
/// <returns></returns>
public static IQueryable<BookTag> UniqueByTags(this IQueryable<BookTag> bookTags, User user)
{
    return bookTags.GroupBy(BT => BT.TagId)
        .Select(g => g.Any(bt => bt.UserId == user.Id) ? 
            new BookTag() { User = user, Tag = g.First().Tag, Book = bookTags.First().Book } : 
            new BookTag() {User = g.First().User, Tag = g.First().Tag, Book = bookTags.First().Book}
            );
}

Edit:

Пример получения списка автозаполнения:

  • ввод: str
  • : вещи, начинающиеся с str и вещи, содержащие str (без дубликатов)

Другой пример: Выбор ThingTags, который имеет 3 свойства:

  • ThingID
  • UserID
  • TagID

мы хотим выбрать только один ThingTag для каждого TagID при условии, что мы выберем один с параметром UserID, равным, если существует , в противном случае сначала выберите ThingTag для этого TagID .

Все еще со мной? надеюсь, что так:)


Ответы [ 4 ]

2 голосов
/ 20 сентября 2009

Есть ли причина не писать это:

var DesiredList = db.Things.Where(t=> condition1 || condition2);

Это логически тот же набор элементов, в конце концов. Поскольку это более простое выражение, более вероятно, что генератор запросов все сделает правильно. Сказав это, я удивлен, что это неправильно. У вас есть полный пример, который вы могли бы предоставить?

0 голосов
/ 29 сентября 2009

Первое: Я бы на самом деле повторно проверил ваш исходный код на наличие условий, хотя возможно, что в оптимизаторе запросов есть ошибка, скорее всего, была ошибка в используемом выражении, и это действительно не представляю ниже:

var DesiredList = db.Things.Where (t => условие1 || (! Условие1 && условие2));

проблема в том, что оптимизатор запросов кажется обрезать выражение до условия2 только.

Это должно действительно дать вам те, которые соответствуют условию1 независимо от условия2 ... и те, которые не соответствуют условию1 и условию2. Вместо этого одно условие2 не эквивалентно, потому что это исключает записи, которые соответствуют только условию1.

JS-версия just (условие1 || условие2) эквивалентна приведенному выше выражению в кавычках, так как, когда вы соответствуете условию1, вы уже сопоставляете оба условия2 и! Условие2, поэтому вы уже включаете условие2 для обоих случаев условие1 и! Условие1. Если это не совпадает с тем, что вы намеревались с запросами, то понятно, что проблема не в оптимизаторе, а в исходных выражениях.

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


Второе: Из примера кода я думаю, что то, с чем вы сталкиваетесь, менее прямое, чем то, что вы делаете в своем вопросе. Вы упомянули, что получаете первый тег, но то, что вы действительно делаете, можно увидеть в этой переписанной версии:

public static IQueryable<BookTag> UniqueByTags(this IQueryable<BookTag> bookTags, User user)
{
    return bookTags.GroupBy(BT => BT.TagId)
        .Select(g => new BookTag() { 
             User = g.Any(bt => bt.UserId == user.Id) ? user : g.First().User,
             Tag = g.First().Tag, Book = bookTags.First().Book 
        });
}

То, что упомянуто в комментарии, больше похоже на:

public static IQueryable<BookTag> UniqueByTags(this IQueryable<BookTag> bookTags, User user)
{
    return bookTags.GroupBy(BT => BT.TagId)
        .Select(g => g.Any(bt => bt.UserId == user.Id) ? 
            g.First(bt=>bt.UserId == user.Id) : 
            g.First()
        );
}
0 голосов
/ 29 сентября 2009

Если взять пример буквально, вы можете выполнить комбинацию в вопросе, построив Expression на лету:

    static IQueryable<T> WhereXElseY<T>(
        this IQueryable<T> query,
        Expression<Func<T, bool>> predicateA,
        Expression<Func<T, bool>> predicateB)
    {
        var condA = predicateA.Body;
        var param = predicateA.Parameters[0];

        var body = Expression.OrElse(
            condA,
            Expression.AndAlso(
                Expression.Not(condA),
                Expression.Invoke(
                    predicateB, param)));
        var lambda = Expression.Lambda<Func<T, bool>>(
            body, param);
        return query.Where(lambda);
    }

Однако, хотя это может работать с LINQ-to-SQL, оно не будет работать с EF, поскольку EF к сожалению ненавидит Expression.Invoke. Но, как отмечает Джон; если вы отправляете это на сервер базы данных, приоритет не имеет значения, и вы могли бы также пойти с логически эквивалентным condition1 || condition2. Вы можете комбинировать выражения что-то вроде:

    static IQueryable<T> WhereAny<T>(
        this IQueryable<T> query,
        params Expression<Func<T, bool>>[] predicates)
    {
        if (predicates == null) throw new ArgumentNullException("predicates");
        if (predicates.Length == 0) return query.Where(x => false);
        if (predicates.Length == 1) return query.Where(predicates[0]);

        var param = predicates[0].Parameters[0];
        var body = predicates[0].Body;
        for (int i = 1; i < predicates.Length; i++)
        {
            body = Expression.OrElse(
                body, Expression.Invoke(predicates[i], param));
        }
        var lambda = Expression.Lambda<Func<T, bool>>(body, param);
        return query.Where(lambda);
    }

Если я упустил момент, уточните ...

0 голосов
/ 20 сентября 2009

Мне кажется, что вы хотите сделать XOR (Эксклюзив или) вместо обычного ИЛИ между двумя вашими условиями (другими словами, вы хотите, чтобы элементы, которые удовлетворяли требованиям только одного ИЛИ другого ... не и другое).

Я не уверен насчет LINQ to SQL, но я знаю, что LINQ to Objects поддерживает XOR ... так что вы можете попробовать его. Вот синтаксис:

var DesiredList = db.Things.Where(t => condition1 ^ condition2);
...