EF:"包含"导航属性,在创建具有"选择"投影的包装器对象时

ten*_*its 7 c# entity-framework linq-to-sql entity-framework-6

我在我的查询中包含导航属性Include,以便以后不会延迟加载.但是,当我使用Select投影创建一个匿名的包装器对象时,它不起作用.

让我展示一下简化的例子. 实体:

public class UserEntity {
    public string Name {get;set;}
    public virtual ICollection<UserEntity> Friends { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

查询:

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();
Run Code Online (Sandbox Code Playgroud)

我也从生成的SQL中看到,导航属性不包括在内:

SELECT 
    [Limit1].[Name] AS [Name], 
    FROM ( SELECT TOP (1) 
        [Extent1].[Name] AS [Name]
        FROM [dbo].[Users] AS [Extent1]
    )  AS [Limit1]
Run Code Online (Sandbox Code Playgroud)

在这种情况下是否可以Include使用导航属性Friends,以便User在没有延迟加载的情况下获取数据?

我也期待这个工作:

var entry = _dbCtx
    .Users
    .Select(user => new {
        User = user
    })
    .Include(x => x.User.Friends)
    .First();
Run Code Online (Sandbox Code Playgroud)

但得到一个例外:

InvalidOperationException:查询的结果类型既不是EntityType,也不是具有实体元素类型的CollectionType.只能为具有这些结果类型之一的查询指定包含路径.

我遇到了一些解决方法,但它们有些棘手:

  1. 在我们的匿名对象中添加add属性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();
    
    Run Code Online (Sandbox Code Playgroud)
  2. 也将用户映射到数据库级别的匿名对象,然后将属性映射到UserEntityC#.

    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();
    
    Run Code Online (Sandbox Code Playgroud)

所以现在有一个LEFT OUTER JOINfor Friends,但两种解决方法都不太好:

1)附加属性和副本不是一种干净的方式.

2)我的UserEntity还有更多其他属性.另外,每次添加新属性时,我们都应该在这里修改选择器.

有没有办法实现导航属性,包括从第一个样本?

感谢您阅读,我希望有人知道这一点.

编辑:

我将扩展实体和查询以显示真实的用例.

实体

public class UserEntity {
    public string Name {get;set;}
    public int Score {get;set;}
    public virtual ICollection<UserEntity> Friends { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

查询

var entry = _dbCtx
    .Users
    .Include(x => x.Friends)
    .Select(user => new {
        User = user,
        Position = _dbCtx.Users.Count(y => y.Score > user.Score)
    })
    .First();
Run Code Online (Sandbox Code Playgroud)

D S*_*ley 0

不是_为什么_的答案,但想要更好的代码格式......

我真的很惊讶它是这样运作的。也许 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()
Run Code Online (Sandbox Code Playgroud)