如何在条件之间保留Linq2SQL OR?

时间:2009-09-20 19:04:07

标签: .net linq-to-sql lambda expression-trees

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

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

如何保持 condition1 condition2

之间的优先级

现实生活中的示例解决方法是:

/// <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的内容(无重复内容)

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

  • ThingID
  • UserID
  • TagID

我们希望为每个ThingTag选择一个TagID,条件是我们选择UserID等于参数的情况,如果存在,否则选择第一个ThingTag {1}} {1}}。{/}

还在我身边吗?希望如此:)


4 个答案:

答案 0 :(得分:2)

有没有理由不写这个:

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

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

答案 1 :(得分:0)

在我看来,你想在你的两个条件之间做一个XOR(独家或)而不是常规的OR(换句话说,你想要的项目只满足一个或另一个的要求......不是两者)。

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

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

答案 2 :(得分: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);
    }

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

答案 3 :(得分:0)

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

  

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

     

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

这应该真正给你那些匹配condition1而不管condition2 ......以及那些与condition1不匹配且匹配condition2的那些。相反,条件2本身并不等效,因为这会留下仅匹配condition1的记录。

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

如果你用Concat而不是Union加入2个结果,你只需要完整的表达式,因为这意味着你最终会在两个表达式中得到结果匹配......然后你会有重复的结果。但相比之下,每行评估的位置,因此您没有那些担忧。


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

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