Tuesday, May 21, 2013

Entity Model Design and Configuration for Entity Framework Code First

This is a companion piece to my earlier post entitled Lookup table design for Entity Framework Code First.  This post shows the entity design I use for a non-lookup POCO along with a configuration to relate the entity to a lookup POCO.

Here is the code I use to define my various base classes and interfaces for my primary and lookup POCOs (note a design change to my Lookup entities compare to my earlier blog post).  IDataModelMarker and DataModelMarker are necessary for generic constraints in the rest of my code base.

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace DataModel.Common
  9. {
  10.     /// <summary>
  11.     /// This is a marker base class for all Entity and Lookup models
  12.     /// </summary>
  13.     public abstract class DataModelMarker : IDataModelMarker
  14.     {
  15.     }
  16.  
  17.     /// <summary>
  18.     /// This is a marker interface for all Entity and Lookup models
  19.     /// </summary>
  20.     public interface IDataModelMarker
  21.     {
  22.     }
  23.  
  24.     public interface IEntityModel : IDataModelMarker
  25.     {
  26.         int ID { get; set; }
  27.     }
  28.  
  29.     public interface ILookupModel : IDataModelMarker
  30.     {
  31.         string Value { get; set; }
  32.     }
  33.  
  34.     public abstract class EntityModel : DataModelMarker, IEntityModel
  35.     {
  36.         protected EntityModel() { }
  37.  
  38.         public int ID { get; set; }
  39.     }
  40.  
  41.     public abstract class LookupModel<TEnum> : DataModelMarker, ILookupModel
  42.     {
  43.         [Key, MaxLength(50)]
  44.         public string Value { get; set; }
  45.  
  46.         public TEnum ValueCode { get; set; }
  47.  
  48.         protected LookupModel(string databaseValue, TEnum value)
  49.         {
  50.             this.Value = databaseValue;
  51.             this.ValueCode = value;
  52.         }
  53.     }
  54. }

This code demonstrates a simple Child POCO that inherits from EntityModel.  There is a 1 GenderLookup : Many Child relationship and a 1 Child : Many ChildInquiries relationship, where ChildInquiries is another non-lookup POCO.  Note that I have no interest in ever having to do a select on Gender and then find all related Children so I do not define an ICollection<Child> on my GenderLookup entity.

  1. public class Child : EntityModel
  2. {
  3.     public Child()
  4.     { }
  5.  
  6.     public string FirstName { get; set; }
  7.     public string LastName { get; set; }
  8.     public virtual GenderLookup Gender { get; set; }
  9.     public virtual ICollection<ChildInquiry> Inquiries { get; set; }
  10. }

I use classes derived from EntityTypeConfiguration to fully describe the relationships between my entities.  I have a base class DataModelConfiguration for all of my main table entity configurations (no configuration needed for my lookup entities) and then each main table entity has its own configuration.  Here is the DataModelConfiguration class and the ChildConfiguration class:

  1. public abstract class DataModelConfiguration<TEntity> : EntityTypeConfiguration<TEntity>
  2.     where TEntity : EntityModel
  3. {
  4.     protected DataModelConfiguration(string tableName)
  5.         : base()
  6.     {
  7.         ToTable(tableName);
  8.  
  9.         HasKey(m => m.ID);
  10.         Property(m => m.ID)
  11.             .HasColumnName("Id")
  12.             .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
  13.             .IsRequired();
  14.     }
  15. }
  16. public class ChildConfiguration : DataModelConfiguration<Child>
  17. {
  18.     public ChildConfiguration()
  19.         : base(Context.Tables.TableNames.Child)
  20.     {
  21.         Property(m => m.FirstName).IsOptional().HasMaxLength(100);
  22.         Property(m => m.LastName).IsOptional().HasMaxLength(100);
  23.  
  24.         HasOptional(m => m.Gender).WithMany().Map(m => m.MapKey("Gender"));
  25.         HasMany(m => m.Inquiries).WithRequired(m => m.ChildOfInterest).WillCascadeOnDelete(false);
  26.         HasMany(m => m.Inquiries).WithRequired(m => m.ChildOfInterest).Map(m => m.MapKey("ChildID"));
  27.     }
  28. }

The last step is to add my ChildConfiguration in my DbContext OnModelCreating method override:

  1. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  2. {
  3.     modelBuilder.Configurations.Add(new ChildConfiguration());
  4.     base.OnModelCreating(modelBuilder);
  5. }