Data Access With The Entity Framework

12 בנובמבר 2008

10 תגובות

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.

kick it on DotNetKicks.com

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. (*) שדות חובה מסומנים

10 תגובות

  1. Daniel12 בנובמבר 2008 ב 20:58

    Have you tested this using other IRepository implementations? I've been trying to accomplish the same thing, but in my experience too much of the implementation "leaks" through. For example, try creating an XmlRepository or SqlDataServicesRepository – the filters end up not being compatible between repositories.

    להגיב
  2. Kim13 בנובמבר 2008 ב 4:14

    @Daniel, the only other implementation is an InMemoryRepository. This implementation works off a List<> and on queries basically just compiles the filter predicate expressions and check against that. No leakage so far. :-)

    להגיב
  3. aCoder13 בנובמבר 2008 ב 10:43

    Why all the overloads in the interface?
    Why not just have a method "IQueryable QueryFor()"
    and then you can filter, fetch all, fetch single, join, use any linq query operator.
    The only problem I see is that the client of IRepository, should have problem
    with some LINQ queries when the EF is used underneath. For example, a query like:
    repository.QueryFor().Single(c => c.Id == 1) would fail since the EF only
    supports First() and will throw on Single(). (assuming I remember well)

    If you need more syntactic sugar or shortcuts, you can then have extension methods on your IRepository.
    As long as it returns an IQueryable you can do anything you want. For example:

    public static T FindOne(this IRepository repository, int id) where T : IIdentifiable {
    return repository.QueryFor
    ().FirstOrDefault(x => x.Id == id);
    }

    Also, can you explain the scenario for the BeginTransaction() method on IRepository?
    Shouldn't this be handled internally when you call SaveChanges()?

    להגיב
  4. Scott Allen13 בנובמבר 2008 ב 12:59

    Kim:

    I'm curious to see the implementation for just one of your Get methods for IRepository. Specifically, how you map the generic type parameter to an entity set? I've done it using string mappings, but I'm not too happy with it. Can you share?

    להגיב
  5. Kim13 בנובמבר 2008 ב 13:52

    @aCoder,

    What do you mean by *all* the overloads ? this was the expressiveness I wanted.

    "Why not IQueryable<T>…" – The short answer is that I wanted to prevent deferred execution. The longer answer is more related to design and preferences and that will have to wait for maybe another post.

    Transactions – I'm not sure what's not clear about the need for explicit transactions. In a service we often need to write something to the database. Under certain conditions we need to roll that back.

    להגיב
  6. Kim13 בנובמבר 2008 ב 14:19

    @Scott – The Scott Allen from Herding Code? I'm a fan!
    Regarding the
    "map the generic type parameter to an entity set?"
    I have to check.
    …Just sent you an email through your blog.

    להגיב
  7. Craig13 בנובמבר 2008 ב 15:29

    One of big problems I have heard about EF is the designer is really slow if your DB has > 150 tables. Have you experienced this?

    להגיב
  8. Kim13 בנובמבר 2008 ב 16:33

    If a database has 150 tables, it is possible that they don't all make sense in the same context so you could maybe create a few models. Having said that, the designer is not good enough for big databases. You should at least be able to partition your .edmx into sub views. Maybe in v2…
    We have no .edmx with more than 50 entities. Another point to take into consideration, is that the larger the .edmx the longer the instantiation time the first time you access the object context. I know you can precompile it or something like that, but we haven't had a need for that yet. On my laptop it takes about 1.5 seconds to do the first instantiation and then it drops to a couple of ms.
    If you have used any advanced modeling tool like ERWin you won't feel comfortable in the EF designer.
    Oh, you asked about the speed, sorry. With 50 tables it is fine on a high end dev pc. Haven't tried with more.

    להגיב
  9. John3 בספטמבר 2009 ב 17:18

    How do you add Customer object to context

    repository.Add(customer);
    repository.SaveChanges();

    Can you write down the Add Method of repository class

    להגיב
  10. ToratordDrale25 בפברואר 2011 ב 23:18

    Lyricz74|<3 Just thinking about his death brings tears to my eyes. If anyone deserved aп»ї happy ending, he did. R.I.P.
    http://24pharmshop.com/?p=413

    thplousaqq

    להגיב