同一列上具有多个外键的实体框架核心

Posted

技术标签:

【中文标题】同一列上具有多个外键的实体框架核心【英文标题】:Entity Framework Core with Multiple Foreign Key On Same Column 【发布时间】:2019-02-23 07:04:59 【问题描述】:

我有 3 个表与同一个 TransactionLog.DocumentId 列有关系。 我用 DocumentTypeId 区分外键:

1 - 发票, 2 - 借记单, 3 - CreditNote

我为实体搭建脚手架:

public partial class TransactionLog

    public int TransactionLogId  get; set; 
    public int? DocumentId  get; set; 
    public int? DocumentTypeId  get; set; 
    public decimal? Amount  get; set; 

    public CreditNote CreditNote  get; set; 
    public Invoice Invoice  get; set; 
    public DebitNote DebitNote  get; set; 


public partial class Invoice

    public Invoice()
    
        TransactionLog = new HashSet<TransactionLog>();
    

    public int InvoiceId  get; set; 
    public string InvoiceNumber  get; set; 
    public decimal Amount  get; set; 

    public ICollection<TransactionLog> TransactionLog  get; set; 


public partial class DebitNote

    public DebitNote()
    
        TransactionLog = new HashSet<TransactionLog>();
    

    public int DebitNoteId  get; set; 
    public string DebitNoteNumber  get; set; 
    public decimal Amount  get; set; 

    public ICollection<TransactionLog> TransactionLog  get; set; 


public partial class CreditNote

    public CreditNote()
    
        TransactionLog = new HashSet<TransactionLog>();
    

    public int CreditNoteId  get; set; 
    public string CreditNoteNumber  get; set; 
    public decimal Amount  get; set; 

    public ICollection<TransactionLog> TransactionLog  get; set; 

我想在 Invoice、DebitNote 和 CreditNote 表中分别插入 1 条记录,并在 TransactionLog 中为每笔交易插入 3 条记录。

这是我的代码:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    

        modelBuilder.Entity<CreditNote>(entity =>
        
            entity.Property(e => e.Amount).HasColumnType("decimal(18, 4)");

            entity.Property(e => e.CreditNoteNumber)
                .HasMaxLength(50)
                .IsUnicode(false);
        );

        modelBuilder.Entity<DebitNote>(entity =>
        
            entity.Property(e => e.Amount).HasColumnType("decimal(18, 4)");

            entity.Property(e => e.DebitNoteNumber)
                .HasMaxLength(50)
                .IsUnicode(false);
        );

        modelBuilder.Entity<Invoice>(entity =>
        
            entity.Property(e => e.Amount).HasColumnType("decimal(18, 4)");

            entity.Property(e => e.InvoiceNumber)
                .HasMaxLength(50)
                .IsUnicode(false);
        );

        modelBuilder.Entity<TransactionLog>(entity =>
        
            entity.Property(e => e.Amount).HasColumnType("decimal(18, 4)");

            entity.HasOne(d => d.CreditNote)
                .WithMany(p => p.TransactionLog)
                .HasForeignKey(d => d.DocumentId)
                .HasConstraintName("FK_TransactionLog_CreditNote");

            entity.HasOne(d => d.DebitNote)
                .WithMany(p => p.TransactionLog)
                .HasForeignKey(d => d.DocumentId)
                .HasConstraintName("FK_TransactionLog_DebitNote");

            entity.HasOne(d => d.Invoice)
                .WithMany(p => p.TransactionLog)
                .HasForeignKey(d => d.DocumentId)
                .HasConstraintName("FK_TransactionLog_Invoice");
        );
    

但是,DocumentId 没有保存正确的 InvoiceId、CreditNoteId、DebitNoteId。我用 SQL Profiler 检查,它总是会得到 3 个插入的第一个 scope_identity(),在我的例子中是 CreditNoteid。

知道如何从 Invoice、CreditNote 和 DebitNote 中获取正确的 ID 吗? 或者我不应该在这种情况下使用关系。 如果不是,将事务记录到日志中的最佳做法是什么?

