span.sup { vertical-align:text-top; }

Data Points

The Entity Framework In Layered Architectures

John Papa

This column is based on a prerelease version of the ADO.NET EntityFramework. All information herein is subject to change.

Code download available at:DataPoints2008_07.exe(3,549 KB)

Contents

Defining Layers
Building the Model
How It Works
Persisting Changes
Deleting and Adding
Wrapping It Up

When architects of an n-tiered architecture evaluate any new technology, pattern, or strategy, they have to consider how that new piece of the puzzle is going to mesh with the architecture. With the Entity Framework, integration is not a problem. It can be integrated into an n-tier architecture as well as a single-tier architecture.

In this month's column I will demonstrate how the Entity Framework can fit into an n-tier architecture that uses Windows® Communication Foundation (WCF) and Windows Presentation Foundation (WPF) technologies and the Model View Presenter (MVP) pattern. I will present a sample architecture that contains layers for a logical store database, data access, a domain model, a business manager, a service layer, a presentation layer, and a passive UI layer, and I will demonstrate how these layers integrate using the Entity Framework. All of the code examples I use are available for download from the MSDN® Magazine Web site.

Defining Layers

The application I'll present allows a user to search for customers in the NorthwindEF sample database and view, add, edit, or delete them. Before diving into the code and the examples, let's discuss the overall architecture of the sample. Because my focus is not the architecture itself, but how to integrate the Entity Framework with an architectural design, I chose a relatively common architecture that can be modified and integrated fairly easily with other strategies.

Figure 1 shows a high-level view of a typical layered architecture. The top two layers handle the user interface presentation and navigation using a UI layer and a presentation layer. The UI layer can be implemented in any one of a variety of technologies; however, for this column and its examples I will use WPF. The UI layer follows the MVP pattern with a passive view, which means that the views (the top UI layer) are managed and directed by the presentation layer. The presenters are responsible for giving the views their data, pulling data from the views to be saved in the lower layers, and in general for responding to events raised by the views.

fig01.gif

Figure 1 Architectural Overview

The presenters communicate with the lower tiers through WCF in my example here. A presenter will invoke a service through WCF using the service's contract as a guide. The service layer exposes its services through service contract interfaces. These contracts allow the presenters to be certain of how to call the services.

The service layer is responsible for receiving the communications from the presenters and calling the appropriate business layer methods that will execute appropriate business logic and data gathering or modifications. The business layer is where the business logic and the LINQ to Entities code for this project would reside. The LINQ to Entities code will reference the entity model that is generated from the Entity Framework. When the LINQ queries are executed, the Entity Framework will translate the LINQ query to the conceptual entity model (the Entity Data Model, or EDM), map the entity aspects to the storage layer, and generate a SQL query to execute against the database.

Building the Model

Now that I've provided a high-level overview of how the layers work in the architecture, let's examine the key aspects of each layer as they pertain to the Entity Framework. Since the database already exists for the application, I generated the entity model from the NorthwindEF database as a starting point.

I could have built the entity model first and then mapped the entities to the database as well. The EDM wizard helps generate the base entity model, which can then be modified as needed to incorporate inheritance, entity splitting, and other domain modeling concepts. Figure 2 shows the EDM wizard with all tables and stored procedures selected to be imported into the EDM.

fig02.gif

Figure 2 Generating the Model from the Database

One topic that often causes people confusion in regard to EDMs is the default naming convention for the EntitySets and the Entity­Types. I like to use singular names for all of my entities in my domain models. I create an instance of a Customer or return a list of Order instances using List<Order>. Each entity is a singular instance of a blueprint that has properties defining the entity.

On the other hand, I like my EntitySets to use a plural naming convention. The Entity­Sets are often used in LINQ queries when asking the ObjectContext to refer to its set of Customers or Orders.

As an example of this, take a look at the following LINQ to Entities query:

var q = from c in context.Customers
        select c;
List<Customer> customerList = q.ToList();

This query tells LINQ to Entities to access the Customers EntitySet and return all Customer entity instances when executed. The second line executes the query and returns the List<Customer> to the local variable named customerList. The EntitySet is plural in this example, making it clear that it is querying Entity­Sets and returning instances of the Customer (notice this is singular) entity.

