实体框架和自引用集合

Posted

技术标签:

【中文标题】实体框架和自引用集合【英文标题】:Entity Framework and self-referencing collections 【发布时间】:2021-08-22 01:36:03 【问题描述】:

我正在尝试为位置对象建模。位置可以运送到许多其他位置并从许多位置接收。位置也可以有一个父位置。如何通过实体框架在我的数据库中表示它? 这是我到目前为止所拥有的: 位置类:

public class Location

    public int Id  get; set; 

    public string Name  get; set; 
    
    public string ShortName  get; set; 

    public Location ParentLocation  get; set; 

    public int? ParentLocationId  get; set; 

    public ICollection<Location> ShipToLocations  get; set; 
    public ICollection<Location> ReceiveFromLocations  get; set; 
    

我的 DbContext 类中的 OnModelCreating 函数:

protected override void OnModelCreating(ModelBuilder modelBuilder)

   modelBuilder.Entity<Location>(entity =>
   
       entity.HasKey(x => x.Id);
       entity.HasOne(x => x.ParentLocation);
       entity.HasMany(x => x.ShipToLocations);
       entity.HasMany(x => x.ReceiveFromLocations);
    );

我似乎正在通过数据库获取 ParentLocationId 字段,这对我来说很有意义。 但是,ef 也会为数据库生成 LocationId 和 LocationId1 字段,这是没有意义的。按照我的理解,Entity Framework 应该生成某种形式的连接表,因为每个位置都有许多运送到位置的位置和许多从位置接收的位置,所有这些位置都是其他位置。

【问题讨论】:

TLDR; .HasOne(x =&gt; x.ParentLocation).WithMany() & .HasMany(x =&gt; x.ShipToLocations).WithMany(x =&gt; x.ReceiveFromLocations) .docs.microsoft.com/en-us/ef/core/modeling/… 【参考方案1】:

由于您尝试使用单个表进行此操作,因此 EF 尝试为目标和源位置创建额外的外键。您实际上无法通过一张桌子实现这一目标。您应该有一个额外的表格,表示该位置可用的目的地。

你应该尝试像这样构建你的类:

public class Location

    public int Id  get; set; 

    // other fields here
    
    public int? ParentId  get; set; 
    public Location Parent  get; set; 

    public ICollection<Location> ChildLocations  get;  
    
    public ICollection<LocationDestintaion> Destinations  get; set; 
    
    public ICollection<LocationDestintaion> Sources  get; set; 


public class LocationDestintaion

    public int SourceId  get; set; 
    public Location Source  get; set; 
    
    public int DestinationId  get; set; 
    public Location Destination  get; set; 

那么你会有这种ModelBuilder 配置:

modelBuilder.Entity<Location>()
            .HasOne(x => x.Parent).WithMany(x => x.ChildLocations)
            .HasForeignKey(x => x.ParentId).IsRequired(false);

modelBuilder.Entity<Location>()
            .HasMany(x => x.Destinations).WithOne(x => x.Source)
            .HasForeignKey(x => x.SourceId);
        
modelBuilder.Entity<Location>()
            .HasMany(x => x.Sources).WithOne(x => x.Destination)
            .HasForeignKey(x => x.DestinationId);

modelBuilder.Entity<LocationDestintaion>().ToTable("Paths")
            .HasKey(x => new x.SourceId, x.DestinationId);

然后您可以像这样查询目的地和来源:

const int locationId = 1;

var availableDestinations = context.Locations.Where(x => x.Id == locationId)
            .SelectMany(x => x.Destinations).ToList();

var sourceLocations = context.Locations.Where(x => x.Id == locationId)
            .SelectMany(x => x.Sources).ToList();

【讨论】:

以上是关于实体框架和自引用集合的主要内容,如果未能解决你的问题,请参考以下文章

实体框架检查属性是否为导航属性

使用 IdentityServer4、Asp.Net Core Identity 和自定义提供程序进行 Blazor WebAssembly 身份验证,无需实体框架

集合框架(01)

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

使用 Automapper,将 DTO 映射回实体框架,包括引用的实体

实体框架 + Web API,在 DbContext 之外返回实体(复杂、集合等)