一对多关系不会在实体框架中检索数据

Posted

技术标签:

【中文标题】一对多关系不会在实体框架中检索数据【英文标题】:One to many relationship doesn`t retrieve data in entity framework 【发布时间】:2021-11-17 21:15:34 【问题描述】:

我正在学习 C# & .NET 和 EF(使用 aspnetboilerplate),我想出了创建一些虚拟项目的想法,以便我可以练习。但是过去 4 小时我遇到了这个错误,希望这里有人可以帮助我。

我创建的(至少我认为我正确地创建了它)是 2 个名为“Ingredient”和“Master”的类

我想用它来分类“大师”类的成分。

例如像这样的成分

鸡胸肉 鸡腿

它们都属于 Meat(女巫在“Master”数据库中输入),这是我的代码

成分.cs

   public class Ingrident : Entity


    public string Name  get; set; 
    public Master Master  get; set; 
    public int MasterId  get; set; 


Master.cs

public class Master : Entity

    
        public string Name  get; set; 
        public List<Ingrident> Ingridents  get; set;  = new();
    
    

IngridientAppService.cs

public List<IngridientDto> GetIngWithParent()
    
        var result = _ingRepository.GetAllIncluding(x => x.Master);
         //Also I try this but doesn`t work 
        // var result = _ingRepository.GetAll().Where(x => x.MasterId == x.Master.Id);
        return ObjectMapper.Map<List<IngridientDto>>(result);
    

IngridientDto.cs

  [AutoMap(typeof(IndexIngrident.Entities.Ingrident))]

public class IngridientDto : EntityDto

    public string Name  get; set; 
    public List<MasterDto> Master  get; set; 

    public int MasterId  get; set; 
 

MasterDto.cs

    [AutoMap(typeof(IndexIngrident.Entities.Master))]

    public class MasterDto : EntityDto
    
     public string Name  get; set; 
    

当我创建(最后一次练习)M -> M 关系时,这种方法与 .getAllIncluding 工作但现在当我有一个 -> 很多它不会工作。

希望有人能够帮助我,或者至少给我一些好的提示。

祝你有美好的一天!

【问题讨论】:

在 GitHub 上创建一个从 aspnetboilerplate/module-zero-core-template 派生的 repro 项目。 【参考方案1】:

直截了当地提到的示例(关于存储库等)过于复杂,并且在大多数情况下,这不是您想要实现的。

我看到的第一个问题是,虽然您的实体设置为从 Master 到 Ingredients 的一对多关系,但您的 DTO 是从 Ingredient 到 Masters 设置的,这肯定不会正确映射。

从最简单的事情开始。摆脱存储库并摆脱 DTO。我不确定基类“Entity”的作用,但我猜它公开了一个名为“Id”的通用键属性。对于初学者,我可能也会放弃它。当涉及到主键时,通常有两种命名方法,每个表使用一个名为“Id”的 PK,或者每个表使用一个 TableName 后缀为“Id”的 PK。 IE。 “Id”与“IngredientId”。就我个人而言,我发现第二个选项在配对 FK 和 PK 时非常清楚,因为它们具有相同的名称。

在通过导航属性表示关系时,一个重要的细节是确保导航属性链接到它们各自的 FK 属性(如果存在),或者更好的是,为 FK 使用阴影属性。

以您的成分表为例,去掉 Entity 基类:

[Table("Ingredients")]
public class Ingredient : Entity

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int IngredientId  get; set; 
    public string Name  get; set; 
    public int MasterId  get; set; 
    [ForeignKey("MasterId")]
    public virtual Master Master  get; set; 

此示例使用 EF 属性来帮助告诉 EF 如何将实体属性解析为相应的表和列,以及成分和主对象之间的关系。 EF 可以通过约定解决大部分问题,但最好理解并明确应用它,因为最终您会遇到约定无法按预期工作的情况。

识别(主)键并指示它是一个身份列也告诉 EF 期望数据库将自动填充 PK。 (强烈推荐)

在 Master 方面,我们做类似的事情:

[Table("Masters")]
public class Master : Entity

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int MasterId  get; set; 
    public string Name  get; set; 
    [InverseProperty("Master")]
    public virtual ICollection<Ingredient> Ingredients  get; set;  = new List<Ingredient>();