Is it necessary to have this naming convention? Of course not. However, I find it helps make my code more readable. Otherwise, if you take the default of what is returned from the EDM wizard, you get an EntitySets named Customers and an EntityType named Customers, which makes your LINQ to Entities query look like this:

var q = from c in context.Customers
          select c;
  List<Customers> customerList = q.ToList();

When the EDM wizard generates the model, the EntitySet and EntityType names can easily be modified. This can be done by selecting the entity in the diagram, viewing its properties in the Properties window, and modifying the desired setting (see Figure 3). For this application, I modified all of the EntityTypes to be singular by setting the Name property. I did not change the EntitySet Name property, since it was already plural.

fig03.gif

Figure 3 Changing the EntityType Names

How It Works

Now I will demonstrate the application and discuss how it operates from the top layer down, starting with the views (located in the NWUI project) and the presenters (located in the NWPresentation project). Both projects are available in the code download that accompanies this column. The application loads a customer search view, which allows the user to search for customers by matching the company name criteria (see Figure 4). The view is implemented using WPF, and when a user interacts with the view it raises events that are listened to by its presenter, who can then take appropriate action.

fig04.gif

Figure 4 Searching for Customers

When the user searches for all customers starting with the letter D, as shown in Figure 4, the view raises an event when the user clicks the Search button. The presenter listens for this event and responds by calling the service layer through WCF to get the list of customer entities that will be displayed on the CustomerSearchView. Here is the code in the view when the user clicks the Search button:

private void btnSearch_Click(object sender, RoutedEventArgs e)  {
      if (FindCustomerSearchResults != null)
          FindCustomerSearchResults();
}

This code does not interact with the list of entities that are returned, but, rather, it leaves that up to the presenter. The view uses WPF data binding to reference the entity's properties so it knows how to bind the list of entities to the list view control's elements. The only interaction the views have with the entities is through the data binding.

The CustomerSearchView raises the event FindCustomer­SearchResults, and the Customer­SearchPresenter listens for the event and responds by taking the baton and performing the search. The following code shows how the CustomerSearchPresenter class creates an instance of the NWServiceClient class, which is a proxy to the WCF service that is exposed on the lower tier:

public void view_FindCustomerSearchResults()
{
    if (this.view.CompanyNameCriteria.Length > 0)
        using (var svc = new NWServiceClient())
        {
            IList<Customer> customerList = svc.FindCustomerList(
                                                 view.CompanyNameCriteria);
            view.CustomerSearchResultsList = customerList;
        }
}

The NWServiceClient is referenced using a ServiceReference from the NWPresentation project so the presenters are aware of how to call the services and what types of data will be returned. The presentations layer does not and should not reference the EDM directly. Instead, it is told what types of entities to expect by the DataContracts exposed via WCF. This allows the Entity Framework's entities to be passed across a physical network boundary via WCF to the presenters.

Notice that once this list of Customer entities is returned, it is set to a public property on the view. This property of the view then accepts the List<Customer> and binds it to the DataContext of the view. The presenter serves up the data, passes it along, and the view handles any view-specific binding (since that code is very specific to the technology, whether it be WPF, Silverlight®, Windows Forms, or ASP.NET).

This technique allows the same presenter to be used to interact with any view that implements the ICustomerSearchView interface. In this application, the binding is handled by the WPF binding techniques using the DataContext.

The contracts expose the method that can be called in the service layer as well as the entities that will be returned. In this application, I only have a method to return the Customer and Order entity types. This means that only these entity types will be included in the contract.

WCF handles the serialization of the entities by applying the WCF DataContract attributes to them where needed. By exposing the entities through Data­Contracts the entities can be used in the UI layers without referencing the EDM directly.

Note that as of the .NET Framework 3.5 SP1 Beta 1, the Entity Framework supports automatic graph serialization. For example, if a parent entity has associated child entities, the parent and its child entities will be serialized. In the sample app, because the OrderManager's FindOrderList method uses a LINQ to Entities query that eager loads the Order Details for each Order, each Order entity returned from the middle tier will contain a List<OrderDetail> accessible through its navigation property.

