Linq to SQL: 聚合查询 ||

3

我想写一个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 || ...)

我该如何开始做这件事?

5个回答

0

你需要使用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个查询。 - Deniz Dogan

0

当然,我应该使用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循环。 :)


0

这段代码是针对字符串的。

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() 运算符之外,无法使用本地序列。 - Deniz Dogan
请考虑编辑过的版本,我不确定它是否解决了问题,但可能有帮助。 - Dan Abramov

0

这里有一行代码(为了可读性进行格式化),我相信它会返回你所需的结果:

 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 + "%"))
        );

1
我强烈建议使用 Any(...) 而不是 Count(...) > 0 - Dan Abramov
是的,我同意!我现在会进行更改。 - diceguyd30
我认为写Any(...)比写Where(...).Any()还是更好的 :-) - Dan Abramov
为了使用像“StartsWith”这样的函数,您需要实现结果。我最喜欢的方法是调用“ToList”。我将更新代码并实现列表。 - diceguyd30
我同意,这就是为什么我说这不是一个高效的解决方案。从概念上讲,“StartsWith”可以转换为SQL,但实际上你需要像“SqlMethods.Like”这样的东西。我会在我的答案中加入这个。 - diceguyd30
显示剩余2条评论

-3
你可以构建一个表达式树:
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而不是构造表达式。想象一下如果有人真的用这段代码进行自动补全。 - Dan Abramov
@gaearon。想象一下!这显然是世界末日。 - dtb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接