Entity Framework Code First (EFCF) seems to be Microsoft’s answer to nHibernate. I first came across the technology a couple months ago watching a Channel 9 video from a recent MVC Conference presented by Chris Zavaleta (twitter) and was intrigued as to how useful this could be. The answer I now feel is very useful so keep reading!
Basically EFCF generates your relational database model from your object model. Which when in the early stages of a project I feel can be extremely useful. Being able to change your object model extremely quickly without the worries of creating a new database table, adding new columns to an existing table, updating your ORM or even having to rewrite, or even write new classes in your Data Access Layer (DAL) to handle the mapping of your relational domain to your object domain (Big Hassle!).
Having previously used LINQ to SQL as my DAL I always felt slightly frustrated with it. I like to separate things out and whilst LINQ to SQL provided a convenient way to quickly develop projects shielding me from the ugliness of having to write my own DAL, I always wanted more separation of my entities. I also found it frustrating with it not automatically loading Collections an entity may contain. Yes I can probably guess there are ways to do this but if I’m honest I didn’t have to find out in the end because EFCF came along.
I found EFCF maps my Object Domain to a relational database very well. The only difficulty I had is when I wanted to include additional attributes in join tables. This lead me to research into configuration files for EFCF and it turns out that if you want to have more control over how EFCF maps your object domain to the database you can. I’ve never really worked with nHibernate but have seen some examples of it on projects at work and it would seem that these configuration files are very much like the mapping xml files nHibernate uses. Apart from the EFCF configuration files are written in C# using lambda expressions to define the mappings.
One of the great things I discovered with EFCF is generic repositories. This to me was amazing, no longer must I write the same CRUD LINQ for different entities, I could just write one generic base repository and then inherit the generic functionality, whilst adding specific functionality where necessary. This pattern will be covered in a later blog post!
I used EFCF on a variety of projects before attempting this and finally decided to upgrade my final year project to use this for data access to use as an example for this blog post. Below is an example of three POCO’s.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | public class User { #region Properties public int UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } #endregion #region Collections public virtual ICollection Problems { get; set; } #endregion #region Constructors public McsUser() { Problems = new HashSet() } #endregion } public class Problem { #region Primary Key public int ProblemId { get; set; } #endregion #region Foreign Keys public int StudentId { get; set; } public int CommunicationTypeId { get; set; } public int ProblemTypeId { get; set; } public int MitigatingCircumstanceLevelId { get; set; } #endregion #region Properties public DateTime? DateTime { get; set; } public string Outline { get; set; } public string MitigatingCircumstanceFile { get; set; } public DateTime? AbsentFrom { get; set; } public DateTime? AbsentUntil { get; set; } public DateTime? RequestedFollowUp { get; set; } public bool iCalAppointment { get; set; } public bool OutlookAppointment { get; set; } public virtual Student Student { get; set; } public virtual CommunicationType CommunicationType { get; set; } public virtual MitigatingCircumstanceLevel MitigatingCircumstanceLevel { get; set; } public virtual ProblemType ProblemCategory { get; set; } #endregion #region Collections public virtual ICollection ProblemCommunications { get; set; } public virtual ICollection AssessmentExtensions { get; set; } public virtual ICollection Users { get; set; } #endregion #region Constructor public Problem() { ProblemCommunications = new List(); AssessmentExtensions = new List(); Users = new HashSet(); } #endregion } public class CommunicationType { #region Primary Key public int CommunicationTypeId { get; set; } #endregion #region Properties public string Name { get; set; } #endregion #region Collections public virtual ICollection Problems { get; set; } public virtual ICollection ProblemCommunications { get; set; } #endregion #region Constructors public CommunicationType() { Problems = new List(); ProblemCommunications = new List(); } #endregion } |
Below is my Data Context, the string passed to the base class is the name of the connection string which is stored in my web.config file in my MVC project. As you can see I create Database Sets based upon my Object Domain. The on model creating override allows the developer to gain more control over the way EFCF maps Objects to the database. In my example I provide mapping classes which define primary keys, properties (columns) and relationships.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class McsContext : DbContext { public IDbSet Assessments { get; set; } public IDbSet AssessmentExtensions { get; set; } public IDbSet CommunicationTypes { get; set; } public IDbSet McsUsers { get; set; } public IDbSet MitigatingCircumstanceLevels { get; set; } public IDbSet Modules { get; set; } public IDbSet Problems { get; set; } public IDbSet ProblemCommunications { get; set; } public IDbSet ProblemTypes { get; set; } public IDbSet SeminarGroups { get; set; } public IDbSet Students { get; set; } public IDbSet StudentStatus { get; set; } public McsContext(string connStringName) : base(connStringName) { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Conventions.Remove(); modelBuilder.Configurations.Add(new AssessmentMapping()); modelBuilder.Configurations.Add(new AssessmentExtensionMapping()); modelBuilder.Configurations.Add(new CommunicationTypeMapping()); modelBuilder.Configurations.Add(new UserMapping()); modelBuilder.Configurations.Add(new MitigatingCircumstanceLevelMapping()); modelBuilder.Configurations.Add(new ModuleMapping()); modelBuilder.Configurations.Add(new ProblemMapping()); modelBuilder.Configurations.Add(new ProblemCommunicationMapping()); modelBuilder.Configurations.Add(new ProblemTypeMapping()); modelBuilder.Configurations.Add(new SeminarGroupMapping()); modelBuilder.Configurations.Add(new StudentMapping()); modelBuilder.Configurations.Add(new StudentStatusMapping()); } } |
The Configurations contain the mappings between the Entities. The class below shows a Primary Key Mapping, a One to Many Mapping and Property Mappings.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class UserMapping : EntityTypeConfiguration { public UserMapping() { #region Primary Key this.HasKey(x => x.UserId); #endregion #region Foreign Keys #endregion #region Properties this.Property(x => x.UserName); this.Property(x => x.LoweredUserName); this.Property(x => x.ApplicationName); this.Property(x => x.Email); this.Property(x => x.Comment); this.Property(x => x.Password); this.Property(x => x.PasswordQuestion); this.Property(x => x.PasswordAnswer); this.Property(x => x.IsApproved); this.Property(x => x.LastActivityDate); this.Property(x => x.LastLoginDate); this.Property(x => x.LastPasswordChangedDate); this.Property(x => x.CreationDate); this.Property(x => x.IsOnline); this.Property(x => x.IsLockedOut); this.Property(x => x.LastLockedOutDate); this.Property(x => x.FailedPasswordAttemptCount); this.Property(x => x.FailedPasswordAttemptWindowStart); this.Property(x => x.FailedPasswordAnswerAttemptCount); this.Property(x => x.FailedPasswordAnswerAttemptWindowStart); this.Property(x => x.MobileAlias); this.Property(x => x.IsAnonymous); #endregion #region Collections HasMany(x => x.Problems) .WithMany(y => y.Users) .Map(m => { m.ToTable("UserProblems"); }); #endregion ToTable("Users"); } } |
There are many other mappings available in the Configuration files.
One to Many
1 2 3 4 | HasRequired(x => x.CommunicationType) .WithMany(y => y.Problems) .HasForeignKey(z => z.CommunicationTypeId) .WillCascadeOnDelete(false); |
Inheritance Mapping which you include in the abstract parent entity. Here you define a discriminator column and a unique string value for each child. This enables EFCF to cast the object to the correct child when reading relational data. There are many ways you can store Inheritance Hierarchies such as Table Per Type, Table per Hierarchy etc. Below shows inheritance mapping for four classes which all inherit from a class called Review.
1 2 3 4 5 6 | #region Inheritance Mapping this.Map(x => x.Requires("ReviewType").HasValue("1")) .Map(x => x.Requires("ReviewType").HasValue("2")) .Map(x => x.Requires("ReviewType").HasValue("3")) .Map(x => x.Requires("ReviewType").HasValue("4")); #endregion |
Here’s the resolution for my previous difficulty with Many to Many Mappings. The issue was that I had a scenario where an Event can contain many bands and Bands can be part of many events. However it would be useful to store the start and end time of the Bands appearance at an event, which would be stored in the join table.
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class EventConfiguration : EntityTypeConfiguration { public EventConfiguration () { #region Primary Key this.HasKey(x => x.EventId); #endregion #region Foreign Keys this.HasMany(x => x.Bands) .WithRequired(z => z.Event) .HasForeignKey(z => z.EventId); #endregion } } |
Band inherited from User in my example (Just an explanation for missing Primary Key / Properties.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class BandConfiguration : EntityTypeConfiguration { public BandConfiguration() { #region Primary Key #endregion #region Foreign Keys this.HasMany(x => x.BandEvents) .WithRequired(y => y.Band) .HasForeignKey(z => z.BandId); #endregion } } public class EventBandConfiguration : EntityTypeConfiguration { public EventBandConfiguration() { #region Primary Key this.HasKey(x => new { x.EventId, x.BandId }); #endregion #region Foreign Keys this.HasRequired(x => x.Event) .WithMany(y => y.Bands) .HasForeignKey(z => z.EventId) .WillCascadeOnDelete(false); this.HasRequired(x => x.Band) .WithMany(y => y.BandEvents) .HasForeignKey(z => z.BandId) .WillCascadeOnDelete(false); #endregion #region Properties this.Property(x => x.StartDateTime); this.Property(x => x.EndDateTime); #endregion } } |
I believe I then just had two Collections in my POCO’s that just represented the One to Many relationship between the Entity and Join table.
1 2 | public virtual ICollection Events { get; set; } public virtual ICollection Bands { get; set; } |
For simple joins that do not contain additional attributes EFCF allows you to model this quite easily. Just create Collections in both classes but initialise the collection as a HashSet in the Entities Constructor.
User Entity
1 2 3 4 5 6 7 8 9 | #region Collections public virtual ICollection Problems { get; set; } #endregion #region Constructors public McsUser() { Problems = new HashSet(); } #endregion |
Problem Entity
1 2 3 4 5 6 7 8 9 | #region Collections public virtual ICollection Users { get; set; } #endregion #region Constructor public Problem() { Users = new HashSet(); } #endregion |
With Configuration Mappings
User:
1 2 3 4 5 | #region Collections HasMany(x => x.Problems) .WithMany(y => y.Users) .Map(m => { m.ToTable("UserProblems"); }); #endregion |
You can download my sample project here!
It‘s quite in here! Why not leave a response?