【问题标题】:EF Core 3.1 - build tree structure from different entitiesEF Core 3.1 - 从不同实体构建树结构
【发布时间】:2021-11-03 15:50:08
【问题描述】:

我正在开发一个仓库系统,其中我有 Pack 和 Load 实体。 现实生活中的结构是这样的:

仓库:

  • 包1
    • 加载1
  • 包2
    • 包3
      • 加载2
    • 包4
      • 包5
        • 加载3
      • 包6
        • 加载4

等等

如您所见,Packs 可以在其他 Packs 中,而那些也可以在其他 Packs 中,并且在链的末端,有 Loads。 Pack 和 Load 实体完全不同,它们唯一的共同点是它们是上述树结构的一部分。 我试图通过创建一个基本抽象类(StorageEntity)来实现这一点,Packs 并且负载可以继承。

StorageEntity 类将具有允许将对象用作树节点的字段:

  public abstract class StorageEntity
    {
        public int ContainerId { get; set; }
        public virtual StorageEntity Container { get; set; }
        public virtual ICollection<StorageEntity> Content { get; set; }
    }

我的问题:

  1. 我不知道如何使用实体框架进行这项工作。我试图使用流体 API 来配置机制,如下所示:

     modelBuilder.Entity<StorageEntity>()
                     .HasMany(p => p.Content)
                     .WithOne(s => s.Container);
    

但是当我尝试添加迁移时出现错误:

The entity type 'StorageEntity' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.

如果我将 HasNoKey() 添加到配置中,我会得到一个空引用异常。

  1. 这种方法是否正确?有哪些替代方案?

【问题讨论】:

    标签: entity-framework-core


    【解决方案1】:

    这是非常常见的节点结构,如文件夹和文件所示。

    通常您会声明一个根文件夹(顶级节点),其中注册了子文件夹和文件作为聚合实体上的集合(子节点),或者让根文件夹成为聚合。

    public abstract class BaseEntity 
    {
      public long Id {get; set;} 
    }
    
    public class Pack : BaseEntity
    {
      public long StorageEntityId {get; set;}
      public virtual StorageEntity ParentStorageEntity {get; set;}
    
      public virtual Pack ParentPack {get; set;} 
    
      public virtual ICollection<Pack> SubPacks {get; set;}
      public virtual ICollection<Load> Loads {get; set;}
    }
    
    public class Load : BaseEntity
    {
      // Load entity's props here
    }
    
    public class StorageEntity : BaseEntity
    {
      public virtual Pack RootPack {get; set;}
    }
    

    您的所有数据库实体都应该有一个主键。 这应该并且会在没有配置的情况下正确地为您的迁移搭建支架。虽然为了更容易理解数据库上的实际配置,我只能建议对 EF Core 使用显式流畅配置。

    modelBuilder.Entity<StorageEntity>()
      .HasOne(t => t.RootPack)
      .WithOne(t => t.ParentStorageEntity)
      .HasForeignKey<Pack>(t => t.ParentStorageEntityId);
    
    modelBuilder.Entity<Pack>()
      .HasMany(t => t.SubPacks)
      .WithOne(t => t.ParentPack);
    
    modelBuilder.Entity<Pack>
      .HasMany(t => t.Loads)
      .WithOne();
    
    modelBuilder.Entity<Pack>
    

    查询它可能有点棘手, 但你可以做类似的事情

    var flattenedPacks = await _context.Packs.Where(t => t.ParentStorageEntityId == storageId).ToListAsync();
    var flattenedLoads = await _context.Loads.Where(t => t.ParentStorageEntityId == storageId).ToListAsync();
    // assemble them in a map method or use changetracking through EF core 
    
    var rootPack = flattenedPacks.FirstOrDefault(t => t.ParentPack == null);
    
    root packs structure should now because of EF Core's changetracking have the correct structure. :o) 
    

    【讨论】:

    • 对不起,我不明白这里发生了什么。解决方案的重点应该是使用通过基类 (StorageEntity) 共享的字段,而无需在派生类中定义新字段(就像您在 Pack 类中所做的那样)。为什么要在 Pack 中定义 ParentStorageEntity 和 ParentPack?为什么要对 Loads 和 SubPacks 使用两个单独的列表?如果我们想在树结构中添加第三、第四等对象类型怎么办?
    • 对不起,可能是我的误解。如果您想要 Pack => Pack => Load => Money => Oranges 结构,那么您可以使用继承。 EF Core 可以用一个鉴别器列来告诉它是ef core inheritance 的类型虽然我不是这个实现的忠实粉丝,因为当有多个“NodeTypes”时表确实会变得非常混乱你可以考虑使用 NodeEntity动态字段
    • 完全正确 - 我自己做了一些研究,我发现我可以使用 ef 继承(每个层次结构类型的表)来实现这一点。但正如你所说 - 这看起来很乱,在我的情况下,它会在表格中产生很多未使用的单元格。不知道有没有更好的办法。
    猜你喜欢
    • 1970-01-01
    • 2021-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-20
    • 1970-01-01
    • 2010-11-11
    • 1970-01-01
    相关资源
    最近更新 更多