LINQ中的SELECT和WHERE子句可以结合使用吗?

3

这是我为了将用户Select到我的模型中,然后删除所有null记录所做的:

        model.Users = users
            .Select(u =>
        {
            var membershipUser = Membership.GetUser(u.UserName);
            return membershipUser != null
                ? new UserBriefModel
                {
                    Username = u.UserName,
                    Fullname = u.FullName,
                    Email = membershipUser.Email,
                    Roles = u.UserName.GetRoles()
                }
                : null;
        })
            .Where(u => u != null)
            .ToList();

想知道是否有一种可以将SELECTWHERE子句结合起来的方法。

我尝试过:

        model.Users = users
            .Select(u =>
            {
                var membershipUser = Membership.GetUser(u.UserName);
                if (membershipUser != null)
                    return new UserBriefModel
                    {
                        Username = u.UserName,
                        Fullname = u.FullName,
                        Email = membershipUser.Email,
                        Roles = u.UserName.GetRoles()
                    };
            })
            .ToList();

但是智能感知提示语法错误。这迫使我添加了一个 return null 语句:

        model.Users = users
            .Select(u =>
            {
                var membershipUser = Membership.GetUser(u.UserName);
                if (membershipUser != null)
                    return new UserBriefModel
                    {
                        Username = u.UserName,
                        Fullname = u.FullName,
                        Email = membershipUser.Email,
                        Roles = u.UserName.GetRoles()
                    };
                return null;
            })
            .ToList();

那么正确的写法是什么呢?使用SELECT语句来选择有效记录并填充到我的模型中。


1
你遇到了什么构建错误?我建议在尝试使用实体之前进行空值检查,否则你会在 LINQ 内部得到 null 引用,这很难诊断。 - Liath
3
为什么要将WHERE与SELECT分开?这样做有什么问题? - Mark Rotteveel
只是想知道是否有可能将两者结合起来。首先用一些无用的记录填充模型,然后再过滤它们感觉有点丑陋。在SELECT子句中放弃null会不会更好? - Blaise
在第二个代码示例中,您的lambda表达式不正确 - 当membership为null时它返回什么?Lambda表达式和匿名方法是相同的方法 - 它们应该是正确的方法才能使用。 - Eugene Podskal
4
有时候使用 foreach 循环语句比 LINQ 更清晰简洁。我认为这可能是其中一种情况。 - cadrell0
@Blaise:你在使用哪个用户存储?用户是否存储在数据库中? - Christoph Fink
5个回答

12

从概念上讲,您实际上有三个操作:

  1. 将用户名称投影到成员用户
  2. 过滤掉空的成员用户
  3. 将成员用户投影到模型

就是您的查询应该看起来的样子。您的第一个查询已经尝试将步骤1和3合并在一起,但是您遇到了困难,因为步骤2确实应该在两者之间,而要绕过它需要跳过的障碍并不美观。

当您单独表示所有三个操作时,查询实际上变得更简单、更易读(并且成为惯用的LINQ代码)。

model.Users = users
    .Select(user => new
    {
        user,
        membershipUser = Membership.GetUser(user.UserName)
    })
    .Where(pair => pair.membershipUser != null)
    .Select(pair => new UserBriefModel
    {
        Username = pair.user.UserName,
        Fullname = pair.user.FullName,
        Email = pair.membershipUser.Email,
        Roles = pair.user.UserName.GetRoles()
    })
    .ToList();

这是一个查询,也可以通过查询语法更有效地编写:

model.Users = from user in users
                let membershipUser = Membership.GetUser(user.UserName)
                where membershipUser != null
                select new UserBriefModel
                {
                    Username = user.UserName,
                    Fullname = user.FullName,
                    Email = membershipUser.Email,
                    Roles = user.UserName.GetRoles()
                };

关于能否将投影和筛选合并为单个LINQ操作的字面问题,确实是可能的。这将不是解决问题的合适方案,但使用SelectMany可以同时进行过滤和投影。这可以通过将项目投影到包含要投影为值的一个项序列或基于谓词的空序列来完成。

