Export (0) Print
Expand All
7 out of 8 rated this helpful - Rate this topic

Using the Entity Framework in n-Tier Client-Side Applications

This chapter is excerpted from Programming Entity Framework: Building Data Centric Apps with the ADO.NET Entity Framework by Julia Lerman, published by O'Reilly Media

Many applications implement their user interface, business logic, and data access layer completely on the client side. This is typical in scenarios where the application is completely self-contained on the end-user's machine or users are on a corporate intranet.

Even with these smaller applications, however, you should do your best to separate the UI from business logic and data access logic.

This is not a big challenge for the Entity Framework, because it is reasonable to work with a long-running ObjectContext and take advantage of the Entity Framework's change tracking from the time the data is queried to the time that changes are persisted to the database.

The Entity Framework handles all of the interaction with the database and can be thought of as a data access layer. Through the use of partial classes or your own custom classes, a business layer can provide some of the logic that pertains directly to data, such as the validations I demonstrated earlier in the book. But the tasks of interacting with the data store and validating data are not responsibilities of the user interface. So in this chapter, we will focus on separating the logic of interacting with the user from that of interacting with objects and data.

This chapter will work through many of the important tasks of building a layered, client-side application. Although it will use a specific application as its focus, you will not be building the application step by step, but rather investigating critical elements. The complete source code for this application is available on the book's website.

Thinking in Layers

Figure 20.1, "A basic architecture for client applications using the Entity Framework" portrays a basic architecture for client applications using the Entity Framework. However, the separation isn't really quite as tidy as the figure depicts. For instance, the entity classes contain business logic, and some might consider the act of asking the ObjectContext to get data to be the work of a data layer. But for pragmatic reasons, the examples in this chapter are based on the simple three-layer architecture shown in Figure 20.1, "A basic architecture for client applications using the Entity Framework".

Figure 20.1. A basic architecture for client applications using the Entity Framework

A basic architecture for client applications using the Entity Framework

In Figure 20.1, "A basic architecture for client applications using the Entity Framework", the user interface (a Windows form) will not make calls to the data access layer directly. Instead, the Windows form will stay focused on its task of interacting with the user. In between the Windows form and the model's assembly is the business layer. The Windows form communicates with the business layer and the business layer communicates with the data layer. The Windows form might leverage other classes in the business layer that provide logic for business rules or other functionality that your business domain requires.

The ObjectContext acts as a bridge between the Entity Framework and the entity classes that you'll use in your applications. In the Windows Forms applications you built in Chapter 8, Data Binding with Windows Forms and WPF Applications, the UI itself instantiated the ObjectContext and queried and saved changes to the objects directly. These are not tasks for the UI. But where do they belong? Some will argue that these are data access layer tasks. Others will argue that because these actions do not interact directly with the database they are not data access tasks but business tasks.

What I propose is a layer or class that bridges the UI, the business logic, and the data access logic, as shown in Figure 20.2, "A DataBridge that bridges the UI, the business logic, and the data access logic". I've chosen to call this a DataBridge.

Figure 20.2. A DataBridge that bridges the UI, the business logic, and the data access logic

A DataBridge that bridges the UI, the business logic, and the data access logic

The DataBridge owns the ObjectContext. It will instantiate it, make Entity Data Model (EDM) queries with it, call SaveChanges with it, and perform any interactions with the objects it is managing. The DataBridge will rely on a generic class that will execute any queries and commands using exception handling. For example, this generic class called CommandExecutor will contain the GetReferenceList, ExecuteList, and ExecuteFirstorDefault methods described in Chapter 18, Handling Entity Framework Exceptions, along with some other methods. These methods are put into their own class, so they can be reused throughout the application.

Organizing Your Layers

Although it's not a requirement, using separate projects and assemblies for the different types of logic is the preferred way to keep things organized. A logical layer won't necessarily align with a physical layer. You will find that some elements that fit in a particular logical layer will need to physically exist in a project with elements from another layer.

The project that contains the EDM, its entity objects, and the extended logic for those objects is a good example of this. Here you will find some elements that would fit into the scope of business logic, and others that might be defined as data access logic, yet they all need to be in the same assembly.

Is It Business Logic or Data Access Logic?

Your own definition of what belongs in a data access layer and what belongs in a business layer may be very different from that of another developer. To some, only code that interacts directly with the data store belongs in a data access layer. To others, code that is responsible for querying and returning data belongs in the data access layer. When you write queries against the EDM, you are not interacting directly with the database; the Entity Framework does that for you. Expect to find different opinions on exactly where the query logic belongs. In the meantime, for the sake of this chapter, we won't worry too much about the semantics, and instead we'll get on with the business of ensuring that whatever you want to call this logic, it is not implemented in the user interface.

In this chapter, you will create a single class that acts as a bridge between the UI and the Entity Framework. The class will be in its own project. It will be called by the user interface and in turn it will work with the EDM and classes in the BreakAway assembly as well as leverage the Entity Framework directly. We'll place the CommandExecutor class into this project for the sake of simplifying the sample.

This DataBridge will focus on the needs of a Windows form that uses the entities in data-bound controls. Throughout the chapter, I will highlight some of the things you'll need to consider that are specific to using entity objects in a Windows form and explain why to choose certain options over others.

The Entity Framework on the Client

Windows Forms and WPF applications are perfect candidates for using the Entity Framework directly, since they have full access to the .NET Framework. Although Silverlight applications are also client-side applications, the Silverlight runtime does not include the Entity Framework or ADO.NET, and is therefore dependent on web services to provide its data.

Before getting into the details of what might be in the DataBridge class, it is helpful to have some knowledge of the needs of its consumer, which is to be a Windows Forms data entry form that allows a user to manage customers and their reservations. The form uses only the business layer as a conduit for retrieving and returning all of the data that is needed in the user interface.

Master/detail data entry is pretty standard for business applications. The DataBridge class will need to perform some typical tasks: providing selection lists, retrieving a specific entity graph from the data store to pass to the calling class (in this case, the UI), persisting changes back to the database, and more.

By looking at the form, which appears in Figure 20.3, "A typical master/detail data entry form", you'll get an idea of what you will need in the new class.

Figure 20.3. A typical master/detail data entry form

A typical master/detail data entry form

The form provides a filtered list of customers or potential customers from which the user can select. Then the user can edit any of the customer information as well as view or edit the customers' reservations. This form works with a lot of relationships, which the Entity Framework handles with ease. If there are any unsaved changes, the form will prompt the user to save or roll back edits when he attempts to move to another customer. If you are used to working with DataSets, you'll see that the rollback feature definitely requires more effort with the Entity Framework.

Because we are using the Entity Framework, we will need to address some specific challenges that exist because the consumer of the DataBridge class happens to be a Windows form.

When writing applications it is helpful to constantly ask yourself, "Whose job is it to perform this task?" Unless you are writing Rapid Application Development (RAD) applications, as we did in Chapter 8, Data Binding with Windows Forms and WPF Applications, interacting with the database is not the job of the user interface. The UI's job is to interact with the user. When you need to add some new logic, such as performing a calculation based on an object's values or determining whether an object's properties are valid for updates, ask yourself whether that is within the scope of interacting with the user. If not, which layer should be performing that task?

This also means that the UI developer might have a need that is not met by one of the other layers it is using. Writing a layer that will meet the needs of any UI developer who uses it will require plenty of tweaks to accommodate not only the UI but also the entities. Some of the special requirements aren't even specific to the Entity Framework. For example, a Windows form GridViewComboBox does not have a SelectedItem property that will cause classes to display their class names (e.g., BAGA.Trip). The known workaround for this is to provide a Self property in the object that is being bound to the ComboBox. This is unlikely to preexist in the business layer, but if the UI developer encounters this problem, the Self property is going to need to be added somewhere.

Although it's difficult to predict all of these types of issues, it's important to pay attention to any instances where you might find yourself putting business logic into the UI layer to solve these problems.

Some business logic is specific to the model and some is specific to your business domain. Whether you can prepare for tweaks in advance or you encounter them when you are working on the UI layer, you'll also need to consider whether the logic belongs in the partial classes of the entities or in the business layer that sits between your UI and your entities and model.

The Self property mentioned earlier solves a specific UI problem, but the logical place for it is directly in the Trip class. This means adding the property into the Trip entity's partial class. As you work through building the DataBridge and then trying to implement it in a UI, you may not want to just throw every new piece of logic into the DataBridge. Like the Self property, it may make more sense to add it into the entities' partial classes or even the partial class of the ObjectContext.

