views:

211

answers:

2

What's the preferred approach when using L2E to add behavior to the objects in the data model?

  • Having a wrapper class that implements the behavior you need with only the data you need

        using (var dbh = new ffEntities())
        {
            var query = from feed in dbh.feeds select 
                        new FFFeed(feed.name, new Uri(feed.uri), feed.refresh);
            return query.ToList();
        }
        //Later in a separate place, not even in the same class
        foreach (FFeed feed in feedList) { feed.doX(); }
    
  • Using directly the data model instances and have a method that operates over the IEnumerable of those instances

        using (var dbh = new ffEntities())
        {
            var query = from feed in dbh.feeds select feed;
            return query.ToList();
        }
        //Later in a separate place, not even in the same class
        foreach (feeds feed in feedList) { doX(feed); }
    
  • Using extension methods on the data model class so it ends up having the extra methods the wrapper would have.

        public static class dataModelExtensions {
            public static void doX(this feeds source) {
                //do X
            }
        }
        //Later in a separate place, not even in the same class
        foreach (feeds feed in feedList) { feed.doX(); }
    

Which one is best? I tend to favor the last approach as it's clean, doesn't interfere with the CRUD facilities (i can just use it to insert/update/delete directly, no need to wrap things back), but I wonder if there's a downside I haven't seen.

Is there a fourth approach? I fail at grasping LINQ's philosophy a bit, especially regarding LINQ to Entities.

+1  A: 

The Entity classes are partial classes as far as i know, so you can add another file extending them directly using the partial keyword.

Else, i usually have a wrapper class, i.e. my ViewModel (i'm using WPF with MVVM). I also have some generic Helper classes with fluent interfaces that i use to add specific query filters to my ViewModel.

Botz3000
+1  A: 

I think it's a mistake to put behaviors on entity types at all.

The Entity Framework is based around the Entity Data Model, described by one of its architects as "very close to the object data model of .NET, modulo the behaviors." Put another way, your entity model is designed to map relational data into object space, but it should not be extended with methods. Save your methods for business types.

Unlike some other ORMs, you are not stuck with whatever object type comes out of the black box. You can project to nearly any type with LINQ, even if it is shaped differently than your entity types. So use entity types for mapping only, not for business code, data transfer, or presentation models.

Entity types are declared partial when code is generated. This leads some developers to attempt to extend them into business types. This is a mistake. Indeed, it is rarely a good idea to extend entity types. The properties created within your entity model can be queried in LINQ to Entities; properties or methods you add to the partial class cannot be included in a query.

Consider these examples of a business method:

public Decimal CalculateEarnings(Guid id)
{
    var timeRecord = (from tr in Context.TimeRecords
                      .Include(“Employee.Person”)
                      .Include(“Job.Steps”)
                      .Include(“TheWorld.And.ItsDog”)
                      where tr.Id = id
                      select tr).First();
    // Calculate has deep knowledge of entity model
    return EarningsHelpers.Calculate(timeRecord); 
}

What's wrong with this method? The generated SQL is going to be ferociously complex, because we have asked the Entity Framework to materialize instances of entire objects merely to get at the minority of properties required by the Calculate method. The code is also fragile. Changing the model will not only break the eager loading (via the Include calls), but will also break the Calculate method.

The Single Responsibility Principle states that a class should have only one reason to change. In the example shown on the screen, the EarningsHelpers type has the responsibility both of actually calculating earnings and of keeping up-to-date with changes to the entity model. The first responsibility seems correct, the second doesn't sound right. Let's see if we can fix that.

public Decimal CalculateEarnings(Guid id)
{
    var timeData = from tr in Context.TimeRecords
                   where tr.Id = id
                   select new EarningsCalculationContext
                   {
                       Salary = tr.Employee.Salary,
                       StepRates = from s in tr.Job.Steps
                                   select s.Rate,
                       TotalHours = tr.Stop – tr.Start
                   }.First();
    // Calculate has no knowledge of entity model
    return EarningsHelpers.Calculate(timeData); 
}

In the next example, I have rewritten the LINQ query to pick out only the bits of information required by the Calculate method, and project that information onto a type which rolls up the arguments for the Calculate method. If writing a new type just to pass arguments to a method seemed like too much work, I could have also projected onto an anonymous type, and passed Salary, StepRates, and TotalHours as individual arguments. But either way, we have fixed the dependency of EarningsHelpers on the entity model, and as a free bonus we've gotten more efficient SQL, as well.

You might look at this code and wonder what would happen if the Job property of TimeRecord where nullable. Wouldn't I get a null reference exception?

No, I would not. This code will not be compiled and executed as IL; it will be translated to SQL. LINQ to Entities coalesces null references. In the example query shown on the screen, StepRates would simply return null if Job was null. You can think of this as being identical to lazy loading, except without the extra database queries. The code says, "If there is a job, then load the rates from its steps."

An additional benefit of this kind of architecture is that it makes unit testing of the Web assembly very easy. Unit tests should not access a database, generally speaking (put another way, tests which do access a database are integration tests rather than unit tests). It's quite easy to write a mock repository which returns arrays of objects as Queryables rather than actually going to the Entity Framework.

Craig Stuntz
I was missing the projection part, thanks for a great answer. Although I'm still not sure if I get the philosophy. The idea is for each business class to get the data it needs via L2E directly, instead of the load all needed data, pass it around, store it back approach that old ORMs encouraged?
Vinko Vrsalovic
No, I don't think that business classes should get data directly. Instead, the business classes(EarningsHelpers and EarningsCalculationContext, in my trivial example) should be persistence-ignorant. There would be a service or repository layer which queries the required data, projects it onto a business type, and hands it off to the business method. Yes, you are correct that I don't like the idea of keeping lots of data for lots of different methods in memory for a long time. I feel that shared state is the enemy of scalability.
Craig Stuntz