EF:“包含”导航属性,当使用“选择”投影创建包装器对象时
本文关键字:创建 投影 选择 包装 对象 包含 导航 属性 EF | 更新日期: 2023-09-27 18:31:23
我将导航属性包含在带有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:查询的结果类型既不是实体类型,也不是具有实体元素类型的集合类型。只能为具有这些结果类型之一的查询指定包含路径。
我有一些解决方法,但它们有点棘手:
在
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 映射到数据库级别的匿名对象,然后将属性映射到 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();
所以现在,有一个LEFT OUTER JOIN
Friends
,但这两种解决方法都不是很好:
1)附加属性和副本不是一种干净的方式。
2)我的用户实体还有更多其他属性。此外,每次添加新属性时,我们也应该修改此处的选择器。
有没有办法实现导航属性,包括从第一个示例?
感谢您的阅读,我希望有人对此有所了解。
编辑:
我将扩展实体和查询以显示实际用例。
实体
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();
不是关于_why_的答案,但想要更好的代码格式...
我真的很惊讶它以这种方式工作。 也许 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()