Example 20.1, "Adding a Self property to the partial class" shows the new properties for the Trip class. You might want to add a similar property to any entity class that might be used in data binding.

Example 20.1. Adding a Self property to the partial class

Public ReadOnly Property Self() As Trip
  Get
    Return Me
  End Get
End Property
public Trip Self
{
  get { return this; }
}

You might find that the UI requires other types of information. Another property that the entity classes would benefit from is a BalanceDue property for Reservation, as shown in Example 20.2, "The BalanceDue property". Again, this property is specific to an entity class, Reservation, and therefore the entity's partial class is a logical place to implement it, although you may choose to organize your business layer classes differently.

Note
Remember that these custom properties are not available for queries, since they aren't in the model itself.

Example 20.2. The BalanceDue property

Public ReadOnly Property BalanceDue() As String
  Get
    If Me.UnpaidReservations.Count = 1 Then
      Dim res = Me.UnpaidReservations.ToList()(0)
      Return (res.Cost - res.PaymentTotal).ToString()
    Else
      Return "n/a"
    End If
  End Get
End Property
public string BalanceDue {
get
{
   if (this.UnpaidReservations.Count == 1)
   {
     var res = this.UnpaidReservations.ToList()[0];
     return (res.Cost - res.PaymentTotal).ToString();
   }
   else
     return "n/a";   }
}

Now the UI developer has easy access to Reservation.BalanceDue without having to create this logic in the UI's code.

Creating an IsDirty Property for the ObjectContext

Many applications rely on some type of IsDirty flag to validate a user entry before a user can move to a new record or exit the form completely. An IsDirty flag supports the common pattern that asks a user whether he wants to save his changes before he leaves a record.

Because changes made in the UI will flow to the ObjectContext, you can easily query the context's ObjectStateEntries to see whether the EntityState is something other than Unchanged. This logic fits nicely into the BAEntities partial class, as it might be useful to access from other applications. If you want to keep this logic out of the model's classes, you can put it into the new class you will be building.

The new IsDirty property of BAEntities checks whether an ObjectStateEntry is Added, Deleted, or Modified. This will take into account changed relationships as well. If entries are found, IsDirty returns True. The code in Example 20.3, "Adding an IsDirty property to the BAEntities' partial class" adds an IsDirty property to the BAEntities' partial class.

Example 20.3. Adding an IsDirty property to the BAEntities' partial class

Public ReadOnly Property IsDirty() As Boolean
  Get
    If Me.ObjectStateManager.GetObjectStateEntries _
      (EntityState.Added Or EntityState.Deleted Or _
       EntityState.Modified).Count > 0 Then
      Return True
    Else
      Return False
    End If
  End Get
End Property
public bool IsDirty
{
  get
  {
    if (this.ObjectStateManager.GetObjectStateEntries
      (EntityState.Added | EntityState.Deleted | EntityState.Modified)
      .Count() > 0)
       return true;
    else
      return false;
  }
}

You can take this a step further and make IsDirty an extension method of System.Data.Objects.ObjectContext so that it's not even necessary to add it to the partial class for every model's context.

Because the DataBridge will rely on the CommandExecutor class, you will want to build that first.

The CommandExecutor class will contain the four Execute methods created in Chapter 18, Handling Entity Framework Exceptions.

Having the methods separated out in this class allows you to not only provide any common exception handling when queries or other commands are being executed, but you can also encapsulate all of the actual execution in this class. When building the DataBridge class, you'll see that there is one unavoidable exception to this rule-calling functions that are methods of the context.

Example 20.4, "The CommandExecutor class" shows the methods of the CommandExecutor class. The exception handlers are abbreviated because of the length of the code listing. In addition to the four methods already discussed, you will also see the GetReferenceList method from Chapter 17, Controlling Objects with ObjectStateManager and MetadataWorkspace and one additional method that allows you to take advantage of the GetObjectByKey method. There's a twist to the GetReferenceList. Unfortunately, Entity Framework entities do not support sorting with data-bound controls. Therefore, it's necessary to sort in the query. The method now takes an additional parameter to define what property to sort by. If you want to be able to pass in multiple properties, you'll need to revise the method so that it takes an array, then you can concatenate each string with the reference variable, refData, when building the Entity SQL expression.

Note
I toyed with the idea of creating an interface called IReferenceList to apply to entities that might be used as reference lists, then using properties named DisplayMember, ValueMember, and SortMember. When working with one of these lists, it meant that I wouldn't have to pass around the various property names for sorting or the display fields. Implementing this throughout the layers became cumbersome and overkill for this small solution, so I chose not to follow that path. When building your apps, you should consider interfaces as a means for providing common functionality across entities.

You'll see in the DataBridge class how each of these methods is used.

Example 20.4. The CommandExecutor class

Namespace DAL
Public Class CommandExecutor
  Public Function ExecuteFirstorDefault(Of TEntity) _
   (ByVal objectQuery As ObjectQuery(Of TEntity)) As TEntity
    Try
      Return objectQuery.FirstOrDefault()
    Catch ex As EntitySqlException
      Throw ex 'TODO: Replace with handling code
    'additional exceptions as described in Chapter 18
    End Try
  End Function

  Public Function ExecuteFirstorDefault(Of TEntity) _
   (ByVal L2EQuery As IQueryable(Of TEntity)) As TEntity
    Try
      Return L2EQuery.FirstOrDefault()
    Catch ex As InvalidOperationException
      Throw ex 'TODO: Replace with handling code
    'additional exceptions as described in Chapter 18
      End Try
    End Function

  Public Function ExecuteList(Of TEntity) _
  (ByVal objectQuery As ObjectQuery(Of TEntity)) As List(Of TEntity)
    Try
      Return objectQuery.ToList()
    Catch ex As EntitySqlException
      Throw ex 'TODO: Replace with handling code
    'additional exceptions as described in Chapter 18
    End Try
  End Function

  Public Function ExecuteList(Of TEntity) _
  (ByVal L2EQuery As IQueryable(Of TEntity)) As List(Of TEntity)
    Try
      Return L2EQuery.ToList()
    Catch ex As InvalidOperationException
      Throw 'TODO: Replace with handling code
    'additional exceptions as described in Chapter 18
    End Try
  End Function

  Public Function GetReferenceList(Of TEntity)(ByVal context As ObjectContext) _
   ByVal orderBy as String As List(Of TEntity)
    Dim entityName = GetType(TEntity).Name
    Dim eSetName = context.MetadataWorkspace.GetEntitySetFullName(entityName)
    Dim esql = "SELECT VALUE refData FROM " & eSetName & " AS refData " & _
               "ORDER BY refData." & orderBy;
    Dim objQuery = context.CreateQuery(Of TEntity)(esql)
    Return objQuery.ToList
  End Function

  Public Function ExecuteGetObjectByKey(Of TEntity) _
   (ByVal context As ObjectContext, ByVal key As EntityKey) As TEntity
    Dim entity As New Object
    Try
      If context.TryGetObjectByKey(key, entity) Then
        Return CType(entity, TEntity)
      Else
        Return Nothing
      End If
    Catch ex As ArgumentException
      If ex.Message.Contains("specified named connection is either not found") Then
        Throw 'TODO: Replace with handling code
      Else
        Throw 'handle or Throw the exception
      End If
    'additional exceptions as described in Chapter 18
    End Try
  End Function
End Class
End Namespace  

namespace BAGA.DAL
public class CommandExecutor
{
  public TEntity ExecuteFirstorDefault<TEntity>(ObjectQuery<TEntity> objectQuery)
  {
    try
    {
      return objectQuery.FirstOrDefault();
    }
    catch (EntitySqlException ex)
    {
      throw ex; //TODO: Replace with handling code
      //additional exceptions as described in Chapter 18
    }
  }

  public TEntity ExecuteFirstorDefault<TEntity>(IQueryable<TEntity> L2EQuery)
  {
    try
    {
      return L2EQuery.FirstOrDefault();
    }
    catch (InvalidOperationException ex)
    {
      throw ex; //TODO: Replace with handling code
      //additional exceptions as described in Chapter 18
    }
  }

  public List<TEntity> ExecuteList<TEntity>(ObjectQuery<TEntity> objectQuery)
  {
    try
    {
      return objectQuery.ToList();
    }
    catch (EntitySqlException ex)
    {
      throw ex; //TODO: Replace with handling code
      //additional exceptions as described in Chapter 18
    }
  }

