【问题标题】:EF: "Include" navigation property, when creating a wrapper object with "Select" projectionEF:“包含”导航属性,当使用“选择”投影创建包装对象时
【发布时间】: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。只能为具有这些结果类型之一的查询指定包含路径。

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

  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 映射到 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();

【问题讨论】:

    标签: c# entity-framework linq-to-sql entity-framework-6


    【解决方案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 的 其他 方法(WhereOrderBy 等)?
    • 查看我的帖子编辑,我为用户执行子查询,它应该在 DB 中完成,因为它遍历每个用户。因此,由于性能原因,我无法使用 c# 执行此操作。
    猜你喜欢
    • 2020-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-07
    • 2021-10-15
    • 1970-01-01
    • 1970-01-01
    • 2020-02-10
    相关资源
    最近更新 更多