代码之家  ›  专栏  ›  技术社区  ›  Ahmed Khalaf

如何保存linq2sql或在条件之间?

  •  2
  • Ahmed Khalaf  · 技术社区  · 15 年前

    假设我们需要从一张表中选择两组:“Things”

    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 只有。

    如何在 条件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 它有三个特性:

    • ThingID
    • UserID
    • TagID

    我们只想选一个 ThingTag 对于每一个 标签编号 在我们选择的条件下 用户标识 等于参数if 存在 ,否则先选择 图塔格 为此 标签编号 .

    还和我在一起?希望如此:


    4 回复  |  直到 15 年前
        1
  •  2
  •   Jon Skeet    15 年前

    有没有什么理由不写这个:

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

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

        2
  •  0
  •   Justin Niessner    15 年前

    在我看来,你想做一个XOR(exclusive or),而不是一个常规的或在你的两个条件之间(换句话说,你想要的项目只满足一个或另一个的要求……而不是同时满足两个条件)。

    我对LinqToSQL不太乐观,但我知道LinqToObjects支持XOR…所以您可以试一试。语法如下:

    var DesiredList = db.Things.Where(t => condition1 ^ condition2);
    
        3
  •  0
  •   Marc Gravell    15 年前

    从字面上来看,您可以通过构建 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 . 但是,正如jon所指出的,如果您将其发送到数据库后端,那么优先级是不相关的,您也可以使用逻辑上相同的优先级。 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);
        }
    

    如果我错过了要点,请澄清…

        4
  •  0
  •   eglasius    15 年前

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

    var desiredList=db.things.where(t=>条件1(!条件1和条件2);

    问题是查询优化器似乎 将表达式剪裁为条件2 只有。

    这真的应该给你那些符合条件1的,不管条件2如何…不符合条件1和条件2的。相反,Condition2本身并不是等价的,因为这样会遗漏只与Condition1匹配的记录。

    just(condition1 condition2)的JS版本与上面的引用表达式等效,因为当u与condition1匹配时,已经与condition2和匹配了!条件2,因此您已经为条件1和包含条件2了!条件1案例。如果这与您对查询的预期不匹配,那么就更清楚了,这不是优化器的问题,而是原始表达式的问题。

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