model.Users = users
    .SelectMany(u =>
    {
        var membershipUser = Membership.GetUser(u.UserName);
        return membershipUser != null
            ? new[]{ new UserBriefModel
            {
                Username = u.UserName,
                Fullname = u.FullName,
                Email = membershipUser.Email,
                Roles = u.UserName.GetRoles()
            }}
            : Enumerable.Empty<UserBriefModel>();
    }).ToList();

当然,每次您使用这段代码时,都会杀死一只小猫咪。不要杀害小猫咪,请改用早期的查询。


我怎么得到了-2分,而抄袭我的帖子的那个人却得到了+3分? - Matas Vaitkevicius
2
@LIUFA 你的帖子我直到你提到它之前都没看到,也没有投票,所以只能猜测一些事情。首先,你第一次发布时有一个明显不同的答案,后来进行了编辑。我不确定投票是在什么时候进行的,但可能是基于你第一次修改。其次,更重要的是,你的回答没有解释任何事情。仅仅发布一些代码而不解释你做出了什么改变,以及为什么改变,会极大地影响该帖子的价值。最后,我的回答中的代码并不是和你的完全相同,尽管相似。 - Servy
我刚刚花了大约20分钟才用“SelectMany”来纠正我的答案,但你还是打败了我...该死的 Servy。 - Kyle Gobel
@KyleGobel 现在你可以尝试Eric的挑战,并使用SelectMany实现(低效的)Join - Servy

1
我不认为这是可能的,据我所知,Select 将一一映射所有内容...如果你想过滤,你需要一个 Where
编辑编辑: 我不再相信 SelectMany 能够做到这一点(正如 Servy 所示)。

4
非常确定,你的想法是错误的。SelectMany通常用于将列表中的嵌套列表展开,并且其结果元素数量更多而不是更少。它是一个选择器(select),但绝不是过滤器(filter)。 - Magus
@Blaise 我对 SelectMany 没有什么可说的,但我非常确定答案的第一部分是正确的。不幸的是,我认为你不会能够将它们压缩到一个方法中。 - Tzah Mama
@TzahMama:SelectMany是一种非常有用的方法,它允许您将树形结构转换为列表以进行操作:您可以执行类似于users.SelectMany(user => user.UserName.GetRoles())的操作,结果将是任何用户拥有的所有角色的列表,可能会有重复。这很有价值,只是在这里不相关。 - Magus
1
@KyleGobel:但现在,由于这个答案,你将能够正确地使用它!这个答案带来了知识的净增益。 - Magus
@KyleGobel 目前的答案是错误的,因为它声称这是不可能的。但实际上是可以做到的。之前的回答声称这是可能的,但没有说明如何做,这并不特别有帮助,因为知道它是可能的,但不知道如何做并不能帮助 OP 解决问题。当然,整个事情变得更加不合适,因为虽然它是可能的,但不应该这样做。 - Servy
显示剩余4条评论

0
您可以使用一个单一的方法来完成这个任务:
private IEnumerable<UserBriefModel> SelectUserBriefModels(IEnumerable<User> users)
{
    foreach (var user in users)
    {
        var membershipUser = Membership.GetUser(user.UserName);
        if (membershipUser != null)
        {
            yield return new UserBriefModel
            {
                Username = user.UserName,
                Fullname = user.FullName,
                Email = membershipUser.Email,
                Roles = user.UserName.GetRoles()
            };
        }
    }
}

你可以像这样使用它:
model.Users = SelectUserBriefModels(users);

0

我不知道有任何Linq方法可以让你随意添加或不添加值到结果IEnumerable中。

为了做到这一点,lambda(选择器、谓词、过滤器...)应该能够控制这个添加。只有谓词(Where)能够做到这一点。在你的情况下,你需要执行谓词(Where)和Select。除非最后答案末尾描述的一个非直接方法,否则没有组合方法可以同时为您执行这两个操作。

model.Users = users
   .Where(u => Membership.GetUser(u.UserName) != null)
   .Select(u =>
    {
       return new UserBriefModel
         {
             Username = u.UserName,
             Fullname = u.FullName,
             Email = Membership.GetUser(u.UserName).Email,
             Roles = u.UserName.GetRoles()
          };
    })
    .ToList();