我们再次表示主键,对于我们的成分集合,我们使用 InverseProperty 属性告诉 EF 它应该使用另一侧(成分)的哪个属性与此主成分列表相关联。

属性只是设置关系等的一个选项。其他选项是使用实现IEntityConfiguration&lt;TEntity&gt; (EF Core) 的配置类,或者将它们配置为 DbContext 中OnModelCreating 事件的一部分。最后一个选项我只推荐用于非常小的项目,因为它可以很快开始成为一种上帝的方法。您可以将其拆分为对各种私有方法的调用,但您也可以使用 IEntityConfiguration 类。

现在,当你带着它的 Master 或 Master 带着它的原料去取成分时:

using (var context = new AppDbContext())

    var ingredients = context.Ingredients
        .Include(x => x.Master)
        .Where(x => x.Master.Name.Contains("chicken"))
        .ToList();
    // or

    var masters = context.Master
        .Include(x => x.Ingredients)
        .Where(x => x.Name.Contains("chicken"))
        .ToList();

   // ...

存储库模式是一个更高级的概念,有一些很好的实施理由,但在大多数情况下,它们不是必需的,并且是 EF 实施中的反模式。我认为始终的通用存储库是 EF 实现的反模式。 IE。 Repository&lt;Ingredient&gt;使用存储库的主要原因,尤其是带有 EF 的通用存储库是您自动增加了实现的复杂性和/或削弱了 EF 可以为您的解决方案带来的功能。正如您从使用示例中看到的那样,简单地将急切加载传递到存储库意味着编写复杂的Expression&lt;Func&lt;TEntity&gt;&gt; 参数,而这仅涵盖急切加载。如果没有 EF 开箱即用的这些功能,支持投影、分页、排序等会增加更多样板复杂性或限制您的解决方案和性能。

考虑研究存储库实现 /w EF 的一些充分理由:

促进单元测试。 (存储库比 DbContexts/DbSets 更容易模拟) 集中低级数据规则,例如租赁、软删除和授权。

考虑存储库的一些不好(尽管很常见)的原因:

从引用或对 EF 的依赖知识中提取代码。 抽象代码以便替换 EF。

投影到 DTO 或 ViewModel 是使用 EF 构建高效且安全的解决方案的一个重要方面。目前尚不清楚“ObjectMapper”是什么,它是 Automapper Mapper 实例还是其他东西。我强烈建议通过使用 Linq 的 Select 语法从模型中填充所需的 DTO 来开始掌握投影。正确使用 Projection 的第一个关键区别是,当您投影对象图时,您确实不需要担心急切加载相关实体。投影中引用的任何相关实体/属性 (Select) 将根据需要自动加载。稍后,如果您想利用 Automapper 之类的工具来帮助消除 Select 语句的混乱,您将需要配置映射配置,然后使用 Automapper 的 ProjectTo 方法而不是 MapProjectTo 与 EF 的 IQueryable 实现一起使用,将您的映射解析为 SQL,就像 Select 一样,Map 需要返回所有急切加载的内容以填充相关数据。 ProjectToSelect 可以产生更有效的查询,与急切加载整个对象图相比,它们可以更好地利用索引。 (在数据库和服务器/应用程序之间通过网络传输的数据更少)Map 仍然非常有用,例如您希望将值从 DTO 复制回加载的实体的场景。

【讨论】:

【参考方案2】:

这样做

public class Ingrident:Entity


    public string Name  get; set; 
    [ForeignKey(nameof(MasterId))]
    public Master Master  get; set; 
    public int MasterId  get; set; 


【讨论】:

以上是关于一对多关系不会在实体框架中检索数据的主要内容,如果未能解决你的问题,请参考以下文章

实体框架中的可选一对多关系[关闭]

使用实体框架在 ASP.NET MVC 中为具有一对多关系的数据库播种

实体框架4.1代码优先中的一对多关系

如何以一对多关系访问数据?

如何在greenDAO中设置一对多的关系?

如何在 SQL Server 或实体框架中创建没有主键的一对多关系?