2

假设我们需要从表中选择两组:“事物”

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));

问题是查询优化器似乎只将表达式修剪为condition2

如何保持条件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}
            );
}

编辑:

示例是获取自动完成列表:

  • 输入:str
  • 输出:开头的str东西和包含的东西str(没有重复)

另一个例子:选择ThingTags具有 3 个属性的:

  • ThingID
  • UserID
  • TagID

我们只想ThingTag为每个选择一个,TagID条件是如果存在UserID,我们选择具有等于参数的那个,否则选择第一个。ThingTagTagID

还在我这儿 ?希望如此 :)


4

4 回答 4

2

有什么理由不写这个:

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

毕竟,这在逻辑上是同一组元素。由于它是一个更简单的表达式,查询生成器更有可能正确处理它。话虽如此,我很惊讶它弄错了。你有一个完整的例子可以提供吗?

于 2009-09-20T19:13:58.407 回答
0

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);
    }

如果我错过了重点,请澄清...

于 2009-09-28T20:49:56.100 回答
0

首先:我真的会重新检查您的原始代码的条件,虽然查询优化器中可能存在错误,但使用的表达式更可能存在错误并且它实际上并不代表以下内容:

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

问题是查询优化器似乎只将表达式修剪为 condition2。

这真的应该给你匹配条件1的那些,而不管条件2......以及不匹配条件1和匹配条件2的那些。相反,单独的条件 2 是不等价的,因为它忽略了只匹配条件 1 的记录。

just (condition1 || condition2) 的 JS 版本等价于上面引用的表达式,因为当你匹配 condition1 时,你已经匹配了 condition2 和 !condition2,所以你已经在 condition1 和 !condition1 情况下包含了 condition2。如果这与您对查询的预期不匹配,那么更清楚的是,这不是优化器的问题,而是原始表达式的问题。

如果您使用 Concat 而不是 Union 连接 2 个结果,则只需要完整的表达式,因为这意味着您最终会在两个表达式中得到匹配的结果......然后您会得到重复的结果。但相比之下, Where 是按行评估的,因此您不必担心这些问题。


第二:从代码示例中,我认为您所面临的问题与您在问题中所追求的内容不那么直接。你提到你得到了第一个标签,但你真正在做什么可以在这个重新编写的版本中看到:

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()
        );
}
于 2009-09-29T07:11:50.420 回答
0

在我看来,您想在两个条件之间进行 XOR(异或)而不是常规 OR(换句话说,您希望项目只满足一个 OR 的要求……而不是两者)。

我对 LINQ to SQL 不是很肯定,但我知道 LINQ to Objects 支持 XOR ......所以你可以试一试。这是语法:

var DesiredList = db.Things.Where(t => condition1 ^ condition2);
于 2009-09-20T19:52:33.730 回答