  public List<TEntity> ExecuteList<TEntity>(IQueryable<TEntity> L2EQuery)
  {
    try
    {
      return L2EQuery.ToList();
    }
    catch (InvalidOperationException ex)
    {
      throw; //TODO: Replace with handling code
      //additional exceptions as described in Chapter 18
    }
  }

  public List<TEntity> GetReferenceList<TEntity>(ObjectContext context, string orderBy)
  {
    var entityName = typeof(TEntity).Name;
    var eSetName = context.MetadataWorkspace.GetEntitySetFullName(entityName);
    var esql = "SELECT VALUE refData FROM " + eSetName + " AS refData " +
               "ORDER BY refData." + orderby;
    var objQuery = context.CreateQuery<TEntity>(esql);
    return objQuery.ToList();
  }

  public TEntity ExecuteGetObjectByKey<TEntity>(ObjectContext context, EntityKey key)
  {
    object entity = new object();
    try
    {
      if (context.TryGetObjectByKey(key, out entity))
        return (TEntity)entity;
      else
        return default(TEntity);
    }
    catch (ArgumentException ex)
    {
      if (ex.Message.Contains("specified named connection is either not found"))
        throw; //TODO: Replace with handling code
      else
        throw; //handle or Throw the exception
      //additional exceptions as described in Chapter 18
    }
  }
}
}

The last two methods require that you pass in the context being used by the calling class. This is not necessary in the first four methods because the context is already bound to the query that comes in as a parameter. What's nice about these methods using the query from the calling class is that they don't care if it is a short-lived context, as you would have in web services or web applications, or if it is a long-running context, such as the one you will use for this smart client application. That makes the CommandExecutor class truly reusable.

With the CommandExecutor class in place, let's take a look at the DataBridge class.

The new DataBridge class is where the ObjectContext will live. It will be responsible for creating queries and getting them executed by the CommandExecutor to return data to the UI, as well as sending UI edits back to the database. It will also perform other ObjectContext-related tasks along the way.

The most critical element of the DataBridge is the ObjectContext, which will need to stay in memory to keep track of changes to the entity objects until it is time to call SaveChanges.

Using a Long-Running ObjectContext

The simplest way to achieve the architecture shown in Figure 20.1, "A basic architecture for client applications using the Entity Framework" is with a long-running ObjectContext. When the user interface instantiates your bridge class, the class can instantiate a new context and continue to use that context until the UI disposes the bridge class.

As long as the instantiated bridge class is in scope-staying in memory, and therefore keeping the ObjectContext alive-the user interface can retrieve, modify, and save data without having to worry about the complexities of maintaining state.

Much of the bridge's work will focus on interacting with the EDM and the ObjectContext. All of the benefits of querying against a model are available to you, including the simplicity of working with related data. Developers are used to providing other common functions to the client as well. They often use properties such as IsDirty to identify whether any changes have been made to an object. The ability to roll back changes if the user decides to cancel his updates is an important feature. You can use all of the tools you have been learning about in this book to provide these capabilities in your business layer.

Implementing the Primary Elements of the DataBridge Class

The DataBridge class will contain an ObjectContext that will be responsible not only for retrieving data through the EDM, but also for providing change-tracking support and calling SavingChanges back to the database. To do this the ObjectContext will need to remain in scope for the life of the class.

You can instantiate the context in the class constructor.

Additionally, this class will find many opportunities to take advantage of the model's EntitySet name and EntityContainer name. So, in this constructor, you can create and populate variables to retain the EntityContainer and EntitySet names. Although you'll instantiate the context and these variables in the class constructor, you'll need to declare them as class-level variables.

Note
I'll describe the most important pieces of this solution in this chapter, highlighting issues that are specific to working with the Entity Framework. The entire solution is available as a download on the book's website.

Example 20.5, "The shell for the new class" shows the first step in creating this new class, which we'll call DataBridge.

Example 20.5. The shell for the new class

Public Class DataBridge
  Private _commonContext as BAEntities
  Private _dal As BAGA.DAL.CommandExecutor
  
  Public Sub New()
    _commonContext = New BAEntities ()
    _dal = New BAGA.DAL.CommandExecutor
  End Sub

  Public ReadOnly Property IsDirty() As Boolean
    Get
      Return _commonContext.IsDirty
    End Get
  End Property
End Class
public class DataBridge //: IDisposable
{
  private BAEntities _commonContext;
  private BAGA.DAL.CommandExecutor _dal;
  
  public DataBridge()
  {
     _commonContext = new BAEntities();
     _dal = new BAGA.DAL.CommandExecutor();
  }
  public bool IsDirty
  {
   get
  {
   return _commonContext.IsDirty;
  }
 }
}

The context is named _commonContext and will exist for the lifetime of the business object. Once you've created this context, you can use it throughout the class.

Notice that the DataBridge class also has a property for encapsulating the new BAEntities.IsDirty property so that the client can have easy access to the property.

Creating a Class for Lightweight Objects to Be Used in UI Pick Lists

The form will depend on a variety of lists from which to choose. The most important list is the customer list from which the user will select a customer to view or edit. The Customer class has a lot of properties in it. It would be a wasteful use of resources to return all of the information for every customer to populate that list. Instead, populate the lists using projection queries that return only the ID and Name rather than all of the properties of a Customer.

The ID and Name fields are instead encapsulated in a small custom class called KeyListItem, which you can use generically for lists that you will use in data-bound controls (see Example 20.6, "The KeyListItem class").

The class is necessary for two reasons. The first is so that you can reference it in the signature of the method that returns the lists: Public Function CustomerNamesList() As List(Of KeyListItem). Otherwise, you would be returning an anonymous type, and it is not possible to identify an anonymous type as a type in the signature.

The second reason is that although you can reference a struct in the signature, a struct won't suffice because it is not possible to do a projection query into a type that doesn't have named properties. Structs have only fields, no properties.

Example 20.6. The KeyListItem class

Public Class KeyListItem
    Private _ID As Integer
    Private _Name As String
    Public Property ID() As Integer
      Get
        Return _ID
      End Get
      Set(ByVal value As Integer)
        _ID = value
      End Set
    End Property
    Public Property Name() As String
      Get
        Return _Name
      End Get
      Set(ByVal value As String)
        _Name = value
      End Set
    End Property
  End Class
public class KeyListItem
{
  public int ID  { get; set; }
  public string Name{ get; set; }
}

Creating the Main Entity Pick List: For Customer Names

Now that you have the KeyListItem class, you can build the most important pick list, the one that users will use to select which customer to work with.

The class will offer some flexibility with respect to filtering the list: Customers Only, Customers on a Trip, and All Contacts (customers and noncustomers). Example 20.7, "Extracting the customer IDs and names using a LINQ to Entities query" shows a method that uses a LINQ to Entities query to extract the customer IDs and names for all Customer entities. The method returns a List of KeyListItems. To do this the query uses an object initializer to create a new KeyListItem on the fly from the ID and Name that are selected in the query. Notice that after the query is defined, it is passed to _dal, the CommandExecutor, to execute the query.

Example 20.7. Extracting the customer IDs and names using a LINQ to Entities query

Public Function CustomerNamesList() As List(Of KeyListItem)
  Dim custs = From c In _commonContext.Contacts.OfType(Of Customer)() _
              Order By c.LastName, c.FirstName _
              Select New KeyListItem With {.ID = c.ContactID, _
                .Name = c.LastName.Trim & "," & c.FirstName}
  CType(custs, ObjectQuery).MergeOption = MergeOption.OverwriteChanges
  Return _dal.ExecuteList(Of KeyListItem)(custs);
End Function
public List<KeyListItem> CustomerNamesList()
{
  var custs =  from cust in _commonContext.Contacts.OfType<Customer>()
               orderby cust.LastName, cust.FirstName
               select new KeyListItem {ID = cust.ContactID,
               Name = cust.LastName.Trim() + "," + cust.FirstName };
    ((ObjectQuery)custs).MergeOption = MergeOption.OverwriteChanges;
    return _dal.ExectueList<KeyListItem>(custs);
}
Note
The Visual Basic compiler would allow you to call ExecuteList without identifying the type because it can infer the type from the custs variable. Therefore, _dal.ExecuteList(custs) would also be a valid way to call the method in VB.

Using MergeOptions to Cache or Refresh a Pick List