【问题讨论】:

能否请您展示您的实体类型配置/类引用? 每个表中的列 InvoiceId、CreditNoteId、DebitNoteId 链接到 DocumentId,其中强制外键约束设置为 false。类引用是如上所示的脚手架实体。 我通过添加 OnModelCreating 编辑了问题 看起来你想要按层次结构继承表。每个 FK 都需要一个单独的列,鉴别器 DocumentTypeId 表示正在使用的列。这违反了第三范式,顺便说一句。 【参考方案1】:

在您的DbContext 中添加以下配置,然后添加迁移并相应地更新数据库。

protected override void OnModelCreating(ModelBuilder modelBuilder)

    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<Invoice>().HasMany(i => i.TransactionLog).WithOne(tl => tl.Invoice).HasForeignKey(tl => tl.DocumentId);
    modelBuilder.Entity<DebitNote>().HasMany(dn => dn.TransactionLog).WithOne(tl => tl.DebitNote).HasForeignKey(tl => tl.DocumentId);
    modelBuilder.Entity<CreditNote>().HasMany(cn => cn.TransactionLog).WithOne(tl => tl.CreditNote).HasForeignKey(tl => tl.DocumentId);

【讨论】:

【参考方案2】:

我认为也许外键的方向是错误的。

作为您的表定义,TransactionLog.DocumentId 的值必须同时存在于三个表中(InvoiceCreditNoteDebitNote)。所以,如果只插入其中两个,可能会有异常。

我想你真的希望表定义是这样的。 我删除了TransactionLog.DocumentId列,并将三个表的PK中的FK添加到TransactionLog.TransactionLogId

实体:

public partial class TransactionLog

    public int TransactionLogId  get; set; 
    public Nullable<int> DocumentTypeId  get; set; 
    public Nullable<decimal> Amount  get; set; 

    public virtual CreditNote CreditNote  get; set; 
    public virtual DebitNote DebitNote  get; set; 
    public virtual Invoice Invoice  get; set; 


public partial class Invoice

    public int InvoiceId  get; set; 
    public string InvoiceNumber  get; set; 
    public decimal Amount  get; set; 

    public virtual TransactionLog TransactionLog  get; set; 


public partial class CreditNote

    public int CreditNoteId  get; set; 
    public string CreditNoteNumber  get; set; 
    public decimal Amount  get; set; 

    public virtual TransactionLog TransactionLog  get; set; 


public partial class DebitNote

    public int DebitNoteId  get; set; 
    public string DebitNoteNumber  get; set; 
    public decimal Amount  get; set; 

    public virtual TransactionLog TransactionLog  get; set; 

代码:

Invoice invoice = new Invoice()  InvoiceNumber = "Inv0100", Amount = 66m ;
TransactionLog invoiceLog = new TransactionLog()  Amount = invoice.Amount, DocumentTypeId = 1 ;
invoice.TransactionLog = invoiceLog;
_context.Invoices.Add(invoice);

CreditNote creditNote = new CreditNote()  CreditNoteNumber = "DN003", Amount = 99.99m ;
TransactionLog creditNoteLog = new TransactionLog()  Amount = creditNote.Amount, DocumentTypeId = 2 ;
creditNote.TransactionLog = creditNoteLog;
_context.CreditNotes.Add(creditNote);

DebitNote debitNote = new DebitNote()  DebitNoteNumber = "CN009", Amount = 77.77m ;
TransactionLog debitNoteLog = new TransactionLog()  Amount = debitNote.Amount, DocumentTypeId = 3 ;
debitNote.TransactionLog = debitNoteLog;
_context.DebitNotes.Add(debitNote);

【讨论】:

一张Invoice可能有多个TransactionLog。例如创建和取消发票,记录到 TransactionLog。 @billy_flow 抱歉,我错过了将强制外键约束设置为false。我设置了它并再次尝试了您的代码。 DocumentId 可以正确设置。也许您问题中DocumentId 的值是正确的。 请尝试将 InvoiceId、CreditNoteId 和 DebitNoteId 设置为不同的值。所以你可以在 DocumentId 中看到不同。 是的。我将 InvoiceId、CreditNoteId 和 DebitNoteId 设置为不同的值,DocumentId 也不同。对吗?【参考方案3】:

我猜你的关系有问题。就像一笔交易可以有多张发票,但一张发票只有一个交易记录。我可能错了,因为有时感觉像是一对一的,但无论如何我试了一下,这就是你想要的。

实体:

public class TestMVCEntities : DbContext


    public TestMVCEntities()
        : base("name=TestMVCEntities")
    
    

    public DbSet<Invoice> Invoices  get; set; 
    public DbSet<DebitNote> DebitNotes  get; set; 
    public DbSet<CreditNote> CreditNotes  get; set; 
    public DbSet<TransactionLog> TransactionLogs  get; set; 


    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        modelBuilder.Entity<TransactionLog>()
            .HasRequired(p => p.Invoice)
            .WithMany(p => p.InvoiceLog)
            .HasForeignKey(p => p.DocumentId);

        modelBuilder.Entity<TransactionLog>()
            .HasRequired(p => p.DebitNote)
            .WithMany(p => p.DebitLog)
            .HasForeignKey(p => p.DocumentId);

        modelBuilder.Entity<TransactionLog>()
            .HasRequired(p => p.CreditNote)
            .WithMany(p => p.CreditLog)
            .HasForeignKey(p => p.DocumentId);
    


public partial class TransactionLog

    public int TransactionLogId  get; set; 
    public int? DocumentId  get; set; 
    public int? DocumentTypeId  get; set; 
    public decimal? Amount  get; set; 

    public CreditNote CreditNote  get; set; 
    public Invoice Invoice  get; set; 
    public DebitNote DebitNote  get; set; 


public partial class Invoice

    public int InvoiceId  get; set; 
    public string InvoiceNumber  get; set; 
    public decimal Amount  get; set; 

    public ICollection<TransactionLog> InvoiceLog  get; set; 


public partial class DebitNote

    public int DebitNoteId  get; set; 
    public string DebitNoteNumber  get; set; 
    public decimal Amount  get; set; 

    public ICollection<TransactionLog> DebitLog  get; set; 


public partial class CreditNote

    public int CreditNoteId  get; set; 
    public string CreditNoteNumber  get; set; 
    public decimal Amount  get; set; 

    public ICollection<TransactionLog> CreditLog  get; set; 
  

并插入数据:

var invoice = new Invoice()
    
        InvoiceNumber = "Inv099",
        Amount = 66m,
        InvoiceLog = new Collection<TransactionLog>()
        
            new TransactionLog()DocumentTypeId = 1, Amount = 66m
        
    ;

    var creditNote = new CreditNote()
    
        CreditNoteNumber = "DN002",
        Amount = 99.99m,
        CreditLog = new Collection<TransactionLog>()
        
            new TransactionLog()DocumentTypeId = 3, Amount = 99.99m
        
    ;

    var debitNote = new DebitNote()
    
        DebitNoteNumber = "CN008",
        Amount = 77.77m,
        DebitLog = new Collection<TransactionLog>()
        
            new TransactionLog()DocumentTypeId = 2, Amount = 77.77m
        
    ;

    using (var context = new TestMVCEntities())
    
        context.Invoices.Add(invoice);
        context.CreditNotes.Add(creditNote);
        context.DebitNotes.Add(debitNote);
        context.SaveChanges();
      

表格将如下所示:

【讨论】:

请尝试将 InvoiceId、CreditNoteId 和 DebitNoteId 设置为不同的值。所以你可以在 DocumentId 中看到不同。

以上是关于同一列上具有多个外键的实体框架核心的主要内容,如果未能解决你的问题,请参考以下文章

实体框架代码优先:具有两个外键的表

使用 CodeIgniter 框架将数据插入到具有外键的多个表中

已添加具有相同键的实体框架核心 3.1.6 项

具有 2 个外键实体框架的表

理解实体框架核心外键关系

实体框架中与代码优先外键的更改冲突