While the serialized entities can be passed between the presenters and the service layer via WCF, the Object­Context is neither serialized nor passed to the presenter. This means the entities can be used in the UI layers but the Object­Context is left in the lower tiers where it will be able to access the EDM and the full resources of the Entity Framework.

Leaving the ObjectContext behind means that it can't be used to retrieve or modify entities in the UI layer directly, and it can't be used to manage change tracking in the UI layer. These roles are ideally left to the lower layers anyway. But when the entities are passed back down to the lower layers, the application has to synchronize with the ObjectContext so it can persist any changes in the entities.

When a user clicks the Search button shown in Figure 4, the presenter calls the service layer, which then calls the business layer (found in the NWBusinessManagers project) to retrieve the List<Customer>. This layer has two major roles. The first role is to get or put any data to or from the EDM. The second role is to handle any business logic that may exist.

The CustomerManager uses the ObjectContext to handle the interaction with the EDM, so it defines a local field called context and creates an instance of it in its constructor. The ObjectContext could be created and destroyed in each method. However, it is optimized to open and close database connection resources as needed. Also, by making the ObjectContext accessible throughout the class, it can maintain change tracking without having to be passed around a series of private methods within the class:

public CustomerManager()
{
    context = new NWEntities();
}

Note though that for this type of app, the Object­Context should not be held but created/destroyed as required. Due to identity resolution, holding on to the same object context will eventually lead to inconsistent, stale data and (as more and more data gets tracked) degraded performance when doing identity resolution, and it can lead to problems with updates in a multithreaded environment.

The following code shows the Customer­Manager class's Find­CustomerList method in the business layer. This method declares a LINQ to Entities query that accesses the context to request a list of Customer entities that start with the criteria. When this query is executed, it evaluates the mappings from the conceptual layer to the storage layer and generates an appropriate SELECT command:

public List<Customer> FindCustomerList(string companyName)
  {
      var q = from c in context.Customers
              where c.CompanyName.StartsWith(companyName)
              select c;
      return q.ToList();
  }

If you would like, you can view the queries as they execute using the SQL Server® Profiler.

Persisting Changes

Now that I have walked through the application using a simple retrieval, it is time to examine how modifications to data can be persisted. When a user edits a customer, the CustomerView view appears bound to the appropriate Customer entity instance (see Figure 5). The CustomerView raises an event to the presenter, who in turn requests the Customer entity instance from the lower layers.

fig05.gif

Figure 5 Editing Customers

When a user makes a modification to the customer and saves it, the entity is passed to the lower layers from the presenter using the code shown in Figure 6. This code evaluates whether the user was adding or modifying a customer and then calls the appropriate service layer method, passing the entity along for the ride.

Figure 6 SaveCustomer in the Presenter

public virtual void view_SaveCustomer()
{
    Customer customer = view.CurrentCustomer;
    var svc = new NWServiceClient();
    switch (view.Mode)
    {
        case ViewMode.EditMode:
            svc.UpdateCustomer(customer);
            break;
        case ViewMode.AddMode:
            svc.AddCustomer(customer);
            break;
        default:
            break;
    }
    view.CurrentCustomer = FindCustomer();

}

The service layer will then pass control on to the business layer, which will then save the customer entity to the database. Since the customer entity is no longer part of an ObjectContext, it must first be reunited with one by using the ObjectContext's Attach method, as shown in the code shown below. Once the entity has been attached to the context, the entity's properties must be marked as modified. This can be done by using the context's ObjectState­Manager and invoking the SetModified method for each property. Now that the context knows the entity is modified, the SaveChanges method can be issued, which will then generate a SQL UPDATE command and execute it against the database:

public void UpdateCustomer(Customer customer)
{
    context.Attach(customer);
    customer.SetAllModified(context);     // custom extension method
    context.SaveChanges();
}

Notice that the code in the UpdateCustomer method uses an extension method I named SetAllModified<T>, which makes it easier to set the state of all of the properties for an entity to be modified. SetAllModified<T> gets an instance of the ObjectStateEntry for the given entity T. It then retrieves a list of all of the property names for the entity and iteratively calls SetModifiedProperty for each property:

