使用Linq查询返回复杂类型

4

我有一个相当标准的用户和角色模型:

+-------+   +-----------+   +-------+
| Users |   | UserRoles |   | Roles |
+-------+   +-----------+   +-------+
| ID (P)| < | UserID (P)|   | ID (P)|
| ...   |   | RoleID (P)| > | ...   |
+-------+   +-----------+   +-------+

我正在使用Entity Framework作为我的ORM,我试图在单个Linq查询中构建以下定义的ViewModel:

public class RoleDetail {
  public class RoleUser {
    public int ID { get; set; }
    public string Username { get; set; }
  }
  public int ID { get; set; }
  public string Rolename { get; set; }
  public bool Active { get; set; }
  public IEnumerable<RoleUser> Users { get; set; }
}

我认为以下的查询应该可以工作:

var query = Context.Roles
                   .Include("Users")
                   .Where(r => r.ID == id)
                   .Select(r => new RoleDetail() {
                      ID = r.ID,
                      Rolename = r.Rolename,
                      Active = r.Active,
                      Users = r.Users
                               .Select(u => new RoleDetail.RoleUser() {
                                  ID = u.ID,
                                  Username = u.Username
                                })
                                .ToList()
                    })
                   .FirstOrDefault();

然而,这会抛出以下错误:
LINQ to Entities 不认识方法 'System.Collections.Generic.List [RolesRepository+RoleDetail+RoleUser] ToList[RoleUser](System.Collections.Generic.IEnumerable[RolesRepository+RoleDetail+RoleUser])' 方法,这个方法无法转换为存储表达式。
我没有经常使用 Linq,所以我相当确定我的查询方式有问题。能否有人告诉我我的查询有什么问题,如果有更好的方法来实现我想要的功能呢?
谢谢您的帮助!

1
如果您删除.ToList()会发生什么? - Erik Philips
那似乎就是问题所在了。感谢每个留下答案的人! - BradBrening
3个回答

3

我对linq-to-entities的经验有限,所以这只是一次尝试:

为什么需要使用.ToList()?

.Select()已经返回了一个IEnumerable,所以不需要它也可以工作。

ToList()返回一个List,而不是IList或类似的接口,而是一个具体的c#类。因此,我想Linq to Entities无法将其转换为自己的后台语法。

这样做可行吗?

       var query = Context.Roles
               .Include("Users")
               .Where(r => r.ID == id)
               .Select(r => new RoleDetail() {
                  ID = r.ID,
                  Rolename = r.Rolename,
                  Active = r.Active,
                  Users = r.Users
                           .Select(u => new RoleDetail.RoleUser() {
                              ID = u.ID,
                              Username = u.Username
                            })
                            // .ToList()
                })
               .FirstOrDefault();

删除那个就解决了问题 - 谢谢!我一直在使用.ToList(),因为否则我的Entities类(通过我的存储库构造函数/析构函数基类实例化/释放)会在视图迭代集合之前被释放。这可能是另一个问题,但ToList()似乎有效(通常情况下)。 - BradBrening

2

你LINQ查询中发送给EF LINQ提供程序的所有内容都需要是L2E可以直接转换为SQL的表达式。您需要手动将EF可以翻译的部分与必须在LINQ 2 Objects中完成的部分分开。

执行发生在实际枚举查询的点上,这在您的情况下是FirstOrDefault调用。您可以通过调用IQueryable.AsEnumerable()来强制执行查询;此后的任何操作都由LINQ 2 Objects处理,并且几乎可以做任何您想要的事情。

在您的情况下,lambda表达式中唯一不能翻译的部分是对ToList的调用,它没有数据库等效项,因此您可以选择不调用它;缺点是您的Users属性将设置为某个任意内部类,该内部类恰好是EF实现中的IQueryable.Select的结果。根据您定义的RoleDetail.Users以及对其进行的操作,这可能是一个问题或不是问题。

另一种选择,也是当您必须处理EF不喜欢的表达式时更普遍适用的解决方案,是早期插入AsEnumerable调用:

var query = Context.Roles
           .Include("Users")
           .Where(r => r.ID == id)
           .AsEnumerable()
           .Select(r => new RoleDetail() {
              ID = r.ID,
              Rolename = r.Rolename,
              Active = r.Active,
              Users = r.Users
                       .Select(u => new RoleDetail.RoleUser() {
                          ID = u.ID,
                          Username = u.Username
                        })
                        // .ToList()
            })
           .FirstOrDefault();

非常好的解释。因此,一旦调用.AsEnumerable,查询就处理实际的实体对象,而不是尝试创建一个适合数据库的SQL查询?这对我来说很有意义。 - BradBrening
是的。EF LINQ序列运算符返回IQueryable对象;这些对象只包含一个尚未编译(也永远不会被编译,它将用于生成SQL查询)的表达式。一旦调用不返回IQueryable的方法,EF就必须解析表达式、执行SQL并生成对象/序列。完成后,EF就退出了。 - Michael Edenfield
这可能解释了为什么我的控制器将一个IQueryable对象返回给视图时会抛出错误——数据库上下文超出范围,实际的SQL尚未执行? - BradBrening
是的,这也是为什么你需要使用 Include 调用的原因;只要上下文在范围内,它将延迟加载你使用的任何导航属性,但一旦该上下文已被处理,任何你尚未检索的数据将不再可用。 - Michael Edenfield

1

看起来它正在尝试在数据库中执行ToList,但不知道如何执行。我建议尝试将其更改为ToArray,如果失败,则只需将其保留为IEnumerable。


+1 感谢帮忙。虽然所有回答都很有用,但我选择了第一个回答。 - BradBrening

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