【问题标题】:EF Core One-To-One Bi-Directional Self-Referencing TableEF Core 一对一双向自引用表
【发布时间】:2021-09-27 13:39:02
【问题描述】:

我在使用 EF Core 时遇到了一些问题。

我收到此错误:

InvalidOperationException:无法保存更改,因为在要保存的数据中检测到循环依赖

这是我的模型:

public class Transaction
{
    public int TransactionId { get; set; }

    public int? MutualTransactionId { get; set; }
    public virtual Transaction MutualTransaction { get; set; }

    public long Debit { get; set; }
    public long Credit { get; set; }
}

这是我的OnModelCreating 配置:

public class Repository : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Transaction>(entity =>
        {
            entity.HasKey(x => x.TransactionId);

            entity.HasOne(x => x.MutualTransaction)
                .WithOne()
                .HasForeignKey<Transaction>(x => x.MutualTransactionId)
                .OnDelete(DeleteBehavior.NoAction)
                .HasConstraintName("FK_Transactions_Mutuals");
        });
    }
}

当我想将一系列数据植入数据库时​​,就会出现问题。

错误本身并不重要。我只是想知道如何使用 EF Core 制作一个一对一的双向自引用表。

每一行引用同一个表中的另一行,被引用的行也使用模型类中的相同属性引用回该行。

【问题讨论】:

  • 不得将英语中的每个单词都大写!
  • 您只能在一次调用中插入有向无环图以保存更改。 [添加、保存、更新、保存],或者在插入第二条记录时编写一个触发器来修复第一条记录。

标签: c# entity-framework one-to-one self-reference bidirectional


【解决方案1】:

您所说的“一对一”关系实际上并不是“一对一”关系,而是一种自我引用的“多对一”关系。

你不需要显式映射这种关系,一个简单的属性映射就可以了,但是如果你想要显式映射/w modelBuilder:

    modelBuilder.Entity<Transaction>(entity =>
    {
        entity.HasKey(x => x.TransactionId);

        entity.HasOne(x => x.MutualTransaction)
            .WithMany()
            .HasForeignKey(x => x.MutualTransactionId)
            .OnDelete(DeleteBehavior.NoAction)
            .HasConstraintName("FK_Transactions_Mutuals");
    });

一对一关系存在于通常共享相同 PK 的两个不同表之间。例如,如果 Transaction 有一些大的二进制列,您通常不需要并且不希望在检索事务时产生加载开销。

这是典型的自引用父样式多对一关系。如果您想确保一个实体仅被另一个实体(1 个子实体)引用并且该子实体只能具有该 1 个父实体,那么您要么必须通过应用程序逻辑强制执行,要么采用连接多对多表在构成复合 PK 的每个键上添加了唯一约束。

更新:如果您希望 MutualTransaction 引用共同依赖项,那么这是允许的,但不强制执行。例如,如果我希望事务 A 和 B 相互依赖,其中 A 的相互是 B,B 是 A,那么您可以使用上述映射和多对一来完成此操作,但这是一个隐含的规则由应用程序(而不是数据库)强制执行,因此您必须依赖代码以及可能的数据库脚本健康检查来规范其强制执行。问题是它需要 2 次显式保存:

var transactionA = new Transaction{ Name = "A" };
var transactionB = new Transaction{ Name = "B", MutualTransaction = transactionA };
context.Transactions.Add(transactionA);
context.Transactions.Add(transactionB);
context.SaveChanges();
transactionA.MutualTransaction = transactionB;
context.SaveChanges();

现在您可以加载任一实体并访问对共同伙伴的引用。

var test = context.Transactions
    .Include(x => x.MutualTransaction)
    .Single(x => x.Name == "A");
// or
var test2 = context.Transactions
    .Include(x => x.MutualTransaction)
    .Single(x => x.Name == "B");

如果这能奏效那就太好了:

var transactionA = new Transaction{ Name = "A" };
var transactionB = new Transaction{ Name = "B", MutualTransaction = transactionA };
transactionA.MutualTransaction = transactionB;
context.Transactions.Add(transactionA);
context.Transactions.Add(transactionB);
context.SaveChanges();

但这会导致 EF 中出现循环引用问题。

【讨论】:

  • 但是我怎样才能从孩子访问父母,反之亦然?我的意思是我可以通过简单地调用例如 var transaction2 = transaction1.MutualTransaction 来获得 Mutual Transacrion,但是可以从同一个属性向后访问它吗?我的意思是这是真的:transaction2.MutualTransaction.TransactionId == transaction1.TransactionId
  • 当我回到我的测试项目时,我不得不尝试一下,然后才回复,但看起来可以将这种相互交易用于您的相互依赖的引用。我已经更新了上面的答案,概述了这是如何完成的以及该方法的注意事项。
【解决方案2】:

你可以试试这个可能有效:

 public class Transaction
    {
        public int TransactionId { get; set; }
        [ForeignKey("MutualTransaction"), Column(Order = 0)]
        public int? MutualTransactionId { get; set; }
        
    
        public long Debit { get; set; }
        public long Credit { get; set; }
        public virtual Transaction MutualTransaction { get; set; }

    }

【讨论】:

    猜你喜欢
    • 2012-03-07
    • 1970-01-01
    • 1970-01-01
    • 2021-06-27
    • 1970-01-01
    • 1970-01-01
    • 2011-08-11
    • 1970-01-01
    • 2019-03-01
    相关资源
    最近更新 更多