Entity Framework Core with Multiple Foreign Key On Same Column

Viewed 3340

I have 3 tables with relationship to the same TransactionLog.DocumentId column. I differentiate the Foreign Key with DocumentTypeId:

1 - Invoice, 2 - DebitNote, 3 - CreditNote

enter image description here

I scaffold the entities:

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; }
}

I want to insert 1 record to each of the table Invoice, DebitNote and CreditNote, and 3 records for each transaction to TransactionLog.

And this is my code:

enter image description here

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");
        });
    }

However, the DocumentId doesn't save the correct InvoiceId, CreditNoteId, DebitNoteId. I check with SQL Profiler, it will always get the first scope_identity() of the 3 inserts, which in my case is CreditNoteid.

enter image description here

Any idea how to get the correct Id from Invoice, CreditNote and DebitNote ? Or shouldn't I use relationship in this case. If not, what is the best practice of logging the transaction into the log?

3 Answers

Add the following configurations in your DbContext and then add a migration and update the database accordingly.

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);
}

I think maybe the Foreign Key's direction is wrong.

As your table definition, the TransactionLog.DocumentId's value must be exist in the three tables (Invoice CreditNote DebitNote) at the same time. So, if only insert two of them, there will maybe have a Exception.

enter image description here

I think you really want table definition is like this. I delete TransactionLog.DocumentId column and add FKs from the PK of the three tables to TransactionLog.TransactionLogId.

enter image description here

the entities:

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; }
}

code:

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);

I guess you have a wrong relationship there. like one transaction can have multiple invoices but an invoice will have only one transaction record. I might be wrong because sometimes it feels like one-to-one but anyways I gave it a try and here is what you want.

the entities:

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; }
}  

and to insert data:

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();
    }  

and the table will look like this:

enter image description here

Related