public static void SetAllModified<T>(this T entity, ObjectContext  context) 
where T : IEntityWithKey
{
    var stateEntry = context.ObjectStateManager.      GetObjectStateEntry(entity.EntityKey);
    var propertyNameList = stateEntry.CurrentValues.DataRecordInfo.      FieldMetadata.Select
      (pn => pn.FieldType.Name);
    foreach (var propName in propertyNameList)
        stateEntry.SetModifiedProperty(propName);
}

Another way to ultimately save the entity is to call the context's Refresh method. This tells the context to go get the data for the entity instance and refresh its property values from the database's values. The RefreshMode enumerator of ClientWins will replace the original values with the latest values from the database, thus allowing a last-in-wins strategy.

The RefreshMode of StoreWins will overwrite both original and current values in the entity cache with the values from the database. ClientWins is the appropriate strategy for last-in-wins, while StoreWins is an appropriate strategy when you want to cancel the changes and refresh the UI's view with the latest database values:

context.Refresh(RefreshMode.ClientWins, customer);  // Last in wins

The Entity Framework enforces optimistic concurrency when generating update and delete commands. It does this by including original values in the WHERE clause for any properties with a ConcurrencyMode attribute value set to Fixed.

By default, models are generated with no fields specified as concurrency fields. This means that when a user saves his changes, he possibly could be overwriting another user's changes inadvertently. If another user changes a value while the CustomerView is open and you want to use optimistic concurrency, you can do so by setting the ConcurrencyMode attribute of the EntityType in the conceptual model.

Editing the EDM file and setting the ConcurrencyMode to Fixed tells the Entity Framework to add this column to the WHERE clause of any Update or Delete commands. Thus, an OptimisticConcurrencyException is raised if a matching row is not found. Figure 7 shows this exception being raised when I modified the region of a customer in the database, just before the user tried to modify the same region.

fig07.gif

Figure 7 OptimisticConcurrencyException

You can trap this exception and take whatever action is appropriate. For example, you could trap the exception, log it, and then overwrite the user's changes anyway, as shown here:

catch (OptimisticConcurrencyException e){
    context.Refresh(RefreshMode.ClientWins, customer); // Last in wins
    logger.Write(e);
    context.SaveChanges();
}

Deleting and Adding

When a user deletes a customer, the CustomerManager's Delete­Customer method gets the customer entity and applies the delete:

context.Attach(customer);
context.DeleteObject(customer);
context.SaveChanges();

First, the Customer entity instance must be reunited with an Object­Context using the Attach method. Then the customer must be deleted from the ObjectContext. Done this way, the change tracking mechanism within the ObjectContext is aware the Customer entity instance has been deleted. Finally, when the SaveChanges method is called, the ObjectContext is now aware the entity was deleted and it should generate a DELETE SQL command and execute it.

When adding a customer, the CustomerManager's Add­Customer method gets the customer entity and applies the insert, like so:

context.AddToCustomers(customer);
context.SaveChanges();

This entity instance is new, so it must be added to the context and flagged as a new instance of a Customer entity by associating the Customer entity instance with an ObjectContext using the AddToCustomer method. Finally, when the SaveChanges method is called, the ObjectContext is aware that the entity was added and that it should generate an INSERT SQL command and execute it.

Wrapping It Up

Here I've demonstrated how the Entity Framework can integrate into an architecture, use modern patterns such as the MVP pattern, and handle the common architectural concerns. Key components of the Entity Framework in layered architectures are its change tracking mechanisms, integration with LINQ to Entities, ability to disconnect and reconnect with its ObjectContext, and how it provides a means for a developer to handle concurrency issues.

Send your questions and comments for John to mmdata@microsoft.com.

John Papa (johnpapa.net) is a Senior Consultant with ASPSOFT (aspsoft.com) and a baseball fan who spends summer nights rooting for the Yankees with his family. John, a C# MVP and INETA speaker, has authored several books and is now working on his latest, titled Data Access with Silverlight 2. He often speaks at conferences such as DevConnections and VSLive.