it:ad:code_first:howto:define_models:relationships

IT:AD:EF/CodeFirst:HowTo:Define Models/Relationships

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.

  • Not very intuitive, but note how in FluentAPI, an FK is denoted with either NotOptional or WithRequiredPrincipal
    • 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.
  • 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 Invoice entity a Category property and a CategoryId property.
    • 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: within DataContext.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.
  • 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.

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

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; }
}
  • /home/skysigal/public_html/data/pages/it/ad/code_first/howto/define_models/relationships.txt
  • Last modified: 2023/11/04 02:19
  • by 127.0.0.1