Gáspár Nagy on software

coach, trainer and bdd addict, creator of SpecFlow; owner of Spec Solutions

Entity Framework as an OR/M

by Gáspár on December 10, 2008

I know that Entity Framework (EF) has a lot of concepts behind the OR/M features, but I think most of the people who try it out just use it as an object-relational mapping tool. And it seems that also the tools around it (designer) are also driving you to this direction. And BTW this is the reason why MS could announce it a recommended replacement for LINQ to SQL.

I have also investigated EF from this point (I did a small application with it). Here are my feelings.

Note: I have used the currently released version of the Entity Framework, as I wanted to see, how it is useable now.

Generally I have been quite productive with EF and I have not encountered any problem that I could not solve. On the other hand, it did not really impress me. It does not really try to be smart, you have to do everything you want explicitly. This seems to be a defined concept that I can accept, but in some cases the default behaviors are not defensive enough, which caused some tricky errors in my application.

So I have collected some things where IMHO it makes sense to take extra care when writing an application with EF.

Designer

I think in a larger project the designer will be the first problematic point. As normally the model is modified by multiple developers, I already see the nasty merging issues that you can run into. But even for a single developer, it is very hard to have an overview of your domain model in a single screen, where only 6-8 entities can fit nicely.

I also encountered with funny issues with the designer that does not seem to support the code-first approach to extend your domain model. I wanted to create a new entity, so I selected the Add/Entity in the designer. Then I realized that I cannot map it to a table, until I add a table too – which you cannot do from the designer (at least I could not find out how). The new unmapped class, although it was not used in the application yet, broke all other use-cases too, so my application stopped working. So finally I created the table manually in the database and refreshed the model. It still did not work, and after 10 minutes I realized that the refresh also created a new entity with an “1” postfix and mapped the new table to it – but as the designer surface was full, the generated entity was out of the screen (and I did not scroll down). The only thing that I don’t understand is why it is allowed to create a new entity from the designer then? And of course the same refers to the new properties created in the designer.

Lazy framework

From the OR/M functionality my biggest problem was the way how EF handles the unloaded relations. There is no lazy loading, I knew that. But I was very much surprised that if you access a property that is not loaded yet, you don’t receive an exception! For references, it returns null, for collections an empty collection. This is a great source for hidden errors, so I decided that I’ll consider a line of code like these, simply as a mistake:

Console.WriteLine(product.Category.Name);

DoSomething(product.Category)

foreach(var item in order.Items)
    //…

These lines properly should look like this:

if (!product.Category.IsLoaded)
  product.CategoryReference.Load();
Console.WriteLine(product.Category.Name);

if (!product.Category.IsLoaded)
  product.CategoryReference.Load();
DoSomething(product.Category)

if (!order.Items.IsLoaded)
  order.Items.Load();
foreach(var item in order.Items)
  //…

You say now that this is lazy loading. Ok, you are right, but if you want to be correct and you don’t want to have lazy loading (because it is the evil that makes our application wrong), you still have to write:

if (!product.Category.IsLoaded)
  throw new ProgrammersFaultEx(
    “Don’t you think that this should be loaded, mate?”);
Console.WriteLine(product.Category.Name);

if (!product.Category.IsLoaded)
  throw new ProgrammersFaultEx(
    “Please load the category, please, please, please!”);
DoSomething(product.Category)

if (!order.Items.IsLoaded)
  throw new ProgrammersFaultEx(
    “I told you already, that this wont work!”);
foreach(var item in order.Items)
  //…

You say now that it is too verbose. And you are actually right.

Transactions and query consistency

There is a little bit similar problem with the query consistency. The use case is that you write a method that lists some products.

void ListProducts()
{
    foreach(var p in db.Product.Where(p => p.Color == “blue”))
        Console.WriteLine(“{0} {1}”, p.Name, p.Color);
}

This method will work fine until you call it from an update use case, where you already updated a color of a product from blue to red. In this case you might see something like:

product1 blue
product2 blue
product3 red

Strange, isn’t it? The problem is that the db.SaveChanges(); is only invoked at the end of the transaction, and therefore the database does not know about this change at the time when the method is called. The solution: you have to call the db.SaveChanges() earlier. Clear. But who should do this exactly? The method that updated the color? Just to make sure that methods like ListProducts() work well if they called later? Or should the ListProducts() call it? Just to make sure that it does not depend on pending changes? Or should someone else call it?

Well, of course this is not an easy question (which is good, if you see this as a reason for salary increase), but the funny thing here is that the OR/M knows that there is a pending product change and that you are about to list products! So why I should care?

Collection as a query

Sometimes you don’t want to populate a collection, but you want to use it as a query for further transformations. If you write

order.Items.Where(oi => oi.Product.Color == “blue”)

this will filter the loaded collection (which is an empty, when you forgot to call Load()). If you want to query the database, you have to write:

order.Items.CreateSourceQuery()
    .Where(oi => oi.Product.Color == “blue”)

But this will not contain the items, that are added/removed locally, so you have to call db.SaveChanges(), or check whether it is loaded already, so you end up with:

var collectionToQuery = order.Items.IsLoaded ?
    order.Items : order.Items.CreateSourceQuery();
collectionToQuery.Where(oi => oi.Product.Color == “blue”)

but it does not compile, as order.Items is not queryable… :) So the winner is…

var collectionToQuery = order.Items.IsLoaded ?
    order.Items.AsQueryable() : order.Items.CreateSourceQuery();
collectionToQuery.Where(oi => oi.Product.Color == “blue”)

And now you are finished.

Small annoying things

Of course I’ve found also some small annoying things. Without too much explanation here is a list:

  • Single() / SingleOrDefault() does not work in LINQ to Entities. You have to use First() / FirstOrDefault().
  • There is no operator in LINQ to Entities to express the SQL “LIKE”. They suggest to use Contains(), but LIKE can do more!
  • You cannot map custom methods / properties, so you cannot encapsulate parts of the queries, and also you have no chance to solve things like the missing LIKE.
  • I have not found a way how to map a database field to an enum property. (You need to write a wrapper property).
  • There are not too many ways to control what is cached in the object context, you can Detach() objects individually or skip loading of a query result.
  • If you use the Include() method to add relations to the query load span, you have to specify the expressions to be loaded as string. In the time of lambdas, this is quite ugly IMHO. Also the syntax looks quite strange, if you want to include a collection and a reference in each item in the collection, as you have to write this as: orders.Include(“Items.Product”), although Items is an EntityCollection that does not have a “Product” property.
  • You cannot compare entities in queries, you have to compare the id fields (the id fields should be hidden by the OR/M as much as possible):
    …Where(p => p.Customer == selectedCustomer)
    causes

    “Unable to create a constant value of type ‘Closure type’. Only primitive types (‘such as Int32, String, and Guid’) are supported in this context.”

    and has to be written as:
    …Where(p => p.Customer.Id == selectedCustomer.Id)

4 thoughts on “Entity Framework as an OR/M