实体框架:如何从相关实体中选择特定列

Posted

技术标签:

【中文标题】实体框架:如何从相关实体中选择特定列【英文标题】:Entity Framework: How to select specific columns from a related entity 【发布时间】:2022-01-23 23:08:08 【问题描述】:

我有一个 PlayerContext 模型,其中包含许多 BlueprintContext 模型。像这样:

public class PlayerContext
    
        public PlayerContext()  

        [Key]
        public int id  get; set; 
        ...
        public List<BlueprintContext> Blueprints  get; set; 
        ...
    
public class BlueprintContext
    
        [Key]
        public int id  get; set; 
        public Dictionary<Coord3, Chonxels> BigData = new Dictionary<Coord3, Chonxels>()  ;
        public Dictionary<BlueprintIngredient, int> recipe = new Dictionary<BlueprintIngredient, int>();
        public string Name  get; set; 
        public int CreatorId  get; set; 
        public PlayerContext Creator  get; set; 
    

BlueprintContext 中,BigData 字段可以变得非常大。因此,当我加载PlayerContext 时,我想要Blueprints,但我只想要idrecipeName 字段(而不是BigData)。

有没有办法加载PlayerContext 并在没有BigData 字段的情况下包含我需要的BlueprintContext 字段?

这是我尝试过的

        using (var db = new EfContext())
            
                PlayerContext playerContext = db.Players
                    .Where(p => p.id == playerId)
                    ...
                    .Include(p => p.Blueprints.Select(b => new  b.id, b.recipe, b.Name))
                    .AsNoTracking() // disables change tracking
                    .FirstOrDefault();

我得到了这个异常:

System.InvalidOperationException: The expression 'p.Blueprints.AsQueryable().Select(b => new <>f__AnonymousType43`3(id = b.id, recipe = b.recipe, Name = b.Name))' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.

我正在使用 Entity Framework Core。

【问题讨论】:

这能回答你的问题吗? select specific columns when using include statement with entity framework 【参考方案1】:

这是考虑投影***实体的原因之一。例如,如果您想显示玩家及其蓝图的摘要详细信息:

[Serializable]
public class PlayerSummaryViewModel

    public int Id  get; set; 
    public string Name  get; set; 

    // Any other fields the view actually needs.
    public IList<BlueprintSummaryViewModel> Blueprints  get; set;  = new List<BlueprintSummaryViewModel>();


public class BlueprintSummaryViewModel

    public int Id  get; set; 
    public string Name  get; set; 
    public string Recipe  get; set; 

然后通过Select或Automapper的ProjectTo进行投影:

using (var db = new EfContext())

    PlayerViewModel player = db.Players
        .Where(p => p.id == playerId)
        .Select(p => new PlayerSummaryViewModel
        
            Id = p.Id,
            Name = p.Name,
            // ...
            Blueprints = p.Blueprints.Select(b => new BlueprintSummaryViewModel 
            
                Id = b.Id,
                Name = b.Name,
                Recipe = b.Recipe
            ).ToList()
     ).Single();

使用 automapper,因为属性本​​质上是具有一致命名的 1 对 1 映射,映射本质上看起来像:

var config = new MapperConfiguration(cfg => 

    cfg.CreateMap<Player, PlayerSummaryViewModel>();
    cfg.CreateMap<Blueprint, BlueprintSummaryViewModel>();
;

using (var db = new EfContext())

    PlayerViewModel player = db.Players
        .Where(p => p.id == playerId)
        .ProjectTo<PlayerSummaryViewModel>(config)
        .Single();

可以为所有视图模型集中配置或按需创建配置。

为什么像您建议的那样执行选择性包含这样的事情会很糟糕的一个原因是,任何接受实体的方法都应该能够预期传递的实体是域状态的完整表示,或者至少是完整的 -能代表。如果您有一个采用 Player 实体的方法,则该方法不需要开销来检查该播放器是否包含蓝图,或者是否可以包含蓝图,或者可能只获得一些精简版本的蓝图。视图模型或 DTO 确保传递的类型适合用途,并且很清楚预期是什么。

【讨论】:

以上是关于实体框架:如何从相关实体中选择特定列的主要内容,如果未能解决你的问题,请参考以下文章

在选择类型 Symfony 5 中显示相关实体中的特定字段

如何使用实体框架代码优先从数据库中删除所有相关实体

如何在实体框架中为复合主键的特定列创建外键

CosmicMind / Graph:搜索特定的相关实体

从实体框架中的集合加载相关实体

本机查询 - 如何仅从数据库中检索实体的特定列