It is possible for the customer list to be queried more than once during the lifetime of the business layer class. You will need to decide whether the user requires fresh data each time the list is queried. For this example, we'll rely on the rule that the user should always have an updated list of customers and contacts. Therefore, the database will be queried anytime the user requests the customer list. Remember that by default, if the database returns data that already exists in the ObjectContext, those entities in the context will not be refreshed. Only new data will be added to the context. However, because we are using the rule that requires fresh data, the method sets the query's MergeOption to MergeOption.OverwriteChanges, overwriting any modified entities with the data from the store. Because you can use a MergeOption only with an ObjectQuery, you'll have to cast the LINQ to Entities query to an ObjectQuery to set the MergeOption.

An additional method, AllContactsList, returns a list of combined customers and noncustomers (both deriving from Contact) so that when someone from BreakAway's mailing list signs up for her first trip, the user pulls the list that includes noncustomers and converts that person into a customer.

Building a Frequently Used Entity Graph for the UI

The DataBridge's GetCustomerwithRelatedData method will return a Customer graph based on an ContactID that is passed in as a parameter. This method creates a query using Include to eager-load all of the related customer data that the form needs. If the selected item turns out to be a noncustomer, the code calls the ConvertContacttoCustomer function that you built in Chapter 13, Working with Stored Procedures When Function Mapping Won't Do to turn that contact into a customer in the database. In that case, you'll need to reexecute the query that loads the customer graph. You do this by calling the entire method recursively.

Precompiling a frequently used query

The query for eager-loading the customer graph uses a number of Includes. It will require a bit of effort for the Entity Framework to compile the query:

From c In _commonContext.Contacts.OfType(Of Customer)
                        .Include("Addresses")
                        .Include("Reservations.Trip.Destination")
                        .Include("Reservations.UnpaidReservation")
Where c.ContactID = custID

More importantly, the query will be used repeatedly as the user moves from one customer to another in the UI. That makes the query a perfect candidate for precompilation, which you learned about in Chapter 16, Making It Real: Connections, Transactions, Performance, and More.

Example 20.8, "Creating a precompiled LINQ query" shows a method that precompiles this query.

Example 20.8. Creating a precompiled LINQ query

Private Sub PreCompileQueries()
  _compiledCustGraphQuery = CompiledQuery.Compile _
   (Function(ctx As BAEntities, custID As Integer) _
    From c In _commonContext.Contacts.OfType(Of Customer) _
                           .Include("Addresses") _
                           .Include("Reservations.Trip.Destination") _
                           .Include("Reservations.UnpaidReservation") _
       Where c.ContactID = custID)
End Sub
private void PreCompileQueries()
{
  _compiledCustGraphQuery = CompiledQuery.Compile
   ((BAEntities ctx, int custID) =>
      from c in _commonContext.Contacts.OfType<Customer>()
                              .Include("Addresses")
                              .Include("Reservations.Trip.Location")
                              .Include("Reservations.UnpaidReservation")
       where c.ContactID == custID
       select c);
}

To share the compiled query with other methods, you need to declare it as a class-level variable. But when you do that, you need to specify the type. In the PreCompiledQueries method, you were able to take advantage of implicitly typed variables and did not need to specify the type. But when declaring the variable, you'll have to spell out the System.Func type in the declaration (see Example 20.9, "Declaring the variable that will be used for a compiled query").

Example 20.9. Declaring the variable that will be used for a compiled query

Private _compiledCustGraphQuery As _
 System.Func(Of BreakAwayEntities, Integer, IQueryable(Of Customer))
private Func<BAEntities, int, IQueryable<Customer>>
 _compiledCustGraphQuery;

Next, you'll need to run the PreCompiledQueries method. Because the query will always be required by the DataBridge class, you can call the method in the class constructor where the context is instantiated. The CompiledQuery does not need the ObjectContext to perform the compilation. The context won't be used until it is time to invoke the query, which happens when you build the Customer graph.

Example 20.10, "Getting a customer from the database, and converting a noncustomer to a customer on the fly if necessary" shows the GetCustomerwithRelatedData method, which builds the graph that will be sent back to the UI after the user has selected which customer or contact to work with. If they have chosen a Customer, we simply query for that Customer. However, if they have used the AllContactsList and selected a Contact, that Contact will be converted to a Customer on the fly. Since only the ID is being passed in, you can't determine the type until after performing the query, which is why the method has additional code based on whether the query returned a customer or returned nothing. This is explained in more detail after the code listing.

Example 20.10. Getting a customer from the database, and converting a noncustomer to a customer on the fly if necessary

Public Function GetCustomerwithRelatedData(ByVal id As Int32) As Customer

  'contact and eKey will be used by TryGetObjectByKey
  Dim contact As Object
  Dim eKey = New EntityKey(_containerName & ".Contacts", _
                          "ContactID", ContactID)

  'retrieve the customer graph using the compiled query
    Dim custQuery = _
     _compiledCustGraphQuery.Invoke(_commonContext, ContactID)

    CType(custQuery, ObjectQuery).MergeOption =  _
     MergeOption.OverwriteChanges

    Dim cust = _dal.ExecuteFirstorDefault(Of Customer)(custQuery)
  If Not cust Is Nothing Then
    'customer was found, return it
    Return cust
  ElseIf _commonContext.TryGetObjectByKey(ekey, contact) Then
   'if contact exists, then convert to customer
      ConvertContacttoCustomer(CType(contact, Contact))
    Return GetCustomerwithRelatedData(id)
  Else
    Return Nothing
  End If
End Function
public Customer GetCustomerwithRelatedData(Int32 ContactID)
{
  object con = null;
  var ekey = new EntityKey(_containerName + ".Contacts", _
                           "ContactID", ContactID);
  var custQuery =
     _compiledCustGraphQuery.Invoke(_commonContext, ContactID);

  ((ObjectQuery)custQuery).MergeOption =  MergeOption.OverwriteChanges;
  var cust = _dal.ExecuteFirstorDefault<Customer>(custQuery);
  if (cust != null)
  {
    return cust;
  }
  else if (_commonContext.TryGetObjectByKey(ekey, out con))
  {
   // if contact exists, then convert to customer
    var contactToConvert = (Contact)con;
    ConvertContacttoCustomer(ref contactToConvert);
    return GetCustomerwithRelatedData(ContactID);
  }
  else
    return null;
}
Note
Because TryGetObjectByKey specifically requires an object type as its parameter, after TryGetObjectByKey returns the con variable, you'll need to cast con to a Contact type before passing it to the conversion function. This is what's happening in the Else clause.

Some decisions that drove the design of the GetCustomerwithRelatedData method

I made a number of choices for this class method when weighing the cost of trips to the database against the potential complexity of writing queries. Although it's not uncommon to have to make these types of decisions, the Entity Framework adds new factors that you need to consider:

Query the database, or use GetObjectByKey to retrieve the customer graph?

There are a few reasons not to use GetObjectByKey here. The first is that if the entity is already in the context, the call won't return the most current data from the database. Additionally, even if the object is already in the context, it might not have the related data. By going right to the database, you are assured that you'll get the freshest and most complete set of data. If the customer is not found in the database, TryGetObjectByKey is used to get the contact record. If the contact is already in the cache for some reason, an extra trip won't be made to the database. It's really just a safety measure to be sure that no customer was returned for the ID because the ID belongs to a contact (which should be safe to assume, but who likes to make assumptions?).

Warning
In a highly concurrent system, keep in mind that if you have an entity in your cache and it is deleted from the data store, a typical query of the data store will not cause the entity to be removed from the cache. This will happen whether you use GetObjectByKey or a query. You will need to decide how long you want to keep objects in the ObjectContext when you have these types of concerns.
Query for a contact, or for a derived customer type?

Rather than having two separate methods for the UI developer to choose from, the business layer has a single method that can handle both customers and noncustomers.

The ID passed in will be a ContactID and could be for either derived type. If you query for a generic contact, a second query will always be necessary to fetch the related data for those contacts who are customers. However, if the initial query is specifically for a Customer type, you can eager-load the related data at the same time.

