Data Access With The Entity Framework
A lot has been written about L2S and the Entity Framework over the last few weeks since the announcement that the Microsoft Data Team will focus their efforts on the Entity Framework. A lot has also been written about all the deficiencies the Entity Framework has and that it is not ready for prime time.
For us the Entity Framework has greatly simplified data access across the board.
Data Access code is tedious, repetitive and boring. I don’t want to focus my energy on how to access data. It should be simple, fast and it should just work. While stored procedures still have their place in certain scenarios and certain environments, most of the time writing sql by hand is a waste of your customers money. Most ORMs will do the work at least as well as you do, if not better. In edge cases you will have to tweak, but for mainstream scenarios the sql generated by most ORM tools is good enough.
I want to point out that this is not an introductory post on how to use the Entity Framework. If you want information on how to work with it, we have assembled a good list on our site. So without further delay, let’s see how we use the Entity Framework at Renaissance.
We made an early decision that we don’t want the Entity Framework to leak too much into our services. For that reason we access our model using the fairly standard repository pattern.
Here’s the main repository interface.
public interface IRepository : IDisposable
{
T[] GetAll<T>();
T[] GetAll<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
void Delete<T>(T entity);
void Add<T>(T entity);
int SaveChanges();
DbTransaction BeginTransaction();
}
As you can see from the interface, the Getters return a generic type or an array of a generic type. Delete and Add affect the repository instance, but will not hit the database. To commit any pending changes in the repository we call the SaveChanges method. If you’re not familiar with C# 3.0 expressions, the code “Expression<Func<T, bool>> filter” might look a little cryptic. Don’t worry, from the consuming side you don’t have to deal with this. What these expressions facilitates, is to allow us to use Lambda Expressions as a specifications for our repository.
The parameter “List<Expression<Func<T, object>>> subSelectors” requires some additional explanation. The Entity Framework will not retrieve related entities when you request a top level entity. For example, if you have a customer that can have many orders you will not receive the orders when you query for a customer. The Entity Framework will only retrieve related data if you explicitly tell it to do so. I don’t have a huge issue with this, but I know a lot of others do. What did bother me tremendously though, was the fact that you have to specify the related entities using string literals.
For example: context.Customer.Include("SalesOrderHeader.SalesOrderDetail");
This was pretty much a showstopper for me, so we introduced the IncludeBuilder. The IncludeBuilder allows you to retrieve related entities as well, but strongly typed. More on that later.
Ok, enough background, let’s look at the code that consumes the repository.
Here’s a simple example of adding a new data.
…
using (var repository = _factory.Create())
using (var tx = repository.BeginTransaction())
{
Customer customer = new Customer();
customer.FirstName = firstName;
repository.Add<Customer>(customer);
repository.SaveChanges();
… Some more code that requires transaction management…
tx.Commit();
}
…
The boilerplate code is generated by a custom CodeRush template “nrp” aka “New Repository” (I just had to insert something related to CodeRush :-) )
Here’s a standard example of retrieving a customer with an ID of 10.
Customer customer = repository.GetSingle<Customer>(p => p.CustomerId == 10);
It can’t get much simpler than that.
If we want to retrieve an entity with its related entities the code is a little more involved, but remember, boiler plate is generated. (Snippets is a decent option if you don’t have CodeRush or R#)
var builder = new IncludeBuilder<Customer>();
builder.Add(p => p.Orders);
customer = repository.GetSingle<Customer>(p => p.CustomerId == customerId, builder.Includes);
If you often retrieve the same hierarchy of data you could always create a specialized repository that implements the IRepository interface and add a GetCustomerWithOrders() method. If you have many variations this will become a burden though.
To summarize, I am basically a happy camper with the Entity Framework. It has a some rough edges, but nothing we haven’t been able to work around.