【问题标题】:Linq to SQL: Aggregating over ||Linq to SQL:聚合 ||
【发布时间】:2010-10-05 22:22:22
【问题描述】:

我正在尝试编写一个 Linq 查询,该查询获取所有用户的名字或姓氏至少以字符串列表中的一个字符串开头。这用于在消息系统中自动完成收件人。

这是我第一次天真的尝试:

var users = UserRepository.ALL()
foreach (var part in new [] { 'Ha', 'Ho', 'He' })
{
    string part1 = part; // "Copy" since we're coding lazily
    users = users.Where(x => x.LastName.StartsWith(part1) ||
                             x.FirstName.StartsWith(part1));
}

这不起作用,结果变成:

users.Where(a || b).Where(c || d).Where(e || f)...

...而我想要:

users.Where(a || b || c || d || e || f || ...)

我该怎么做呢?

【问题讨论】:

    标签: c# linq linq-to-sql autocomplete aggregate


    【解决方案1】:

    您需要使用 2 个集合 - 在您的代码中,您正在过滤一个列表……实际上,您需要过滤列表的集合 - 而不是经过多次过滤的列表的集合。

    一个作为匹配的存储库,另一个在你的循环中

    var userCollection = new Collection<string>();
    var users = UserRepository.ALL() 
    foreach (var part in new [] { 'Ha', 'Ho', 'He' }) 
    { 
        string part1 = part; // "Copy" since we're coding lazily 
        var matches = users.Where(x => x.LastName.StartsWith(part1) || 
                                 x.FirstName.StartsWith(part1)); 
        foreach (var i in matches)
        {
            userCollection.Add(i);
        }
    } 
    

    我并不是说这是最优雅的解决方案 - 只是试图指出您的逻辑失败的原因。

    你或许可以用 Contains 做一些事情

    var results= from i in collection 
                where idList.Contains(i.Id) 
                select i; 
    

    只是看不到我的头顶

    【讨论】:

    • 当涉及到这种情况时,我对数据库性能不是很了解,但如果我不必对 N 个字符串进行 N 次查询,我真的更喜欢它。
    【解决方案2】:

    当然,我应该使用Union...

    IQueryable<User> result = null;
    foreach (var part in terms)
    {
        string part1 = part;
        var q = users.Where(x => x.FirstName.StartsWith(part1) ||
                                 x.LastName.StartsWith(part1));
        result = result == null ? q : result.Union(q);
    }
    

    使用 ReSharper 可以将其转换为 Linq 表达式:

    IQueryable<User> result = terms.Select(part1 =>
        users.Where(x => x.FirstName.StartsWith(part1) ||
                         x.LastName.StartsWith(part1)))
             .Aggregate<IQueryable<User>, IQueryable<User>>(
                    null, (current, q) => current == null ? q : current.Union(q));
    

    ...但这次我可能会选择foreach 循环。 :)

    【讨论】:

      【解决方案3】:

      此代码用于字符串。

      var users = new [] {"John", "Richard", "Jack", "Roy", "Robert", "Susan" };
      var prefixes = new [] { "J", "Ro" };
      
      var filtered = prefixes.Aggregate(Enumerable.Empty<string>(),
          (acc, pref) => acc.Union(users.Where(u => u.StartsWith(pref)).ToList()));
      

      对于您的 User 课程,它看起来像

      var filtered = prefixes.Aggregate(
          Enumerable.Empty<User>(),
          (acc, pref) => acc.Union(
              users.Where(
                  u => u.FistName.StartsWith(pref) || u.LastName.StartsWith(pref)
                  ).ToList()));
      

      【讨论】:

      • "本地序列不能在查询运算符的 LINQ to SQL 实现中使用,但 Contains() 运算符除外。"
      • 请考虑修改后的版本,我不确定它是否能解决问题,但它可能会。
      【解决方案4】:

      这是一个单行(为便于阅读而格式化),我相信它会返回您寻求的结果:

       var users = UserRepository.ALL()
          .ToList() //ToList called only to materialize the list
          .Where(x => new[] { 'Ha', 'Ho', 'He' }
              .Any(y => x.LastName.StartsWith(y))
          );  //Don't need it here anymore!
      

      这可能不是您所寻求的有效解决方案,但我希望它对您有所帮助!

      编辑:正如 gaearon 指出的,如果 'ALL()' 命令返回很多记录,我的第一个解决方案可能真的很糟糕。试试这个:

      var users = UserRepository.ALL()
              .Where(x => new[] { 'Ha', 'Ho', 'He' }
                  .Any(y => SqlMethods.Like(x.LastName, y + "%"))
              );
      

      【讨论】:

      • 我强烈建议使用Any(...) 而不是Count(...) &gt; 0
      • 是的,我同意!我现在就改。
      • 我认为写Any(...) 比写Where(...).Any() 更好:-)
      • 这也给了我旧的“本地序列不能用于查询运算符的 LINQ to SQL 实现中,除了 Contains() 运算符。”
      • 为了使用像'StartsWith'这样的函数,你需要具体化结果。我最喜欢的方法是调用“ToList”。我将使用正在实现的列表更新代码。
      【解决方案5】:

      你可以构造一个表达式树:

      var parts = new[] { "Ha", "Ho", "He" };
      
      var x = Expression.Parameter(typeof(User), "x");
      
      var body = 
          parts.Aggregate<string, Expression>(
              Expression.Constant(false), 
              (e, p) => 
                  Expression.Or(e,
                      Expression.Or(
                          Expression.Call(
                              Expression.Property(x, "LastName"), 
                              "StartsWith", 
                              null,
                              Expression.Constant(p)),
                          Expression.Call(
                              Expression.Property(x, "FirstName"), 
                              "StartsWith", 
                              null, 
                              Expression.Constant(p)))));
      
      var predicate = Expression.Lambda<Func<User, bool>>(body, x);
      
      var result = users.Where(predicate);
      

      结果相同:

      var result =
          users.Where(x => 
              false ||
              x.LastName.StartsWith("Ha") || x.FirstName.StartsWith("Ha") ||
              x.LastName.StartsWith("Ho") || x.FirstName.StartsWith("Ho") ||
              x.LastName.StartsWith("He") || x.FirstName.StartsWith("He") );
      

      【讨论】:

      • 很酷,但你真的应该使用Contains而不是构造表达式。想象一下有人真的用这段代码来自动完成。
      • @gaearon。设想!这显然是世界末日。
      猜你喜欢
      • 2012-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多