IT:AD:EF/CodeFirst:HowTo:Define Models/Relationships
- See also:
Summary
Relationships can be setup either as as:
- EF attributes on the Entity,
- By convention, where it automatically intuits relationships by the way you name properties, etc.
- or at startup using code which they call 'FluentAPI'.
Why Use FluentAPI Only
EF Attributes are verbotten because they would drag a reference to EF everywhere through the app.
The 'convention' approach works for trivial applications – fails when dealing with legacy applications where the db schema is pre defined. It also gets confused, and needs FluentAPI to untangle edge cases.
Use FluentAPI: it seems to be the only way to get exactly what you want. Just takes a little more sweat than Convention.
Types of Relationships
- Not very intuitive, but note how in FluentAPI, an FK is denoted with either
NotOptionalorWithRequiredPrincipal- Note how even when FK's are added to the Principals, we refer to the Navigation properties.
Relationships can be defined as being in subsets of both of the following groups:
- Multiplicity Based:
- One-to-One Relationships
- By Convention: Make both ends have Navigation Reference (not Collection) Properties.
- One-to-Many Relationships
- Many-to-Many Relationships
* Directionality Based:
- Uni-directional relationships
- ie: when only one of the Entities has navigation properties to the other.
- Bi-directional relationships
- ie: when both Entities has navigation properties to the other.
- Multiplicity Based:
- One-to-One Relationships
- One-to-Many Relationships
- Convention:
- Both ends have Navigation Properties
- Many-to-Many Relationships
- Directionality Based:
- Uni-directional relationships
- ie: when only one of the Entities has navigation properties to the other.
- Bi-directional relationships
- ie: when both Entities has navigation properties to the other.
Design CheckList
- Set up Relationships between Objects:
- By Convention:
- MUST:
- Parent entity
- By DataAnnotations:
- By FluentAPI:
- SHOULD: although it can be skipped, MSDN recommends (here and here) including an FK Properties on the Dependent Entity.
- Ie, add to
Invoiceentity aCategoryproperty and aCategoryIdproperty. - Ensure the FK's nullability equals the Nullability of the Principal Entity.
Convention:- Name the FK property as follows:
<NavPropertyName><PrincipalPKName>or<PrincipalClassName><PrincipalPKName>
DataAnnotations: [ForeignKey]FluentAPI: withinDataContext.OnModelCreating():- 1-0|1:
modelBuilder.Entity<OfficeAssignment>().WithOptional(t => t.OfficeAssignment); - 1-1:
modelBuilder.Entity<OfficeAssignment>().WithRequiredPrincipal(t => t.OfficeAssignment); - Note that if you skip the adding of an FK property, EF will make the Dependent entity have a nullable Principal_Id column – which may or may not be acceptable.
- If it is not, you MUST compensate by adding a
modelBuild.Entity<Invoice>().HasRequired(i=>i.Category).
- Multiplicity is automatically determined by combination of:
- Whether Navigation Property is Reference or Collection,
- If FK property exists, whether it is nullable.
Cascade Delete
- If Dependent entity has an FK property, and it is
Nullable, CascadeDelete is removed.- Ie, When Principal (eg: Category) is deleted, Dependent's FK is set to null.
Quick Tutorial: Setting up Relationships with Fluent API
Relationships are defined by specifying up to 3 attributes of a relationship:
- navigation property (required),
- inverse navigation property (optional),
modelBuilder.Entity<E>.WithOptional(e=>e.SomeProp);modelBuilder.Entity<E>.WithRequiredDependent(e=>e.SomeProp);
* foreign key properties (optional).
An example would be start with the Dependent (Invoice), pointing back to a Primary (Category):
modelBuilder.Entity<Invoice>
.HasRequired(i=>i.Category) | .HasOptional(i=>i.Category)
Then you add the multiplicity:
modelBuilder.Entity<Invoice>
.HasRequired(i=>i.Category)
.WithMany(c=>c.Invoices) //notice that we are refering to the Principal here.
Other options at this stage were:
modelBuilder.Entity<Invoice>
.HasRequired(i=>i.Category)
.WithMany(c=>c.Invoices) | .WithOptional(c=>c.Invoices)
You can finish up with applying info regarding the FK:
modelBuilder.Entity<Invoice>
.HasRequired(i=>i.Category)
.WithMany(c=>c.Invoices)
.HasForeignKey(i=>i.CategoryFK) //notice we are back to referring to Principal here.
- HasRequired
- Ensure the Dependent (eg: Invoice) doesn't end up with a null Principal (eg: Category).
- Applied to
dependent(eg: Invoice)
* WithRequirePrincipal
- Applied to principal entity (eg:
Category) - Targets the dependent (eg: Invoice) that has an
CategoryFK
- WithRequiredDependent
- Applied to dependent entity (eg:
Principal), that has an FK towards the principal.
Relationships
Relationships are defined by:
public class Product {
...
public Category Category {get; set;}
}
public clas Category {
public virtual ICollection<Product> Product {get;set;}
}
FKs:
public class Invoice {
public ICollection<LineItem> LineItems {get;set;}
}
public class LineItem {
public int Id {get;set;}
//Conventions: if nullable (int?) relationship is optional.
public int InvoiceId {get;set;}
}
Resources
Single Reference
Inverse Detection occurs naturally if following conventions are met:
- both types define (only) one (reference to or collection) navigation property to the other.
- if not detected automatically, use FluentAPI to set it up.
Automatically:
public class Invoice
{
public int InvoiceId { get; set; }
...
public InvoiceType InvoiceType { get; set; }
}
public class InvoiceType
{
public int InvoiceTypeId { get; set; }
...
public Invoice Invoice { get; set; }
}
* References:
Simple Collection
public class Invoice
{
public Invoice()
{
//Use constructor to create 0-sized collection:
this.Courses = new List<Course>();
}
....
public virtual ICollection<LineItem> LineItems { get; private set; }
}