EF:“包含”导航属性,当使用“选择”投影创建包装对象时
Posted
技术标签:
【中文标题】EF:“包含”导航属性,当使用“选择”投影创建包装对象时【英文标题】:EF: "Include" navigation property, when creating a wrapper object with "Select" projection 【发布时间】:2016-04-27 19:34:57 【问题描述】:我在查询中使用Include
包含导航属性,以便以后不会延迟加载。但是,当我使用 Select
投影创建匿名包装对象时,它不起作用。
让我展示一个简化的例子。 实体:
public class UserEntity
public string Name get;set;
public virtual ICollection<UserEntity> Friends get; set;
查询:
var entry = _dbCtx
.Users
.Include(x => x.Friends)
// Select here is simplified, but it shows the wrapping
.Select(user => new
User = user
)
.First();
// Here we have additional lazy loaded DB call
var friends = entry.User.Friends.Select(x => x.Name).ToList();
我还从生成的 SQL 中看到,导航属性不包括在内:
SELECT
[Limit1].[Name] AS [Name],
FROM ( SELECT TOP (1)
[Extent1].[Name] AS [Name]
FROM [dbo].[Users] AS [Extent1]
) AS [Limit1]
在这种情况下是否可以Include
导航属性Friends
,以便User
在不延迟加载的情况下获取数据?
我希望这也能起作用:
var entry = _dbCtx
.Users
.Select(user => new
User = user
)
.Include(x => x.User.Friends)
.First();
但遇到异常:
InvalidOperationException:查询的结果类型既不是 EntityType 也不是具有实体元素类型的 CollectionType。只能为具有这些结果类型之一的查询指定包含路径。
我想到了一些解决方法,但它们有点棘手:
在Select
中为我们的匿名对象添加附加属性:
var entry = _dbCtx
.Users
.Select(user => new
User = user,
UsersFriends = user.Friends
)
.First();
// manually copy the navigation property
entry.User.Friends = user.UsersFriends;
// Now we don't have any addition queries
var friends = entry.User.Friends.Select(x => x.Name).ToList();
还将 User 映射到 DB 级别的匿名对象,然后将属性映射到 C# 中的 UserEntity
。
var entry = _dbCtx
.Users
.Select(user => new
User = new
Name = user.Name,
Friends = user.Friends
)
.Take(1)
// Fetch the DB
.ToList()
.Select(x => new
User = new UserEntity
Name = x.Name,
Friends = x.Friends
)
.First();
// Now we don't have any addition queries
var friends = entry.User.Friends.Select(x => x.Name).ToList();
所以现在,Friends
有一个 LEFT OUTER JOIN
,但两种解决方法都不是很好:
1) 附加属性和副本不是一种干净的方式。
2) 我的 UserEntity 有更多其他属性。此外,每次我们添加新属性时,我们也应该修改这里的选择器。
有没有办法实现导航属性,包括第一个示例?
感谢您的阅读,我希望有人对此有所了解。
编辑:
我将扩展实体和查询以显示一个真实的用例。
实体
public class UserEntity
public string Name get;set;
public int Score get;set;
public virtual ICollection<UserEntity> Friends get; set;
查询
var entry = _dbCtx
.Users
.Include(x => x.Friends)
.Select(user => new
User = user,
Position = _dbCtx.Users.Count(y => y.Score > user.Score)
)
.First();
【问题讨论】:
【参考方案1】:不是关于_为什么_的答案,而是想要更好的代码格式...
我真的很惊讶它以这种方式工作。也许 EF 检测到您没有直接在投影中使用 Friends
属性,因此忽略了它。如果您将对象封装在 EF 查询的外部:
var entry = _dbCtx
.Users
.Include(x => x.Friends)
.Take(1); // replicate "First" inside the EF query to reduce traffic
.AsEnumerable() // shift to linq-to-objects
// Select here is simplified, but it shows the wrapping
.Select(user => new
User = user
)
.First()
【讨论】:
当我们在c#
端(查询后)而不是sql
创建包装器对象时,您的示例将起作用。但我在db
级别上需要它。原因:我使用Select
执行附加子查询。有趣的事情,看我的第一个解决方法,虽然我们在查询中使用了user.Friends
,但是我们必须在实现后将属性复制回实体,否则我们仍然会有延迟加载。
为什么需要在db查询中投影到匿名类型?据我所知,您最终会得到相同的结果。或者您正在计划要投影到 SQL 的 其他 方法(Where
、OrderBy
等)?
查看我的帖子编辑,我为用户执行子查询,它应该在 DB 中完成,因为它遍历每个用户。因此,由于性能原因,我无法使用 c#
执行此操作。以上是关于EF:“包含”导航属性,当使用“选择”投影创建包装对象时的主要内容,如果未能解决你的问题,请参考以下文章
即使在显式包含之后,EF Core“InvalidOperationException:包含已用于非实体可查询”的导航属性也是如此