;

Persistence Ignorant POCOS part 2 - Entity Framework

Posted : Tuesday, 30 November 2010 08:58:42

This is the second post in a series comparing the developer experience of hooking up a POCO application to connect to an existing database schema using a selection of the more popular ORM tools. In this post I will first briefly describe the database schema and then go on to describe the process involved in hooking it up using version 4 of Microsoft's Entity Framework.

So the database schema is pretty simple and is shown below:

As you can see it consists of four tables and various foreign-key constraints. There is one table-per-class and the foreign-keys correspond to the relationships between the entities (see PART1) for a refresher. I aslo added a datetime column called insert_date to every table with a default value of getdate(). I generally do this whenever I'm building an application as I've found it invaluable when debugging various issues. Its not in any way essential to do this and these fields play no role in the application but they do serve to illustrate one of the 'features' of EF which I'll cover later. EF4 POCO does have support for lazy loading and change tracking but for the purposes of this post I'm not covering that functionality here.

Right then, on to the first example. After reading a few blog posts including the ADO.NET team blog. I added a new class library called LcaModel.EntityFramework project to the solution. I right-clicked (or left-clicked if you're a leftie ;-) the new project and selected add new item from the context menu. I selected a new ADO.NET Entity Data Model item from the list of available item types and entered the name EfLcaModel. I then chose the option to "Generate From Database". At this point I selected the option to create a new database connection and went through the wizard to create an Entity Framework connection string and store it in App.config (more on this later). I entered LcaModel and left all the other options in their default state. After completing the wizard two new files appear in the project one with a .edmx extension and beneath that is a .Designer.cs file. The next step in the process is to turn off code generation. To do this select a blank area of the designer surface and then in the properties window choose "None" as the Code Generation Strategy - incidentally if you double-click the corresponding Designer.cs file before doing this you will see a bunch of auto generated boiler-plate code which is the non-POCO EF data model. If you view the contents of this file after you change the Code Generation Strategy you will see all the auto-generated code disappears leaving only a comment with instructions on how to reenable code-generation.

So, this is where it starts to get a bit tricky. What you need to do, is open up the edmx file in an xml editor (do this by opening the context menu and choosing to open with "XML editor". There are four distinct sections to an edmx file. Its not a massive consolation but only three of these relate to the model itself. The bottom section is purely aesthetic and relates to the visual layout and is helpfully commented such. The exact nature of the edmx file is well beyond the scope of this post but there are three distinct sections. A storage section containing information about the database schema, a conceptual section related to the domain class model and a mapping section that maps the storage section to the conceptual section. In order to get EF to use my classes I had to go first go through the conceptual layer section and make sure all the class members used the names assigned in my domain model classes rather than the member names generated by the wizard. Next I had to go through the mapping layer section and make sure that the mapping between the storage layer and conceptual layer was also updated to use the correct property names.

During this process, Visual Studio will throw out a fair number of errors that aren't always the most informative. I found it a great help to take a copy of the original auto-generated mapping file prior to making any changes and refer back to it when I encountered such error messages that I couldn't fix.

So once I had the mapping file set up correctly I had to add a special class deriving from ObjectContext, this is the means by which EF communicates with the database (incidentally it is also this ObjectContext derived class that manages change tracking and lazy loading). I added a class to the project called LcaContext. For each EntitySet in the Conceptual layer section of the edmx file I added an ObjectSet<T> strongly typed on the relevant model class. An ObjectSet is a strongly typed collection of entities that provides the interface through which enties are retrieved and persisted to the database. The entire listing for this class is shown below:

    8     public class LcaContext : ObjectContext

    9     {

   10         public LcaContext(string connectionString)

   11             : base(connectionString)

   12         {

   13             Auctions = CreateObjectSet<Auction>();

   14             Items = CreateObjectSet<Item>();

   15             Bidders = CreateObjectSet<Bidder>();

   16             Bids = CreateObjectSet<Bid>();

   17         }

   18 

   19         public ObjectSet<Auction> Auctions { get; private set; }

   20         public ObjectSet<Item> Items { get; private set; }

   21         public ObjectSet<Bidder> Bidders { get; private set; }

   22         public ObjectSet<Bid> Bids { get; private set; }

   23 

   24     }

That was the hard work done. The final two "wiring up" steps in the process were as follows. First I created a repository class for each of the IRepository interfaces and implemented the necessary code. These are pretty trivial classes so I've only shown the listing of one of them.

    6     public class EfBidderRepository : IBidderRepository

    7     {

    8         LcaContext _ctx;

    9 

   10         public EfBidderRepository(string connectionString)

   11         {

   12             _ctx = new LcaContext(connectionString);

   13         }

   14 

   15         public void Add(Bidder bidder)

   16         {

   17             _ctx.Bidders.AddObject(bidder);

   18             _ctx.SaveChanges();

   19         }

   20 

   21         public IEnumerable<Bidder> GetBidders()

   22         {

   23             return _ctx.Bidders;

   24         }

   25     }

The final step was to add a NinjectModule derived class to the project and and override the Load method to supply instances of the EF repository classes when requested. I also added a post build event to copy the project assembly of the LcaModel.EntityFramework project to the bin directory of the LightsCameraAuction project such that it would be loaded from the bin directory when the Console application launched.

In order to run the project, I just switched the command-line parameter to the LightsCameraAuction console project to "EntityFramework" and hey presto, the application got persisted with no knowledge of (or more importantly no dependence upon) the persistence mechanism being used.

There are a few things worth mentioning about the EF wiring process. The developer experience when altering the edmx file was reasonably strightforward but the error messages generated weren't particulary enlightening. If I found myself doing this too often i would consider creating some kind of automatic process to scan the class model via reflection and provide a list of mapping suggestions. Also as I mentioned at the beginning of this post I came across one mild irritation when EF resetting my insert_date fields. In order to make sure they weren't part of the model I removed those fields from the storage section of the edmx file. If I then updated model from database then these fields got added back in. I tried adding StoreGeneratedPattern="Computed" attributes to the storage layer but these also got reset on update. Overall I found the process to be fairly straightforward with only minor niggles. The code-first approach to EF development promises to make the whole experience a lot smoother. While getting down and dirty with config files isn't everyones idea of fun, personally I think it forces one to understand whats going on under the covers so its not all bad. Next I'll take a look at NHibernate and see how that compares.....see ya soon.

  • (This will not appear on the site)