同一列上具有多个外键的实体框架核心
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
的值必须同时存在于三个表中(Invoice
CreditNote
DebitNote
)。所以,如果只插入其中两个,可能会有异常。
我想你真的希望表定义是这样的。
我删除了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 中看到不同。以上是关于同一列上具有多个外键的实体框架核心的主要内容,如果未能解决你的问题,请参考以下文章