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:查询的结果类型既不是实体类型,也不是具有实体元素类型的集合类型。只能为具有这些结果类型之一的查询指定包含路径。

我有一些解决方法,但它们有点棘手:

  1. 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();
    
  2. 还将 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();

EF:“包含”导航属性,当使用“选择”投影创建包装器对象时

不是关于_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()