我们要么使用这样的预过滤来获取两个Membership.GetUser(u.UserName),要么最后得到你原始的后过滤。
这只是将复杂性转移。很难说哪种性能更好。 这取决于Membership.GetUser是否快速,并且有很多非会员用户- 对于我的例子来说。或者如果Membership.GetUser消耗资源,而非会员用户很少,你的后过滤示例更好。
作为一个基于性能的决策,它应该经过深思熟虑和检查。在大多数情况下,差异是微不足道的。
正如在另一篇帖子中已经显示并由'Servy'先生指出的那样,可以使用一个SelectMany调用来实现这一点,选择空的IEnumerable或1个元素数组。但我仍然认为第一个声明在技术上是正确的,因为SelectMany返回元素的集合(它不直接添加或不添加单个元素)。
 model.Users = users
       .SelectMany(u =>
       {
           var membership = Membership.GetUser(u.UserName);

           if (membership == null)
               return Enumerable.Empty<UserBriefModel>();

           return new UserBriefModel[]
           {
               new UserBriefModel()
               {
                   Username = u.UserName,
                   Fullname = u.FullName,
                   Email = membership.Email,
                   Roles = u.UserName.GetRoles()
               }
           };
       })
        .ToList();

membershipUser = Membership.GetUser(u.UserName); 如果 (membershipUser != null) ... 被转换为 .Where(u => Membership.GetUser(u.UserName) != null)。我没有看到任何错误。抱歉,我弄错了。 - Eugene Podskal
有一点重复,但我认为没有太多可以做的。我仍然喜欢它的外观比OP的好看。 - Magus
问题是是否有一种方法可以使用一个IEnumerable LINQ方法来实现结果,或者您必须同时使用Select和Where才能获得正确的结果。 - Eugene Podskal
@Servy:这就好比说“嗯,技术上你可以使用反射调用私有方法”——如果你感到特别追究,那确实是正确的,但也是工具的滥用。你几乎可以用任何东西做任何事情。但是说“技术上你可以在这里滥用这个东西,所以有一种方法”并没有提供任何好处。你只是在自我膜拜。 - Magus
@Magus,你曾经特意强调过这篇帖子在技术上正确地回答了字面上的问题,而这才是最重要的。如果你想说这个答案的这个方面是不相关的,真正重要的是解决实际问题而不是问题本身,那也没问题。但是,如果你想断言在单个LINQ查询中执行投影和过滤是不可能的,那么这种说法是错误的。这个答案的前半部分是错误的。 - Servy
显示剩余11条评论

-1
model.Users = users
    .Where(u => u.Membership != null)
    .Select(u => new UserBriefModel
            {
                Username = u.UserName,
                Fullname = u.FullName,
                Email = u.Membership.Email,
                Roles = u.UserName.GetRoles()
            })
    .ToList();

先筛选,再选择。为了使用这个解决方案,您需要有导航属性,这样就可以使用 u.Membership.Email 而不是 membershipUser.Email

我的用户看起来像这样:

public class UserProfile
{
    // other properties

    public virtual Membership Membership { get; set; }
}

其中Membership是表示会员表的实体,通过以下方式进行映射:

modelBuilder.Entity<Membership>()
   .HasRequired<UserProfile>(m => m.User)
   .WithOptional(u => u.Membership);

然后,您可以使用一个查询来选择所有内容。这里的其他解决方案也很好用,但每次调用Membership.GetUser(u.UserName)都会导致一次额外的数据库调用。


3
你的代码选择了完全不同的东西。Membership.GetUser 的调用在哪里? - Klaus Byskov Pedersen
@KlausByskovPedersen 是正确的。我正在检查 Membership.GetUser(u.UserName) - Blaise
@chrfin 这不是原帖作者的问题。 - Tzah Mama
@TzahMama:这是一个可行的解决方案,可以为每个拥有会员账户的用户选择一个“UserBriefModel”,并且还可以减少许多数据库查询——我在我的应用程序中就是这样使用的... - Christoph Fink
@TzahMama:最后他问道“那么正确的编写SELECT语句的方式是什么,以便只选择有效记录到我的模型中?”对此,我认为我的解决方案是一个(替代但仍然)答案。或者现在不允许提出基本问题的替代(可能更好的)方法来回答OP了吗? - Christoph Fink
显示剩余3条评论

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