Skip to content

Foreign Keys

Leonardo Porro edited this page Feb 21, 2023 · 12 revisions

A foreign key is a property in the dependent entity that are used to store the principal key value for the related entity, so that is a pure database concept.

When an entity is materialized from a query to an in-memory object graph, the principal entity holds a reference to the dependant entity, (i.e.: a Navigation Property). After that, both the property referencing the dependant entity or the foreign key may be updated. Finally, when the ChangeTracker is refreshed, usually by calling dbContext.SaveChanges(), a complex operation is performed to synchornize references and foreing key values.

This library doesn't support foreing key properties more than for copying values from DTOs when performing mapping operations (doesn't apply any special logic between FK and Navigation property, it just delegate this to EntityFramework).

While MS documentation about foreing keys recommends to explicitly define foreing key properties, it works perfectly by letting EF to handle it Adding explicit FKs increses the size and complexity of the entity models by exposing FKs that can also be incorrectly set and lead to issues.

Working without explicit FKs

Given two entities Invoice and InvoiceType, the association between them can be created and queried without the need of an specific FK property. In the example, Invoice is a new entity and InvoiceType was already saved as { Id: 1, Name: "Type A" }. (This is how Detached.Mappers works behind the scenes)

Create relationship by getting the dependant from a query

Using FindAsync(), FirstOrDefault(), etc. a tracked dependent entity can be obtained, and the association is created by setting the related entity property to the dependent entity.

Invoice invoice = new Invoice();
InvoiceType invoiceType = await dbContext.FindAsync<InvoiceType>(1);
            
invoice.InvoiceType = invoiceType;
dbContext.Invoices.Add(invoice);

await dbContext.SaveChangesAsync();

Create relationship, no queries needed

In order to avoid unnecessary queries, a dependent entity with only id value can be created and marked as unchanged, so that, EF will assume that it was loaded from the DB. The association is created in the same way, by setting the related entity property to the dependent entity.

Invoice invoice = new Invoice();
InvoiceType invoiceType = new InvoiceType { Id = 1 };
           
invoice.InvoiceType = invoiceType;
 
dbContext.Entry(invoice).State = EntityState.Added;
dbContext.Entry(invoice.InvoiceType).State = EntityState.Unchanged;

await dbContext.SaveChangesAsync();
Invoice invoice = new Invoice
{
   InvoiceType = new InvoiceType { Id = 1 } // Set Id to the value of existing InvoiceType.
};

dbContext.Entry(invoice).State = EntityState.Added;
dbContext.Entry(invoice.InvoiceType).State = EntityState.Unchanged;

await dbContext.SaveChangesAsync();

Filter/query the relationship

If an Invoice needs to be filtered by InvoiceType without an specific FK, the full navigation path can be used. (Don't worry, InvoiceType won't throw NullReferenceException as it is an expression tree that will be translated to SQL later).

await dbContext.Invoices.Where(i => i.InvoiceType.Id == 1).FirstOrDefaultAsync();

See next