Wednesday, 18 May 2011

Entity Framework 4.1: Riding the Magic Unicorn

UPDATE: there is now a VS2013 version of the sample project.

Although it was released back in April, it’s taken me until now to actually try out Entity Framework 4.1 (aka Magic Unicorn).  It’s very impressive and does a surprisingly large amount of the work of getting data from databases and putting it into objects (and vice versa) without the need for visual designers (although they’re still there) or field-by-field mapping.  The only bad thing I can say about it is that for something so feature-rich, documentation is thin on the ground, so I shall add what I’ve learnt to the scant body of knowledge out there.  Other than ADO.NET team blog, most of what I didn’t work out myself came from the invaluable Morteza Manavi’s blog – so I won’t repeat what’s already there.
To provide a little context (no pun intended), what drew me to EF 4.1 was the possibility of mapping an existing database to an existing object model.  This, to my mind, is the way that ORM should be done – i.e. you create a data schema according to the best practices of relational design, create an object model based on the domain requirement, then use ORM to get them to talk to one another.  Basically I think you should design object models with your OOP developer hat and design data schemas with your DBA hat.  Although there may be similarities between the two models, it’s rare that they’ll be exactly the same.
Anyway, I’ve spent this week mapping a complex object model to an equally complex data structure and despite thinking (at times) that Magic Unicorn didn’t support the type of mapping I was attempting, it proved me wrong on all counts and happily transformed my data schema into my object model.  I’ve also done some tinkering with EF across WCF, but I won’t bring that into the mix just yet as it comes with a whole load of caveats and scariness.

The basics

It’s worth mentioning that if your objects have the same name as the tables they map to and your properties have the same name as the fields, then you don’t need to do much at all.  EF will automatically infer the mappings based on a set of conventions – all you have to do is create the DbContext.  This is all covered by Scott Gu’s blog post.  The only caveat is that all POCOs you want to persist must have a key field.
In order to do custom mapping you have to override the OnModelCreating method of the DbContext and use EF’s fluent API to set up the mappings.  Most of the examples out there put all the mapping rules in the OnModelCreating method like so (I’ve included the DbContext setup for clarity):
public class MyDatabaseContext : DbContext
{
 public MyDatabaseContext()
  : base("data source=(local);initial catalog=MyDatabase;integrated security=True")
 { }

 public DbSet<MyClass> MyClasses { get; set; }

 protected override void OnModelCreating(DbModelBuilder modelBuilder)
 {
  modelBuilder.Entity<MyClass>()
   .Property(c => c.SomeFieldOrOther)
   .HasColumnName("StrangelyDifferentFieldName");
  modelBuilder.Entity<MyClass>()
   .Ignore(c => c.AFieldIDontWantMapped);
 }
}
I prefer to use an EntityTypeConfiguration class as it’s cleaner and more maintainable.  Basically you create one EntityTypeConfiguration for every class on which you want custom mapping and put the mapping code in the constructor:
public class MyClassEntityTypeConfiguration : EntityTypeConfiguration<MyClass>
{
 public MyClassEntityTypeConfiguration()
 {
  Property(c => c.SomeFieldOrOther).HasColumnName("StrangelyDifferentFieldName");
  Ignore(c => c.AFieldIDontWantMapped);
 }
}
Then add a new instance of that class to the DbContext’s configurations (via the OnModeCreating method), like so:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
 modelBuilder.Configurations.Add<MyClass>(new MyClassEntityTypeConfiguration());
}
You can add in as many entity type configurations as you like, but if you don’t add one for a class then the default mapping rules will apply.
As an aside, something worth noting in the above examples is the use of the Ignore() method. This is used because any property in a class that doesn’t map to a field in the database must be explicitly ignored (except for enumerations which are ignored by default).

The example models

The sample project for this can be downloaded here.
For illustration I will use the scenario of a product sales LoB system.  Let us imagine that we have a company that uses field sales people to sells widgets.  The sales people can make either a sales or support visit to a customer – the model for this is shown below:
Visit model
Although the data model is probably close to what I’d actually use in such a situation, I have intentionally designed it to be ‘awkward’ via the use of differently named fields/properties, schemas, odd relationships etc:
Sales sample ERD

The code

In this section I’ll just explain a couple of the tricks I’ve used to get the mapping done.  Although the sample project includes mapping of object hierarchies I won’t bother explaining this as it’s covered better elsewhere.
  • Enumerations
    UPDATE: Later versions of EF do this natively. Don't use the code below.
    If you want to coerce a numeric value into an enumeration then just expose the database field as a numeric property and get the property setters to do the work for you.  In the sample, I’ve done this on the Visit class using the following enumeration:
    public enum VisitTypes : short
    {
    Sales = 1,
    Support = 2
    }
    The enumeration property ‘Type’ gets its value from the ‘VisitTypeId’ property (which maps to the field of the same name in the database).
    public class Visit
    {
     private short? _visitTypeId;
     private VisitTypes? _type;
    
     public VisitTypes? Type
     {
      get
      {
       return _type;
      }
      set
      {
       _type = value;
       _visitTypeId = null;
    
       if (_type.HasValue)
       {
        _visitTypeId = (short)_type.Value;
       }
      }
     }
    
     public short? VisitTypeId
     {
      get
      {
       return _visitTypeId;
      }
      set
      {
       _visitTypeId = value;
       _type = null;
    
       if (_visitTypeId.HasValue)
       {
        VisitTypes visitType;
    
        if (Enum.TryParse<VisitTypes>(_visitTypeId.ToString(), out visitType))
        {
         _type = visitType;
        }
       }
      }
     }
    }
    By using the Enum.TryParse method we can ensure that any values in the database that don't match the ones available in the enumeration force the enum field to null.
  • Multiple tables into one object
    This has been done in the SupportCheckList class in the sample. Simply call the Map method for every table that you want to include, using the ToTable and Properties methods in the lambda expression:
    public SupportChecklistEntityTypeConfiguration()
    {
     HasKey(s => s.Id);
    
     Map(m =>
      {
       m.ToTable("SupportCheckList", "Data");
       m.Properties(s => new
       {
        s.Id,
        s.WasIssueFixedDuringVisit,
        s.HasCustomerBeenGivenFollowUpDate
       });
      });
    
     Property(s => s.Id)
      .HasColumnName("SupportCheckListId")
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    
     Map(m =>
      {
       m.ToTable("SupportCheckListSafety", "Data");
       m.Properties(s => new
       {
        s.HasProductBeenMadeSafe,
        s.ProductFailureCausedIncident
       });
      });
    }
    The Properties method takes an anonymous type which contains a list of the fields which are mapped from the given table. You can then do column mapping as per normal (outside of the Map method) and EF will know where to look for the field.
    If you want to get multiple objects into one table then have a look at this post.
I won’t explain the various cardinality mappings because it’s quicker just to look at the sample.  There’s a mass of odd relationships and strange keys that I’ve put in there purposely to show how to use the Map() method of the fluent API.

No comments:

Post a Comment