【问题标题】:EntityFramework Include Without Guarantee of ExistenceEntityFramework 包含但不保证存在
【发布时间】:2021-01-19 17:02:26
【问题描述】:

我有一个问题,我不确定如何解决。我有三个模型,类似如下。

public class Parent : BaseEntity {
  [Key]
  public string Guid { get; set; }
  
  [NotMapped]
  public List<Child> Childs { get; set; }
}

public class Child : BaseEntity {
  [Key]
  public string Guid { get; set; }

  public string ParentGuid { get; set; }

  public List<Detail> Details { get; set; }
}

public class Detail : BaseEntity {
  [Key]
  public string Guid { get; set; }
  
  [ForeignKey(nameof(Child))]
  public string ChildGuid { get; set; }
  public Child Child { get; set }
}

我试图同时包括孩子和细节。但是,我不能保证子表中的 Guid 存在于父表中。这就是我选择 NotMapped 的原因,但如果需要,我愿意并且能够改变它。现在我有这个:

query.GroupJoin(context.Parents,
                          parent => parent.ChildGuid,
                          child => child.Guid,
                          (parent, childs) => new
                          {
                              Parent = parent,
                              Childs = childs
                          }
                        )
                        .AsEnumerable()
                        .Select(combos =>
                        {
                            combos.Parent.Childs = combos.Childs.ToList();
                            return combos.Parent;
                        })
                        .AsQueryable(); 

但这当然不包括细节。不确定我是否在这里朝着正确的方向前进,但如果有人以前遇到过这个问题,可以使用一些方向。

【问题讨论】:

    标签: c# entity-framework .net-core


    【解决方案1】:

    典型的父子关系在子表/实体上具有 ParentID。您的实体定义似乎反映了这一点,但随后您的 Linq 表达式引用了您的实体中未提及的一些 parent.ChildGuid。

    首先让我们更正您的实体定义关系。不应排除儿童。一个父级可以有 0 个或多个子级。集合应声明为virtual ICollection&lt;T&gt;,而不是具体的List&lt;T&gt;virtual 启用延迟加载,并有助于确保 EF 代理完全可用于跟踪集合中项目的更改。我们还应该使用新的具体列表初始化这些集合。这有助于确保我们创建的任何新实体都准备好接受孩子。

    public class Parent : BaseEntity 
    {
      [Key]
      public string Guid { get; set; }
      
      public virtual ICollection<Child> Childs { get; set; } = new List<Child>();
    }
    
    public class Child : BaseEntity 
    {
      [Key]
      public string Guid { get; set; }
    
      public string ParentGuid { get; set; }
    
      public virtual ICollection<Detail> Details { get; set; } = new List<Detail>();
    }
    
    public class Detail : BaseEntity 
    {
      [Key]
      public string Guid { get; set; }
      
      [ForeignKey(nameof(Child))]
      public string ChildGuid { get; set; }
      public virtual Child Child { get; set }
    }
    

    如果您在命名中遵循受支持的约定,EF 可以自动映射关系。您的 ID 列可能使用“Guid”语法,EF 不会自动解决这些问题,因此您可能需要通过配置为其提供一些帮助。例如在 DbContext 的 OnModelCreating 中:

    // EF Core
    modelBuilder.Entity<Parent>()
        .HasMany(x => x.Childs)
        .WithOne()
        .IsRequired() // If a Child must have a ParentGuid
        .HasForeignKey(x => x.ParentGuid);
    

    由于 Child 有一个 ParentGuid 但没有声明 Parent 引用,我们使用没有映射的WithOne(),然后使用HasForeignKey 来建立关系。同样我们设置 Child 和 Details 的关系:

    // EF Core
    modelBuilder.Entity<Child>()
        .HasMany(x => x.Details)
        .WithOne(x => x.Child)
        .IsRequired() // If a Detail must have a Child
        .HasForeignKey(x => x.ChildGuid);
    

    在这种情况下,因为我们在 Detail 上有一个 Child 引用,所以我们映射到它。

    现在关于这个声明:

    但是,我不能保证子表的 Guid 存在于父表中。

    由此,它在某种程度上暗示了 Child 表上的 ParentGuid 可能不存在于 Parents 表中。从数据库规范化和确保有效数据状态来看,这将是一个相当严重的问题。我强烈建议避免尝试使用非规范化模式,例如尝试在多个父表之间“共享”子表。一个典型的例子是有一个地址表(子)引用一个可能指向客户(父)或站点(父)和/或其他类似父表的父 ID。相反,您应该考虑一种更规范化的方法,该方法将使用 CustomerAddress 和 SiteAddress 等链接表来确保可以映射出这些关系。

    如果 Child 可以在没有 Parent 的情况下存在,那么您只需删除 .IsRequired()

    现在查询时,您无需特别担心加入和分组,只需通过导航属性查询并在使用实体图时急切加载所需的相关数据,或使用@987654330 投影数据@查询详情时:

    var query = context.Parents
        .Include(x => x.Childs)
            .ThenInclude(x => x.Details)
    

    要让孩子及其关联的父母:

    var query = context.Parents
        .Include(x => x.Childs)
            .ThenInclude(x => x.Details)
        .SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
        .ToList();
    

    如果你想包含没有父母的孩子:

    var query = context.Parents
        .Include(x => x.Childs)
            .ThenInclude(x => x.Details)
        .SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
        .Union(context.Childs.Where(x => x.ParentGuid == null))
        .ToList();
    

    这些是您要执行的查询类型的粗略猜测。同样,如果 ParentGuid 可能引用数据库中不存在的行或不同的表,我真的会考虑纠正它以确保数据保持引用完整性。我不建议尝试“破坏” EF 行为以使用有效破坏的架构。您可能会让它看起来可以工作,但它很容易导致异常或意外行为。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-21
      • 2011-08-02
      • 1970-01-01
      • 2014-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多