US / UK-EMEA / Contact Ask DevelopMentor a Question800.699.1932

Taking N-Tier a Step Further with EF4

 

By Tony Sneed
 
 
Download code for this article here.

There’s been some discussion lately in the blogosphere on Self-Tracking Entities in EF4 and how well they might fit into a heterogeneous environment, where a Java client might consume a .NET service that exposes Self-Tracking Entities.  While STE’s are placed in an assembly that does not reference the Entity Framework, the way in which change state is preserved in an STE is overly complex and seems to be designed to make it easier for EF to transmit changes to the ObjectStateManager on the service side.  If you look closely at the ObjectChangeTracker class that is generated for the client, you’ll see that it maintains metadata for navigation properties with items that have been added or removed, as well as original values and extended properties.  Requiring a non-.NET client to implement all that is asking an awful lot, and it couples the client too tightly to the service implementation.

About a year ago I wrote an article for MSDN Magazine on how to track change-state on the client and transmit it to a service for persistence using LINQ to SQL, Entity Framework, or some other data access stack.  Each entity has an ObjectState property indicating its change state (Unchanged, Added, Modified, Deleted). This piece of metadata is part of the data contract for each entity and is sent to the service so that inserts, updates and deletes can be performed against the database.  The beauty of this approach is that it allows multiple changes to be sent to a service in a single round trip, where they can all be saved in a single transaction.  The classic example of this is an Order with OrderDetails that have been added, modified or removed.  An UpdateOrder method in the Data Access Layer can simply read the ObjectState property to insert, update or delete OrderDetails.

Fast forward to Entity Framework 4.0 and Visual Studio 2010.  This week I implemented Trackable Data Transfer Objects with EF4 using the same basic architecture that I wrote about in the article.  I use two sets of T4 templates (a code-generation technology built into Visual Studio).  One set is used by the Data Access Layer on the service side to generate both the ObjectContext container class and POCO classes that serve as DTO’s but have an ObjectState property.  On the client side there is an assembly containing another T4 template that generates DTO’s that also have an ObjectState property.  DTO’s on the client also have a Tracking property (to turn change-tracking on and off) and navigation properties that are of type ChangeTrackingCollection<T>.  This collection is placed in a separate ClientChangeTracker assembly that marks entities as Modified, Added or Deleted when a property changes or they are added or removed from the collection.

public enum ObjectState
{
    Unchanged,
    Added,
    Modified,
    Deleted
}
 
The goal of this design is to keep change state as minimal as possible: a simple ObjectState enum, which is exposed as a data contract by the service and can be easily implemented by a non-.NET client.  While client-side DTO’s are generated by a T4 template that references the EF edmx file, there is nothing in the design of the application which requires this.  In fact, the sample app I write for my article uses svcutil to generate client entities when adding a service reference in Visual Studio.  The only reason why I went the T4 route is that I could more easily control the code generation process, and I very well may create a T4 template in the future that uses a service’s metadata (WSDL) to generate entities on the client.

On the service-side there is a ServiceChangeTracker assembly that has a TrackingHelper class with an ApplyChanges methods that extends ObjectContext by walking an object graph and informing the ObjectStateManager of entity state based on the ObjectState property.

public static void ApplyChanges<TEntity>(this ObjectContext context,
    string entitySetName, TEntity entity) where TEntity : ITrackable
{
    // First add and attach entities
    context.AddAttachEntities(entitySetName, entity);
 
    // Then delete entities
    context.DeleteEntities(entitySetName, entity);
}
 
static void AddAttachEntities<TEntity>(this ObjectContext context,
    string entitySetName, TEntity entity) where TEntity : ITrackable
{
    // Iterate collection navigation properties
    foreach (string navPropertyName in
        context.GetNavigationProperties(entity))
    {
        // First, recursively add child entities
        foreach (ITrackable navEntity in context.GetChildEntities
            (entity, navPropertyName, ObjectState.Added))
        {
            context.AddAttachEntities(navPropertyName, navEntity);
        }
 
        // Recursively attach child modified and deleted entities
        foreach (ITrackable navEntity in context.GetChildEntities
            (entity, navPropertyName, ObjectState.Modified,
             ObjectState.Deleted))
        {
            context.AddAttachEntities(navPropertyName, navEntity);
        }
    }
 
    // Add or attach entity
    switch (entity.ObjectState)
    {
        case ObjectState.Unchanged:
            context.AttachTo(entitySetName, entity);
            break;
        case ObjectState.Added:
            context.AddObject(entitySetName, entity);
            break;
        case ObjectState.Deleted:
            context.AttachTo(entitySetName, entity);
            break;
        case ObjectState.Modified:
            context.AttachTo(entitySetName, entity);
            context.SetPropertiesAsModified(entitySetName, entity);
            break;
        default:
            break;
    }
}
 
static void DeleteEntities<TEntity>(this ObjectContext context,
    string entitySetName, TEntity entity) where TEntity : ITrackable
{
    // Iterate collection navigation properties
    foreach (string navPropertyName in
        context.GetNavigationProperties(entity))
    {
        // Recursively delete child entities
        foreach (ITrackable navEntity in context.GetChildEntities
            (entity, navPropertyName, ObjectState.Deleted).ToList())
        {
            context.DeleteEntities(navPropertyName, navEntity);
        }
    }
 
    // Delete entity
    if (entity.ObjectState == ObjectState.Deleted)
    {
        context.DeleteObject(entity);
    }
}
 
When a DAL method invokes SaveChanges on the ObjectContext, inserts, updates and deletes are persisted to the database in the scope of a single transaction.  The DAL can then call AcceptChanges in TrackingHelper to restore objects to an Unchanged state and return the updated object to the client with database-calculated values, such as identity and concurrency fields.

public Order SaveOrder(Order order)
{
    using (NorthwindEntities ctx = new NorthwindEntities
        (Settings.Default.NorthwindConnection))
    {
        // Apply entity changes
        ctx.Orders.ApplyChanges(order);
 
        // Save updated order
        ctx.SaveChanges();
 
        // Return entity to unchanged state
        ctx.AcceptChanges(order);
 
        // Return updated order
        return order;
    }
}
 
Trackable DTO’s match entities defined in the conceptual model, which allows us to leverage POCO support in EF4 to avoid creating two sets of classes and manually copying data between them.  As such they represent an approach that combines the simplicity of self-tracking entities with the flexibility of DTO’s, without the extra baggage carried by STE’s.  The use of two separate sets of T4 templates allows us to decouple client DTO’s from the service DTO’s, applying the rules of data contract versioning so that they can diverge from one another in a robust fashion.

Anthony Sneed
works as an instructor for DevelopMentor and is author of the course Essential LINQ with Entity Framework 4.0. You can reach him at tonys@develop.com.
Connect
Signup for our Free Newsletter!
Latest news
Twitter Feed MORE
There is something to this statement: Why Quit? Because They Have Bigger Monitors http://t.co/9FrGETG5 #dm (via @mkennedy)
20 hours ago (details)
Essential RESTful Services Training. The new #REST course @BrockLAllen and myself where working on is online. http://t.co/XXhGN5JP #dm ^MdB
2 days ago (details)
Testimonials
  • Rod da Silva is the most knowledgable instructor I've ever had in my 10 years of IT experience. Frank J.