The downside of querying for the Customer type is that when the ContactID is for a noncustomer, nothing will be returned. This leaves you with yet another choice to make. Do you query again (database trip #2) for the noncustomer to be 100% sure it's still in the database before executing the ConvertContacttoCustomer function (database trip #3), or do you save the database trip and just call the function and deal with any exception that might occur?

Furthermore, how likely is it that the ContactID is of a customer versus a non-customer? In this case, the decision was to query for a Customer type and pay the cost of the extra trips to the database for the less common cases where the ContactID is for a noncustomer.

In the long run, is it worth all of this trouble so that the UI developer can have a single method to get a customer, even if he is passing in a noncustomer? You should decide that for your application architecture, but for the BreakAway application, the answer was yes.

Converting a noncustomer to a customer

When converting a noncustomer to a customer, you will encounter a problem because the noncustomer contact is already in the ObjectContext. Because you cannot convert entities to other entity types, the ConvertContacttoCustomer method called in Example 20.10, "Getting a customer from the database, and converting a noncustomer to a customer on the fly if necessary" will completely remove the noncustomer from the context, and then requery the database for the newly converted customer.

Supplying Additional Lists for UI Drop-Downs

In addition to the customer list, the form also requires a number of common lists. In an application using DataSets, you might have a single query that returns all of these simple pick lists (Activities, Destinations, Customer Types, and Trips). However, Entity Framework queries cannot return multiple resultsets; therefore, four separate queries are required.

The GetReferenceList method in the CommandExecutor class will do most of the work here. However, the DataBridge needs to expose a method for the UI developer to access. We'll call that method GetReferenceList also. The method that you already built returns straight-up entities and that will work for most of the lists that this application requires, e.g., Activity, Location, and CustomerType. However, the Trip entity doesn't provide enough information by itself and its list cannot be satisfied by the GetReferenceList method. If we provided multiple methods in the DataBridge for getting reference lists, this could be confusing for the UI developer. Therefore, the DataBridge.GetReferenceList will not only encapsulate the GetReferenceList method from the other class, but also provide additional logic for returning the appropriate Trip list, which will be sorted using navigation properties.

Example 20.11, "The DataBridge's GetReferenceList method calling methods from the CommandExecutor class" shows the GetReferenceList method, which calls into the CommandExecutor to retrieve the data for the UI.

Example 20.11. The DataBridge's GetReferenceList method calling methods from the CommandExecutor class

Public Function GetReferenceList(Of TEntity)(ByVal orderBy As String) _
 As List(Of TEntity)
    If GetType(TEntity) Is GetType(Trip) Then
      Dim tripQuery = From t In _commonContext.Trips.Include("Destination") _
                    Order By t.Destination.DestinationName, t.StartDate _
                    Select t
      Return _dal.ExecuteList(tripQuery).Cast(Of TEntity).ToList()
    Else
      Return _dal.GetReferenceList(Of TEntity)(_commonContext, orderBy)
    End If
  End Function

public List<TEntity> GetReferenceList<TEntity>(string orderBy)
{
  if (typeof(TEntity) == typeof(Trip))
  {
    var tripQuery =
        from t in _commonContext.Trips.Include("Destination")
        orderby t.Destination.DestinationName, t.StartDate
        select t;
    return _dal.ExecuteList<Trip>(tripQuery).Cast<TEntity>().ToList();
  }
  else
  {
    return _dal.GetReferenceList<TEntity>(_commonContext, orderBy);
  }
}

A Set of Extensions to Allow Multiple Resultsets per Query

You may recall a pointer in Chapter 13, Working with Stored Procedures When Function Mapping Won't Do to Colin Meek's EFExtensions that are available on the MSDN Code Gallery. The extensions enable the Entity Framework to call stored procedures that return multiple resultsets whether the results are entities or anonymous types. You might want to consider these as an alternative to what is being demonstrated in this section.

Choosing between complete entities and narrower types for lists

Although the DataBridge class uses the KeyListItem class, which is designed to provide lists for data-bound controls, these pick lists are not using them. Instead, the queries are returning complete entities for these drop-down lists. I decided to use the entities rather than creating lists from projected queries. This results in more data traveling from the database back through the layers to the form. So, why does the business layer use entire entities instead?

I weighed two development benefits against the potential performance cost. One benefit is the simplicity of the queries; but more important is an advantage that Windows Forms data binding provides.

You can use navigation properties in entities for data binding. Combined with the fact that the Windows Forms ComboBox control knows how to bind to objects, you have a very simple solution for populating the lists. When you bind the ComboBox to the navigation property using the ComboBox.SelectedItem property and then use a list of entities as the control's DataSource, the control does the rest of the work for you. All of the entities except for the Trip entity have only two properties anyway, so the performance impact of using them rather than a key/value type of class shouldn't be significant in this scenario.

However, it is something to keep in mind, and you may choose a different method of creating pick lists in your application layers.

Saving Changes

The new class should have a way for the UI developer to save the user's changes.

Here is a SaveChanges method that wraps the ObjectContext's custom SaveAllChanges so that the UI can call it easily:

Public Function SaveChanges() As Boolean
  Try
    _commonContext.SaveAllChanges()
    Return True
  Catch ex As Exception
    Return False
  End Try
End Function
public bool SaveChanges()
{
  try
  {
    _commonContext.SaveAllChanges();
    return true;
  }
  catch (Exception ex)
  {
    return false;
  }
}

Rolling Back User Changes

In Chapter 16, Making It Real: Connections, Transactions, Performance, and More, you learned that there is no transactional support for the ObjectContext, nor is there an automatic way to revert entity modifications back to their original values. What if the user wants to undo the edits he made and continue working on the client?

Although the Entity Framework's rich features offer a number of ways to roll back to original values, in the long run the simplest route is to refresh the graph from the database. Additionally, you may already have a mechanism in place for rolling back changes to your objects. Be sure to test it thoroughly with the entity objects since the relationships work differently than most other frameworks might expect.

You may be tempted to try to do something in memory rather than make an extra trip to the database. Let's look at some of the opportunities, along with their pros and cons.

Considering in-memory rollbacks

Rolling back changes in the Entity Framework poses a number of challenges, because the ObjectContext does not provide any methods to do this.

You need to consider a few things when rolling back data in a graph:

Scalar properties of the parent object (e.g., Customer)

You must reset fields such as Name and BirthDate.

Navigation properties that are EntityReferences (e.g., PrimaryActivity)

To ensure that a reference, such as which activity the customer prefers, has not been changed, you'll need to reset the EntityKey values of each EntityReference.

Navigation properties that are EntityCollections

You need to reset these properties to the original EntityCollection. The scalar properties of items in the collection could have been modified, and items could have been added or removed.

Any additional EntityReferences or EntityCollections deeper in the graph that might have been modified

All of the aforementioned rollback considerations would need to be applied for additional items in a graph as well.

Changing the scalar properties is not a major challenge, and a few patterns are available for you to do this. One involves storing the scalar values in local variables, resetting them to roll back, and applying them. Another involves using the ObjectStateEntry's features to read the OriginalValues and push them into the CurrentValues. The first option is probably the easiest to code, but the second option, shown in Example 20.12, "Resetting an entity's current (scalar) values to its original values", could be coded as a generic routine that you can reuse.

Example 20.12. Resetting an entity's current (scalar) values to its original values

Public Sub RevertEntityScalars(ByRef entity As EntityObject)
  Dim custEntry = _commonContext.ObjectStateManager. _
   GetObjectStateEntry(entity.EntityKey)
  Dim dri = custEntry.CurrentValues.DataRecordInfo
  For i = 0 To custEntry.CurrentValues.FieldCount - 1
    'changing an EntityKey property will throw an error, check first
    Dim propName = dri.FieldMetadata(i).FieldType.Name
    If Not (From keys In custEntry.EntityKey.EntityKeyValues _
            Where keys.Key = propName).Any Then
      custEntry.CurrentValues.SetValue(i, custEntry.OriginalValues(i))
    End If
  Next
End Sub
public void RevertEntityScalars(ref EntityObject entity)
{
  var custEntry = _commonContext.ObjectStateManager
                  . GetObjectStateEntry(entity.EntityKey);
  var dri = custEntry.CurrentValues.DataRecordInfo;
  for (var i = 0; i < custEntry.CurrentValues.FieldCount; i++)
  {
    //changing an EntityKey property will throw an error, check first
    var propName = dri.FieldMetadata[i].FieldType.Name;
    if (!(from keys in custEntry.EntityKey.EntityKeyValues
          where keys.Key == propName
          select keys).Any()) ;
    {
      custEntry.CurrentValues.SetValue(i, custEntry.OriginalValues[i]);
    }
  }
}

Navigation properties pose a bigger challenge, however. To read that information out of the ObjectStateManager you must search through RelationshipEntries to find Related EntityKeys. But at this point, you will hit a wall. Is the relationship Added or Deleted? If it's Added, how do you remove it completely from the context? If it's Deleted, how do you change it to Unchanged? Even if you could find a way around this, the next steps would be equally onerous: finding the related entities, removing any Added ones, changing the state of any Deleted ones, and reverting the scalar properties. This is probably not a path you want to go down unless you are writing a framework that will be reused often.

Other possibilities are available, but each poses such daunting challenges that it would take a good amount of crafty code to solve them.

Note
I have worked toward achieving a number of these patterns with the hope of sharing them, but I found them to be so elaborate that I don't believe they are good guidance for general use of the Entity Framework, nor would they fit into the scope of this book. I'll lay out the concepts here so that you know what I've attempted already, but I won't show the pieces of code, as they won't add up to a complete solution.

One of these schemes is to store the original EntityCollections and the EntityReference EntityKeys into a dictionary and then refresh the values from there. But you will run into another wall going down this path. You'll need to store a copy of these items; otherwise, any changes to the current graph will be made to items in the dictionary, and you won't have original values.

How do you make a copy of the graph? Not easily. You could use a combination of reflection and MetadataWorkspace to create clones of the entities. That would require a lot of work, but it has been done (see Matthieu Mezil's Entity Cloner blog post at http://msmvps.com/blogs/matthieu/archive/2008/05/31/entity-cloner.aspx/). You could also use binary serialization, streaming the results of the serialized graph into an object that you store in a variable (see the MSDN topic "How to: Serialize and Deserialize Objects [Entity Framework]," at http://msdn.microsoft.com/en-us/library/bb738528.aspx/). However, you will need to manually detach each item from the context before you serialize the graph, and you may recall that ObjectContext.Detach can detach only a single entity at a time, not a graph. You need to detach these items because there is a lingering reference to the ObjectContext when you serialize, which will prevent you from reattaching the entities to any other context. This results in a major effort and a lot of code if you were to do this in memory, although in some cases it will certainly be worth the effort.

Keeping it simple: Rolling back from the database if you can

The cleanest path for a rollback is to refresh the data from the database by calling the GetCustomerwithRelatedData method from Example 20.10, "Getting a customer from the database, and converting a noncustomer to a customer on the fly if necessary", which uses the OverwriteChanges merge option when it executes the query.

Why not use ObjectContext.Refresh with StoreWins? Remember that this will update only the Customer. Even though it seems like overkill to make an extra trip to the database, this truly is the simplest way to achieve the rollback.

To make it simpler for the UI developer, the bridge class has a RollbackCustomer method (see Example 20.13, "The RollbackCustomer method in the DataBridge class"). With this method, the UI developer doesn't have to know how you decided to solve the rollback problem. Plus, you can change the RollbackCustomer method at a later time without impacting the UI.

Example 20.13. The RollbackCustomer method in the DataBridge class

Public Function RollbackCustomer(ByRef cust As Customer) As Customer
  Return GetCustomerwithRelatedData(cust.ContactID)
End Function
public Customer RollbackCustomer(ref Customer cust)
{
  return GetCustomerwithRelatedData(cust.ContactID);
}

Now that you've got the critical elements of the DataBridge class laid out, let's take a look at using it from a UI along with some of the specific concerns that entities bring to the UI in a layered application.

Rather than walk through building up the entire form, this section will focus on the critical UI elements and code that the form will need in order to work with the DataBridge toward the goal of removing business and data access logic from the UI.

The first thing to tackle is data binding to the data returned by the DataBridge.

Using BindingSourceControls with the DataBridge

Data binding is not limited to scenarios where the UI controls the data access. It is very handy even when you have separated your business and data access logic from the user interface. Without it you would have to manually wire up every text box and every drop-down control and keep track of the state of each control, updating the entities as needed. Although many developers may turn up their noses at the thought of using any type of automation in their applications, it makes a lot of sense to take advantage of these tools.

This form uses BindingSourceControls to handle that tedium for the Customer, Addresses, and Reservations collections (see Figure 20.4, "BindingSource controls wired up to grids for displaying the Customer's EntityCollections").

Figure 20.4. BindingSource controls wired up to grids for displaying the Customer's EntityCollections

BindingSource controls wired up to grids for displaying the Customer's EntityCollections

Instantiating the DataBridge Class in the Form

The DataBridge class is elemental to the form. Rather than merely declaring it in the form's declarations, you can instantiate it at that time. There's no need to postpone instantiating the class, since it is used right away when the form itself is loading.

Public Class Form1
  Dim bridge As New BAGA.DataBridge
public partial class Form1
{
  private BAGA.DataBridge bridge = new BAGA.DataBridge();

Populating the Form with an Entity and Its Related Data

Note
Use the form's screenshot in Figure 20.3, "A typical master/detail data entry form" as a reference for the elements of the form.

One of the form's first tasks is to populate a ComboBox control that will allow the user to select which customer to work with. Call the DataBridge.AllContactsList method and then bind the results, a List of KeyListItem types that have an ID property and a Name property, to the ComboBox control to do this.

Note
Tasks that are common to building Windows Forms will not be spelled out. If you want to see every detail of the form, you can download the solution from the book's website.

Each time a user selects a customer from the customer pick list, the customer graph is retrieved from the bridge class and then passed to a method, FillForm, which populates the form. Here the method binds the customer entity and its related children, Addresses and Reservations, to the three BindingSource controls. In turn, the form fields and grids are wired up to those BindingSources (see Example 20.14, "Retrieving the customer graph and binding the customer to the form's data sources").

Example 20.14. Retrieving the customer graph and binding the customer to the form's data sources

Dim cust As Customer = bridge.GetCustomerwithRelatedData( _
 CInt(cboCustList.SelectedValue))
FillForm(cust)

Private Sub FillForm(ByVal cust As Customer)
  _fillCustomer = True 'prevents the combos' selected events from executing
  CustomerBindingSource.DataSource = cust
  ReservationsBindingSource.DataSource = cust.Reservations
  AddressesBindingSource.DataSource = cust.Addresses
  NeedsSaveSymbol.Visible = False
  _fillCustomer = False
End Sub
Customer cust = bridge.GetCustomerwithRelatedData( _
 Convert.ToInt32(cboCustList.SelectedValue));
FillForm(cust);

private void FillForm(Customer cust)
{
  _fillCustomer = true; // prevents the combos' selected events from executing
  CustomerBindingSource.DataSource = cust;
  ReservationsBindingSource.DataSource = cust.Reservations;
  AddressesBindingSource.DataSource = cust.Addresses;
  NeedsSaveSymbol.Visible = false;
  _fillCustomer = false;
}

The form uses a local Boolean, _fillCustomer, as a flag to help prevent the Selected events of the various combos from firing as they are being populated. You can see how this property is used in the complete sample application.

As you saw in previous chapters, Entity Framework entities are designed to work with data-bound controls in .NET. You can even data-bind the navigation properties, which is why you can data-bind the EntityReference properties such as PrimaryActivity and the Reservations and Addresses EntityCollections to another grid (using the BindingDataSource controls). With this capability, it is possible to build master/detail forms with entity graphs.

The controls that bind to scalar properties do so through the controls' DataBinding.Text property. As described earlier, the ComboBox controls that bind to navigation properties that are EntityReferences (e.g., PrimaryActivity) do so through the controls' SelectedItem property. Finally, the grids that are bound to the EntityCollection navigation properties do so using the DataSource property.

CustomerTypeComboBox.SelectedItem = cust.CustomerType
Note
The ComboBox controls are odd in that when the rollback is performed, even though the correct data is in the Customer the ComboBoxes don't automatically refresh. A RefreshCombo method explicitly sets the SelectedItem for each ComboBox control. For example:

Once the binding is in place, editing data in the graph, even within the collections, happens automatically. But there is one thing that is important to watch out for: deleting entities from the entity collections (e.g., deleting a reservation). We'll take a look at this issue after dealing with the pick lists.

Consuming the Pick Lists in the Form

In previous samples in this book, you saw how to use pick lists to allow the user to edit the EntityReference navigation properties of an entity. In the case of the Customer entity, this would require lists of activities and destinations for the customer's preferences and a list of CustomerTypes.

The form can easily call the GetReferenceList method from the DataBridge by merely passing in the required type. Because we'll need to consume the Activity and Destination lists twice, you'll see in Example 20.15, "Filling the ComboBoxes in the UI" that I'm doing something peculiar with the second combo box for each of these. The explanation for this follows the code listing. Because the lists contain complete entities and the control uses the SelectedItem property, it is not necessary to define a ValueMember.

Example 20.15. Filling the ComboBoxes in the UI

Private Sub FillDropDowns()
  'Activity & Destination lists created in advance for reuse
  Dim ActList = bridge.GetReferenceList(Of Activity)("ActivityName")
  Dim DestList = bridge.GetReferenceList(Of Destination)("DestinationName")

  CustomerTypeComboBox.Fill(bridge.GetReferenceList(Of CustomerType)("CustomerTypeName"), _
   "CustomerTypeName")

  PrimaryActivityComboBox.Fill(ActList, "ActivityName")
  SecondaryActivityComboBox.Fill((From a In ActList Select a).ToList, "ActivityName")

  PrimaryDestinationComboBox.Fill(DestList, "DestinationName")
  SecondaryDestinationComboBox.Fill((From d In DestList Select d).ToList, _
   "DestinationName")
End Sub

private void FillDropDowns()
{
  //Activity & Destination lists created in advance for reuse
  var ActList = bridge.GetReferenceList<Activity>("ActivityName");
  var DestList = bridge.GetReferenceList<Destination>("DestinationName");

  CustomerTypeComboBox.Fill(bridgeS.GetReferenceList<CustomerType>("CustomerTypeName"), 
                            "CustomerTypeName");

  PrimaryActivityComboBox.Fill(ActList, "ActivityName");

  SecondaryActivityComboBox.Fill((from a in ActList select a).ToList(),
                                 "ActivityName");

  PrimaryDestinationComboBox.Fill(DestList, "DestinationName");
  SecondaryDestinationComboBox.Fill((from d in DestList select d).ToList(),
                                     "DestinationName");
}
SecondaryActivityComboBox.DataSource
Warning
In this form, the ActivityList is bound to two separate controls: one for primary activities and one for secondary activities. Similarly, the destination list is also being used for multiple controls. The list of entities suffers the same fate as any other object type used in a list when bound to multiple objects: when one control changes the pointer on the list, the pointer change shows up in the other control. That is why a new list is created using a query for the following:

Deleting from Grids When EntityCollections and Referential Constraints Are Involved

Reservations is an EntityCollection, so when you bind to Customer.Reservations, you are literally binding to the EntityCollection.

When you delete a row from a grid, you are not deleting a Reservation, but rather are triggering EntityCollection.Remove, which merely removes the Reservation from the collection. The relationship is deleted, but the Reservation itself is Unchanged and will not be deleted when you call SaveChanges. However, when you call SaveChanges, you will get the following exception message, which is thrown as the Entity Framework is trying to work out the store query:

A relationship is being added or deleted from an AssociationSet
'FK_Reservations_Customers'. With cardinality constraints,
a corresponding 'Reservations' must also be added or deleted.

This is caused not by something in the conceptual model, but by a constraint defined in the store model, which reflects the primary key/foreign key relationship between Customer and Reservations in the database. A reservation must have a CustomerID foreign key. When you delete the relationship, the Entity Framework will try to update the Reservation by removing the CustomerID, which is not allowed based on the model's definition of this relationship (see Example 20.16, "A dependent relationship between Reservation and Customer as described by the model's store layer (SSDL)").

Example 20.16. A dependent relationship between Reservation and Customer as described by the model's store layer (SSDL)

<Association Name="FK_Reservations_Customers">
  <End Role="Customers" Type="BreakAwayModel.Store.Customers"
       Multiplicity="1" />
  <End Role="Reservations" Type="BreakAwayModel.Store.Reservations"
        Multiplicity="*" />
  <ReferentialConstraint>
    <Principal Role="Customers">
      <PropertyRef Name="ContactID" />
    </Principal>
    <Dependent Role="Reservations">
      <PropertyRef Name="ContactID" />
    </Dependent>
  </ReferentialConstraint>
</Association>

The fact that the grid's Delete action only removes the child entity is a great thing if, for instance, your master and detail are for a Class with Students. It's probably pretty clear that you are just removing the student from the class and not kicking the student out of the school and obliterating every record of her existence. But the Entity Framework can't have different behaviors for different scenarios. Instead, the single predictable behavior is that the relationship is deleted, not the entity.

Note
LINQ to SQL has a similar issue because you are also deleting the relationship. As Beth Massi describes in her extended tutorial blog post "One-To-Many (Master-Detail) Forms with LINQ to SQL," LINQ to SQL deletes the relationship by setting the foreign key value of the child entity to null. So, when you delete a row, the entity isn't deleted and only the foreign key is removed. LINQ to SQL has an attribute that you can apply that will delete the object when a particular value is null. Check Beth's post for the details, at http://blogs.msdn.com/bethmassi/.

There is a case when this problem won't occur. That's when the child entity has a composite key. In that case, the constraint will be built into the Conceptual Schema Definition Layer (CSDL) and the Entity Framework will be able to handle it more easily. Following is an explanation of how the delete is impacted when the child has a composite key. Then you will see a solution for handling the problem that exists when attempting to delete the Reservation-where the child entity does not have a composite key, and therefore no constraint exists in the CSDL.

Deleting children from a grid is easier when the child has a composite key

Like the Customer/Reservations relationship, in the AdventureWorksLT database a SalesOrderDetail must be attached to a SalesOrderHeader. However, the SalesOrderDetail table has a composite key (OrderDetailID plus OrderID) that makes it possible to create a referential constraint in the CSDL; see Example 20.17, "An association with a constraint in the CSDL".

Example 20.17. An association with a constraint in the CSDL

<Association Name="FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID">
  <End Role="SalesOrderHeader"
       Type="AdventureWorksLTModel.SalesOrderHeader" Multiplicity="1">
    <OnDelete Action="Cascade" />
  </End>
  <End Role="SalesOrderDetail"
       Type="AdventureWorksLTModel.SalesOrderDetail" Multiplicity="*" />
  <ReferentialConstraint>
    <Principal Role="SalesOrderHeader">
      <PropertyRef Name="SalesOrderID" />
    </Principal>
    <Dependent Role="SalesOrderDetail">
      <PropertyRef Name="SalesOrderID" />
    </Dependent>
  </ReferentialConstraint>
</Association>

When the user deletes a SalesOrderDetail in a detail grid, this causes the relationship between that entity and its SalesOrderHeader to be deleted. In turn, the constraint defined in the model tells the Entity Framework that because the relationship is gone, it must delete the entity (SalesOrderDetail) when SaveChanges is called. In this scenario, everything works as expected.

In more common scenarios (or less well-designed databases), you won't have the foreign key as an actual property in the entity once it's been used for a navigation property, so you can't create the constraint. This works only when you have a composite key.

Deleting child entities in a grid when no referential constraint exists in the CSDL

How can you solve the problem of deleting reservations and addresses from their detail grids? Let's look at some options that don't involve introducing business logic into the UI.

One option is to modify the database, introducing the composite key and then updating the association details in the model to reflect the constraint. If you are still designing your model and do not have a lot of code that already depends on the model, this might not be a bad option. But introducing the composite key as well as a new property in the entity could wreak havoc in existing code.

Another option is to create logic in the partial classes or even an extension method that identifies the deleted relationship, read the Store Schema Definition Layer (SSDL) using the MetadataWorkspace to identify which end of the relationship also needs to be deleted, and therefore place the logic into the model's business classes. This can get pretty complicated, but it would keep the logic out of the user interface.

But there's a much nicer option. You may recall from Chapter 10, Customizing Entities that there is an AssociationChanged event but no AssociationChanging event. The AssociationChanged event is wired up to navigation properties in an entity. You can add logic to the AssociationChanged event for the Reservations and Addresses properties of the Customer entity and raise an event that will let the new class know to delete the object from the context. Then, when SaveChanges is called, the child will be deleted from the database.

This will take a few steps to wire up. Example 20.18, "Creating the public event that will be raised" does this for the deleted reservations.

First, add an event to the Customer class that you can use for any child entity type being removed. The event will need to pass the entity that needs to be deleted, so include that in the signature. Use the IEntityWithRelationships interface as the type so that all of the casting will work as the entity is passed around. C# does this with an event and a delegate, as you'll see in the example.

Example 20.18. Creating the public event that will be raised

Partial Public Class Customer
'add an Public Event to the class
 Public Event ChildRelationshipRemoved( _
   ByVal entity As IEntityWithRelationships)
public partial class Customer
{
public delegate void ChildRelationshipRemovedEventHandler(
 IEntityWithRelationships entity);
public event ChildRelationshipRemovedEventHandler
 ChildRelationshipRemoved;

Next, you need to add an EventHandler for the custom method that will be called, Reservations_AssociationChanged, and that will be fired when the AssociationChanged event is raised. This goes in the Customer's constructor (see Example 20.19, "Creating an EventHandler for the AssociationChanged event").

Example 20.19. Creating an EventHandler for the AssociationChanged event

Public Sub New()
AddHandler Me.Reservations.AssociationChanged, _
  AddressOf Reservations_AssociationChanged
End Sub
public Customer()
{
  Reservations.AssociationChanged +=
   CollectionChangeEventHandler(Reservations_AssociationChanged);
}

Still in the Customer's partial class, you now need to create the Reservations_AssociationChanged method. This will check to see whether the action that caused us to get here was from a relationship being removed. If so, you'll check for the condition that is a problem, which is that the relationship was removed, but the entity was not deleted. Example 20.20, "The event handler for AssociationChanged" shows this code.

Note
You are building in this business rule for any application that uses this entity. However, you are not automatically deleting the entity, just raising an event. Even if you wanted to automatically delete the entity, you can't do so from the Customer class anyway. So, you can raise the event to the class that caused the relationship to be removed, and if it wants to handle it, it can. Otherwise, it can just ignore the event completely.

Example 20.20. The event handler for AssociationChanged

Private Sub Reservations_AssociationChanged( _
 ByVal sender As Object, ByVal e As CollectionChangeEventArgs)
  Dim act As System.ComponentModel.CollectionChangeAction = e.Action
  If act = CollectionChangeAction.Remove Then
    Dim impactedReservation = CType(e.Element, Reservation)
    If Not impactedReservation.EntityState = EntityState.Deleted Then
        RaiseEvent ChildRelationshipRemoved( _
         CType(impactedReservation, IEntityWithRelationships))
      End If
    End If
  End Sub
private void Reservations_AssociationChanged(
 object sender, CollectionChangeEventArgs e)
{
  CollectionChangeAction act = e.Action;
  if (act == CollectionChangeAction.Remove)
  {
    var impactedReservation = (Reservation)e.Element;
    if (!(impactedReservation.EntityState == EntityState.Deleted))
     ChildRelationshipRemoved(
      (IEntityWithRelationships)impactedReservation);
  }
}

That's it for the Customer class. Now you have to capture this in the DataBridge class.

The first step is to wire up this event to the Customer when the Customer is created in the DataBridge class. In the GetCustomerwithRelatedData method, add the handler in Example 20.21, "Adding the EventHandler into the GetCustomerwithRelatedData method", just before the part of the code that returns the customer to the UI. The new line of code is in bold. This handler says that when the ChildRelationshipRemoved event is raised, call the DeleteChildEntityMethod.

Example 20.21. Adding the EventHandler into the GetCustomerwithRelatedData method

If Not cust Is Nothing Then
    AddHandler cust.ChildRelationshipRemoved, _
    AddressOf DeleteChildEntity
  Return cust
End If
if (cust != null)
{
    cust.ChildRelationshipRemoved +=  DeleteChildEntity;
  return cust;
}

The last step is to add the new DeleteChildEntity method in the new layer to ensure that a Reservation that is removed from a Customer is deleted in the database (see Example 20.22, "Deleting a child"). Notice that the method has the same signature as the event handler raised by the Customer.

Example 20.22. Deleting a child

Public Sub DeleteChildEntity(ByVal entity As IEntityWithRelationships)
  _commonContext.DeleteObject(entity)
End Sub
public void DeleteChildEntity(IEntityWithRelationships entity)
{
   _commonContext.DeleteObject(entity);
}

Although this problem was highlighted by the Windows Forms GridView control, you will have the same issue when deleting EntityCollection items from any other data-binding control, whether it's Windows Forms, WPF, or even the ASP.NET platform. So, by putting the solution within the business layer (including the entity class), you will be able to reuse it in other applications.

In the UI, you can provide a rollback button on the form that calls the RollbackCustomer method and repopulates the form. In Figure 20.5, "Adding a rollback feature (the Discharge Changes button) to the UI" the Discard Changes button is the user's mechanism for rolling back edits.

Figure 20.5. Adding a rollback feature (the Discharge Changes button) to the UI

Adding a rollback feature (the Discharge Changes button) to the UI

In a Windows form where a BindingSource control is used, such as the CustomerBindingSource in the example form, you'll need to dig into the BindingSource to get a reference to the entity to roll back. Example 20.23, "Rolling back changes from the UI" shows how the CustomerBindingSource.Current property returns a customer entity (though the returned item needs to be cast to Customer). Then that customer can be passed as a parameter to the DataBridge.RollbackCustomer method.

Example 20.23. Rolling back changes from the UI

Private Sub Rollback_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles Rollback.Click
    Dim custtoRollBack = CType(CustomerBindingSource.Current, Customer)
    Dim newcust = bridge.RollbackCustomer(custtoRollBack)
  'this seems to be necessitated by a drawback with WinForms objectbinding
    RefreshCombos()
End Sub
private void Rollback_Click(object sender, System.EventArgs e)
{
  var custtoRollBack = (Customer)CustomerBindingSource.Current;
  var newcust = bridge.RollbackCustomer(ref custtoRollBack);
 //this seems to be necessitated by a drawback with WinForms objectbinding
  RefreshCombos();
}

A typical line-of-business application helps users who might attempt to close a form or move to another record without explicitly saving their changes. Rather than automatically saving the edits, the application will ask whether the user wants to save his edits before leaving the form, or whether he wants to cancel them, leaving the database untouched.

The IsDirty property facilitates validating current data before the user is allowed to move to another set of data or leave the data entry form. In the sample form, this would occur when the user selects a new customer from the drop-down list, attempts to close the form, or shuts down the application completely (see Example 20.24, "Using IsDirty to validate edits").

You can call the ValidateCustomerEdits method from any of these events.

Example 20.24. Using IsDirty to validate edits

Private Function ValidateCustomerEdits() As Boolean
  If bridge.IsDirty Then
  Dim response = MessageBox.Show _
     ("Save Changes to current Customer" & _
      " (including addresses and reservations)?", _
     "BreakAway", MessageBoxButtons.YesNoCancel)
  Select Case response
    Case Windows.Forms.DialogResult.Yes
     'save and move on
      bridge.SaveChanges()
      Return True 'ok to continue
    Case Windows.Forms.DialogResult.No
     'ignore the changes, if user returns to this customer,
     'GetCustomer will update the cache
      Return True 'ok to continue
    Case Windows.Forms.DialogResult.Cancel
     'do not leave customer
      Return False
    End Select
  Else
    Return True
  End If
End Function
private bool ValidateCustomerEdits()
{
  if (bridge.IsDirty)
  {
    var response = MessageBox.Show
      ("Save Changes to current Customer " +
      "(including addresses and reservations)?",
      "BreakAway", MessageBoxButtons.YesNoCancel);
    switch (response)
    {
      case System.Windows.Forms.DialogResult.Yes:
       //save and move on
        bridge.SaveChanges();
        return true;
        break;
      case System.Windows.Forms.DialogResult.No:
       //ignore the changes, if user returns to this customer,
       //GetCustomer will update the cache
        return true;
        break;
      case System.Windows.Forms.DialogResult.Cancel:
        //do not leave customer
        return false;
    }
  }
  else
    return true;
}

If the user chooses to save changes, you only need to make a call to the SaveChanges method in the layer class, which calls the ObjectContext.SaveChanges or the new SaveAllChanges method from Chapter 18, Handling Entity Framework Exceptions, which checks for exceptions.

You also can use the IsDirty property to provide visual clues regarding the state of the customer. In Figure 20.5, "Adding a rollback feature (the Discharge Changes button) to the UI", notice the icon to the right of the Save button. This is driven by the value of bridge.IsDirty and acts as a reminder to the user that he has changes to save. The icon's Visible property is set to True or False in the Validate events of the BindingSource and some of the controls. You can see it being set in the FillForm method in Example 20.14, "Retrieving the customer graph and binding the customer to the form's data sources".

In this chapter, you got a chance to build code that can be reused in other EF applications. You also saw how to separate your interaction with the ObjectContext out of the UI, along with some of the challenges you will encounter in doing so. Although you saw solutions for these challenges, you also got a chance to step through some of the thought process that went into choosing the right solution for each given scenario. As such, you should be able to walk away with some ideas for going to the next level if you are willing to make the investment, such as some suggestions for persisting entities in memory for rolling back changes to a particular point in time.

Because you can take advantage of a long-running ObjectContext in a client application, you can focus on separating your logic without having to worry about the issues that occur when detaching and reattaching entities from a context that you have encountered (and are about to revisit in the next few chapters) with web applications and services. Therefore, you can perform queries and leave the entities attached to the original context, allowing users to modify the data and save using the same context. Although the long-running context makes change tracking easy, other properties of entities and the Entity Framework make it necessary to work out particular solutions to keep the business logic separate from the user interface when working with entities.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.