Sunday, August 1, 2010

Self-Tracking framework with Code-Only – now supports Entity Framework CTP4

Someone recently asked if I could update my framework to support the latest CTP4 release of the Entity Framework Feature library. Lucky I had some spare time last night – you can download the latest version from here.

The framework now uses the new EF DbContext class instead of ObjectContext. What this means is that you can now benefit from the productivity enhancements afforded by DbContext – such as model discovery. I suggest you have a look at the following blog for a good overview on what’s new in CTP4.

Now, granted, there’s a lot more I could have done. The CTP4 release now supports the data annotations attributes. By decorating your entity class properties with these attributes, the CTP4 release will now automatically use these attributes as part of its code-first code convention programming model. In essence this means that, for the most part, you can use these attributes instead of having to use the fluent interface to map your entities. You can certainly benefit from these now – however note the following shortcomings which I hope to address in future:

  • Provide support for these attributes in my T4 template. Specifically, it’ll be nice if one could also use the [RelatedTo] data annotation attribute instead of my custom [Persist] attribute.
  • The current framework still only uses the Enterprise Library 4.1 validation block instead of the latest 5.0 version. This latest EntLib validation block release now also supports the data annotations attributes, thus this would be a good fit in promoting the principles of DRY.

Finally, I’ve made one minor refactoring in order to give you more flexibility in mapping your entities:

  1. public interface IMappingStrategy
  2. {
  3.     void MapEntities(ModelBuilder modelBuilder);
  4. }

An implementation of this IMappingStrategy interface is now taken as a parameter to the EntityFrameworkRegistrar constructor. The idea is that you can provide your own implementation to automate mapping from your entities to the domain model. For example, I provide a MappingByEntityConfiguration implementation so that you can continue to create mapping classes for each of your entities. Alternatively, you can provide another implementation that will allow you to do all your mapping in one class by simply using the fluent interface already provided by the EF ModelBuilder class as per the below example:

  1. public class MyMappingStrategy : IMappingStrategy
  2. {
  3.     #region IMappingStrategy Members
  4.  
  5.     public void MapEntities(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
  6.     {
  7.         // Map company
  8.         modelBuilder.RegisterSet<DomainModel.Company.Company>();
  9.         var company = modelBuilder.Entity<DomainModel.Company.Company>();
  10.         company.HasKey(c => c.Id);
  11.         company.Property(u => u.ConcurrencyToken)
  12.             .IsRequired()
  13.             .IsConcurrencyToken()
  14.             .HasStoreType("timestamp")
  15.             .IsComputed();
  16.         company.HasOptional(c => c.Ceo).WithOptionalDependent(x => x.CeoOfCompany);
  17.  
  18.         // Map Employee
  19.         modelBuilder.RegisterSet<Employee>();
  20.         var employee = modelBuilder.Entity<Employee>();
  21.         employee.Property(u => u.FirstName)
  22.                 .IsRequired()
  23.                 .IsVariableLength()
  24.                 .IsNotUnicode()
  25.                 .HasMaxLength(Employee.MaxNameLength);
  26.  
  27.         employee.Property(u => u.MiddleName)
  28.             .IsOptional()
  29.             .IsVariableLength()
  30.             .IsNotUnicode()
  31.             .HasMaxLength(Employee.MaxNameLength);
  32.     }
  33.  
  34.     #endregion
  35. }

You could take this a step further and use reflection to discover all your entities and call the ModelBuilder RegisterSet<TEntity> automatically for all entities (much like what I already do in the provided MappingByEntityConfiguration class implementation). Then, provided your entities are already decorated with the data annotation attributes, you probably could get away without having to use the fluent interface to further configure your entity mapping.

It’s also interesting to note that the ModelBuilder class also has a RegisterEdmx(string) method providing you with yet another potential way of mapping your entities (although this edmx approach is probably not the most practical approach when already using the code-only approach). My point is – you’ve got options with IMappingStrategy!!

More on using the Self-Tracking framework…

I’ve been using a version of this framework at my current employer. Actually, the version you have here is a drastically stripped down version of what I’ve already developed – unfortunately I won’t be able to provide you with the full version of this framework...but at least let me share some ideas and thoughts on how you could progress this framework further in your own time:

  • Use WCF!!! No really, the self tracking entities are designed to work in a 3-tier architecture. Specifically, most of the “self-tracking” capabilities don’t really kick in (i.e. become enabled) until after the entities are de-serialised. As such, I recommend that you provide a WCF service endpoint to your client applications – this WCF service will allow the client applications to save an object graph of self-tracking entities. Thus,
    • for 2-tier deployment architectures, run your WCF process in-process instead of allowing the client to directly manipulate the DAL,
    • To support unit-testing, debugging and flexibility in deployment, make it easy to run your WCF service either in-process or out-of process (i.e. external as per normal)
  • Use of the lazy loading support provided by the EF is not recommended* – in fact, I’ve turned it off in my framework. Instead:
    • Provide an ability in your framework to specify, per entity object, which navigation properties should be eager loaded by default,
    • Provide support for lazy loading over WCF. Furthermore, lazy loading shouldn’t occur implicitly, but rather should be explicitly triggered by the user. For example, have a method on Company called LoadCompanyDivisions() instead of loading the company divisions automatically the first time the divisions property is accessed. Bear in mind that the UI/client side threading model (e.g. WPF vs Silverlight) could have a bearing on how this lazy loading is performed, so ensure that you cater for this via, for example, an IoC-type solution.
    • Combine the concepts of eager loading with lazy loaded navigation properties. This will greatly improve scalability and performance aspects in your application architecture by minimizing both the number of WCF service calls as well as separate calls to your database backend.

* To use Lazy loading in EF, your entity objects are “proxied” at run-time so as to support the implicit lazy loading behavior – however these proxied objects won’t serialize and as such you have to do some extra WCF work to get around this. In addition, this EF lazy loading won’t work client side…but client-side lazy loading can be quite useful when used wisely.