开发者社区 问答 正文

如何使用EF将三角形关系插入数据库

您好,我有一个数据库,其中有3个表设计成三角形,如下所示:

File  has many Page 
Page  has many Segment
File  has many Segment

当我尝试插入类型的实体时,File出现以下错误:

InnerException {“ MERGE语句与FOREIGN KEY约束\” FK_Segment_File_FileId \“冲突。冲突发生在数据库\” mydatabase \“,表\” dbo.File \“的列'Id'中。\ r \ n已终止。“} System.Exception {Microsoft.Data.SqlClient.SqlException} 楷模

public class File
{
  public int Id{get;set;}
  public ICollection<Segment> Segments{get;set;}
  public ICollection<Pages> Pages{get;set;}
}
public class Page
{
  public int Id{get;set;}
  public ICollection<Segment> Segments{get;set;}
}
public class Segment
{
  public int Id{get;set;}

  public Page Page{get;set;}
  public int PageId{get;set;}  //foreign key from page

  public File File{get;set;}
  public int FileId{get;set;} //foreign key from file
}

语境

public class MyContext: DbContext {
        public DbSet<Segment> Segments { get; set; }
        public DbSet<Page> Pages { get; set; }
        public DbSet<File> Files { get; set; }
        public SXSContext(DbContextOptions<MyContext> options):base(options) {

        }
        protected override void OnModelCreating(ModelBuilder modelBuilder) {

            modelBuilder.Entity<File>(ent => {
                ent.ToTable("File");
                ent.HasKey(x => x.Id);
                ent.Property(x => x.Id).IsRequired();
            });
            modelBuilder.Entity<Page>(ent => {
                ent.ToTable("Page");
                ent.HasKey(x => x.Id);

                ent.Property(x => x.Id).IsRequired();
                ent.Property(x => x.FileId).IsRequired();
                ent.HasOne(y => y.File).WithMany(t => t.Pages).HasForeignKey(g => g.FileId).OnDelete(DeleteBehavior.NoAction);
            });
            modelBuilder.Entity<Segment>(ent => {
                ent.ToTable("Segment");
                ent.HasKey(x => x.Id);
                ent.Property(x => x.Id).IsRequired();
                ent.Property(x => x.PageId).IsRequired();
                ent.Property(x => x.FileId).IsRequired();
                ent.HasOne(y => y.Page).WithMany(t => t.Segments).HasForeignKey(g => g.PageId).OnDelete(DeleteBehavior.Cascade);
                ent.HasOne(y => y.File).WithMany(t => t.Segments).HasForeignKey(g => g.FileId).OnDelete(DeleteBehavior.Cascade);
            });
        }
    }

用法

static async Task Main(string[] args) {
            await Task.Delay(1);

            DbContextOptionsBuilder<MyContext> optionsBuilder = new DbContextOptionsBuilder<MyContext>();
            optionsBuilder.UseSqlServer(connectionString);
            SXSContext context = new SXSContext(optionsBuilder.Options);
            context.Database.EnsureCreated();
            List<Page> pages = new List<Page>{
                new Page{
                Segments = new List<Segment> {
                    new Segment{  },
                    new Segment{  },
                    new Segment{ }}
                },
                new Page{
                Segments = new List<Segment> {
                    new Segment{  },
                    new Segment{  }}
                }
            };
            File file = new File {  Pages = pages };
            context.Files.Add(file);

            context.SaveChanges();
            int val = await context.SaveChangesAsync();

    }

PS难道是一个问题,我有Segment外交键作为required,他们可能他们的父母之前得到插入File和Page?

应该如何像这样的模式被插入?我开始与File我有Pages和Segments他们不posess id的,直到插入。

可以将它们插入一个操作中,还是必须从根开始插入(对于我而言是)File?

展开
收起
祖安文状元 2020-01-06 12:03:06 505 分享 版权
阿里云 AI 助理回答

在您的数据库模型中,确实存在一个挑战,即Segment实体同时依赖于PageFile。当您尝试在一个事务中创建包含嵌套对象(如页面和段落的文件)时,由于外键约束,可能会遇到问题。这是因为您试图为Segment同时设置PageIdFileId,而这两个父级实体(PageFile)尚未在数据库中拥有自己的ID。

解决这个问题的一种方法是分步进行插入操作,确保每次插入都遵循引用完整性。具体步骤如下:

  1. 首先保存File实体:因为File没有直接依赖其他实体来获取其ID,所以可以先将其保存到数据库中。这一步将为File分配一个ID。
context.Files.Add(file);
await context.SaveChangesAsync();
  1. 更新页面以关联已保存的File实体:在保存File后,您现在有了File的ID,可以将其分配给每个PageFileId属性,并且可以添加页面到上下文中。
foreach (var page in pages)
{
    page.FileId = file.Id; // 确保每个Page都知道其所属的File的Id
}
context.Pages.AddRange(pages); // 添加所有页面到上下文
await context.SaveChangesAsync(); // 保存更改,此时Pages和对应的FileId已经正确关联
  1. 最后保存Segment实体:既然现在Page也已经有了ID(因为在上一步中已经保存),您可以为每个Segment指定正确的PageId和从第一步获得的FileId(因为一个Segment通过Page间接关联到File)。然后将它们添加到上下文中并保存。
foreach (var page in pages)
{
    foreach (var segment in page.Segments)
    {
        segment.PageId = page.Id; // 确保Segment知道其所属的Page的Id
        segment.FileId = file.Id; // Segment也可以直接知道File的Id
    }
}
// 注意:如果Segments已经在Pages的集合中,则无需再次AddRange,因为EF Core会跟踪这些变化。
await context.SaveChangesAsync(); // 最终保存所有更改

通过这种方式,您按照依赖关系顺序逐步构建了数据库中的记录,从而避免了外键约束冲突。这种方法确保了每次尝试关联实体时,所需的父实体ID都已经存在。

有帮助
无帮助
AI 助理回答生成答案可能存在不准确,仅供参考
0 条回答
写回答
取消 提交回答