Chapter 10. Customizing Entities

In previous chapters, we worked with entity classes that the Entity Framework’s code generator created. The methods and events available to you for these classes were limited to the methods and events derived from their base classes: ObjectContext and EntityObject.

Because the purpose of entities is to provide data schema, they contain little in the way of business logic. This is great for getting started, but many applications will need more.

The extensibility of the Entity Framework provides a number of ways to not only add your own logic, but also use your own classes and plug them into an ObjectContext.

In this chapter, you’ll learn how to add new logic to entities or override their existing logic using partial classes, a feature introduced in .NET 2.0.

In Chapter 19, you will learn how you can tie your own custom classes into the Entity Framework.

Partial Classes

All of the classes that are generated from an Entity Data Model (EDM)—the class that inherits from ObjectContext as well as the entities themselves—are partial classes. Partial classes allow you to break a class into multiple code files, and they are especially valuable when you want to make changes to generated code. Without partial classes, modifications to generated code will be lost whenever the generation is performed again. Rather than making your changes directly in the generated code, you can make them in a separate file that will not be touched when the code generator performs its magic. As long as your class is declared a partial class, another class with the same name will not cause a conflict. During compilation, .NET merges the separate files into one class.

Note

For a great introduction to partial classes, the article “Implications and Repercussions of Partial Classes in the .NET Framework 2.0” (http://www.code-magazine.com/article.aspx?quickid=0503021/) by Dino Esposito is very informative.

For example, a quick look at the code that is generated for the BreakAway application described in previous chapters reveals that the ObjectContext class and the application entities are marked as partial classes, as shown in Example 10-1.

Example 10-1. The ObjectContext and entities marked as partial classes

VB
Partial Public Class BreakAwayEntities
    Inherits Global.System.Data.Objects.ObjectContext

Partial Public Class Trip
    Inherits Global.System.Data.Objects.DataClasses.EntityObject
C#
public partial class BreakAwayEntities :
  global::System.Data.Objects.ObjectContext

public partial class Trip :
 global::System.Data.Objects.DataClasses.EntityObject

To add to any of these classes all you need to do is to create another file and declare another partial class, which you will see in the upcoming examples. There are a few rules for implementing partial classes: you don’t need to repeat inheritance or interface implementations; all of the partial classes for a particular class need to be in the same assembly; and you must not repeat any attributes. With regard to the last point, if you try to state the attributes more than once, you will get a compiler error letting you know that this is a problem.

Visual Basic infers the assembly namespace when creating additional parts of a partial class, whereas C# requires the namespace to be specified, as shown in the following code:

VB
Partial Public Class BAEntities

End Class
C#
namespace BAGA  //assembly namespace is required for C# partial classes
{
  public partial class BAEntities
  {
  }
}

Customizable Methods

In addition to being able to split classes into multiple files, partial classes allow you to split methods across the files as well, using a technique called partial methods. The Entity Framework creates a few partial methods for its code-generated classes. These methods are declared but not implemented in the generated class. You can then add the method’s implementation in your partial class. These generated partial methods include one that is called when an ObjectContext is instantiated, named OnContextCreated, and a pair of methods, Changed and Changing, for every property of every entity. In the following sections we’ll look at each in more detail.

The OnContextCreated Method

The first partial method, ObjectContext.OnContextCreated, lets you add custom code that will be executed at the time the context is instantiated. Here is how that is implemented in the generated code.

Note

The partial methods have names that follow the naming convention of an event. But even though the names may make you think these are events, they are, in fact, methods. This is because of the nature of partial classes and the ability to split up a class’s code across files. In the end, they do behave like events at runtime, but no inheritance is involved. At compile time, if the partial method is not implemented, it is not included in the compiled assembly, which is a nice form of optimization.

The method is defined in the class that derives from ObjectContext (e.g., BAEntities). As you can see in the following code, VB and C# differ in their syntax:

VB
Partial Private Sub OnContextCreated()
End Sub
C#
partial void OnContextCreated();

OnContextCreated is called by the context object’s constructor and the constructor overloads, as shown in the following code:

VB
Public Sub New()
  MyBase.New("name=BAEntities", "BAEntities")
  Me.OnContextCreated
End Sub

Public Sub New(ByVal connectionString As String)
  MyBase.New(connectionString, "BAEntities")
    Me.OnContextCreated
End Sub

Public Sub New(ByVal connection _
 As Global.System.Data.EntityClient.EntityConnection)
  MyBase.New(connection, "BAEntities")
  Me.OnContextCreated
End Sub
C#
public BAEntities() : base("name=BAEntities", "BAEntities")
{
   this.OnContextCreated();
}

public BAEntities(string connectionString) : base(connectionString, "BAEntities")
{
  this.OnContextCreated();
}

public BAEntities(global::System.Data.EntityClient.EntityConnection connection)
 : base(connection, "BAEntities")
{
  this.OnContextCreated();
}

By default, the OnContextCreated partial method contains no code because in the generated classes, the partial methods are only being declared. In the partial class that you write, you can add your own code to the method.

Implementing OnContextCreated

To add code that you want to run when a context is instantiated, place the method calls inside the partial class for the ObjectContext.

Visual Basic has properties, events, and methods available in drop-down boxes at the top of the code window. Select BAEntities in the Class Name drop-down on the left, and then select OnContextCreated from the Method Name drop-down on the right. This will automatically create the following VB code, which you could also just type in manually; in C#, you must type the method in manually:

VB
Private Sub OnContextCreated()
 'add logic here
End Sub
C#
partial void OnContextCreated()
{
  //add logic here
}

The On[Property]Changed and On[Property]Changing Methods

Every scalar property of every entity has its own version of PropertyChanging and PropertyChanged—for example, FirstNameChanged and FirstNameChanging. Like OnContextCreated, there is no default implementation for PropertyChanging and PropertyChanged; only a declaration. The methods exist in case you want to take advantage of them. PropertyChanging lets you insert logic when a property is about to change, and PropertyChanged is hit after the property has changed.

Note

ObjectContext also has a pair of Changed and Changing events. So, although you can insert logic based on a specific property changing with the partial methods, you can also have logic that runs, no matter which property is changed. We will discuss these events in Customizable Event Handlers.

In the generated code, the methods are defined and then called in each property’s setter. The following examples show what this looks like for the ActivityName property of the Activity entity in the generated code. First the two partial methods are declared (see Example 10-2).

Example 10-2. The property Changing and Changed method declarations

VB
Partial Private Sub OnActivityNameChanging(ByVal value As String)
End Sub
Partial Private Sub OnActivityNameChanged()
End Sub
C#
partial void OnActivityNameChanging(string value);
partial void OnActivityNameChanged();

Then the ActivityName property calls those methods just before and after the value is changed (see Example 10-3).

Example 10-3. The generated class calling the Changing and Changed methods

VB
Public Property ActivityName() As String
  Get
    Return Me._ActivityName
  End Get
  Set
    Me.OnActivityNameChanging(value)
    Me.ReportPropertyChanging("ActivityName")
    Me._ActivityName = Global.System.Data.Objects.DataClasses. _
     StructuralObject.SetValidValue(value, true)
    Me.ReportPropertyChanged("ActivityName")
    Me.OnActivityNameChanged
  End Set
End Property
C#
public string ActivityName
{
  get
  {
    return this._ActivityName;
  }
  set
  {
    this.OnActivityNameChanging(value);
    this.ReportPropertyChanging("ActivityName");
    this._ActivityName = global::System.Data.Objects.DataClasses.
     StructuralObject.SetValidValue(value, true);
    this.ReportPropertyChanged("ActivityName");
    this.OnActivityNameChanged();
  }
}

Implementing the property-level PropertyChanged and PropertyChanging methods

To implement the PropertyChanged and PropertyChanging methods, create a new code file to contain custom code for the Activity entity, and name the file Activity.vb or Activity.cs. In the file, add the code shown in Example 10-4.

Example 10-4. Defining a partial class for an entity

VB
Partial Public Class Activity
End Class
C#
public partial class Activity
{
}

Visual Basic’s event drop-downs make the next steps a little simpler than in C#.

In VB, select Address from the Class Name drop-down; this will cause the Method Name drop-down to populate with all of the property-changing events. Choose OnAddressIDChanging and OnAddressIDChanged, which will stub out the event handler methods for you automatically.

In C#, IntelliSense will help you as you type the methods into your code.

The value parameter of the Changing method is the value that is about to be applied to the property, as shown in Example 10-5.

In this method, we’ll supplement the Activity to restrict the length of the ActivityName field in the OnActivityNameChanging method.

Example 10-5. The partial methods as implemented by VB and C#

VB
Private Sub OnActivityNameChanging (ByVal value As String)
  If value.Length > 50 Then
    Throw New InvalidOperationException _
     ("Activity Name must be no longer than 50 characters")
  End If
End Sub
Private Sub OnActivityNameChanged ()

End Sub
C#
partial void OnActivityNameChanging (string value)
{
  if ((value.Length) > 50)
    throw new InvalidOperationException
     ("Activity Name must be no longer than 50 characters");
}
partial void OnActivityNameChanged()
{}

If you look at the signatures of the Changed and Changing methods for the individual properties, you’ll see that the Changed method has no parameters at all and the Changing method receives the new value. Because you are coding within the entity’s class, you have access to the entity, its properties and methods, and its related data. This means you can impact whatever you wish about the Address entity in this business logic.

Using PropertyChanged to Calculate Database-Computed Columns Locally

Here’s an example of taking advantage of these methods. Many databases use computed columns to perform calculations on the fly. An example of this is in Microsoft’s sample database, AdventureWorksLT. The LineTotal column of the SalesOrderDetail table is a computed column. Figure 10-1 shows the column properties in the database. You can see that the Computed Column Specification property formula calculates the LineTotal based on the UnitPrice, UnitPriceDiscount, and OrderQty columns.

The LineTotal column, a computed column in the AdventureWorksLT SalesOrderDetail table

Figure 10-1. The LineTotal column, a computed column in the AdventureWorksLT SalesOrderDetail table

You would likely want to know that value in your application as the order is being created or modified, without depending on a trip to the database to get the LineTotal. Instead, you can create a method or read-only property in the partial class to compute the LineTotal locally, and then call that method anytime the UnitPrice, UnitPriceDiscount, or OrderQty column is changed.

Because LineTotal is a computed column in the database, the value created on the client side will not be sent to the server upon calling SaveChanges. Thanks to the default dynamic command generation capability, that LineTotal value will be replaced by the value computed by the database when you call SaveChanges.

Note

Computed columns are marked as StoreGeneratedValue in the model, just as an identity column is. Therefore, SaveChanges will construct the command to send the updates and return any properties that are StoreGeneratedValues.

The custom method or property gives you the ability to calculate that property locally as needed.

Although this computed property works very well for formulas in which the required values are contained within the same entity, you have to be careful if you are calculating data from related entities. The SalesOrderHeader entity in AdventureWorksLT has a SubTotal property that could be populated by summing up the LineTotal properties of all related SalesOrderDetails. But this assumes that all of the related details have been retrieved, and it may require a quick trip to the database to ensure that this is so. Depending on your application’s architecture this could be a bad assumption to make, so this is something to consider before depending on this type of calculation on the client side.

Customizable Event Handlers

You can override only a few event handlers in your applications:

  • ObjectContext.SavingChanges

  • EntityObject.PropertyChanging

  • EntityObject.PropertyChanged

  • RelatedEnd.AssociationChanged

The ObjectContext.SavingChanges Event

As I mentioned in the preceding chapter, SavingChanges is your only opportunity to validate or affect data before it is persisted to the database. SavingChanges executes just prior to when the SaveChanges method builds the database Insert, Update, and Delete commands. Because this represents a single event for all of the entities in your model, it is possible that you will have quite a lot of code in here, if you need to make some last-minute changes to any or all of the types in your system. Therefore, you’ll want to consider how you organize these validations.

You could perform them per entity type, or you could perform them per EntityState.

You could also build the validators into partial classes for the various entities, and call them from ObjectContext.SavingChanges.

Note

There is, of course, one more EntityState enum that the preceding examples have ignored: Detached. Detached entities don’t exist in the ObjectContext, so there’s no reason to look for them here.

Implementing SavingChanges

Before you can override SavingChanges, you’ll need to extend the partial class for the ObjectContext. You can do this in the entities code file.

Example 10-6 demonstrates overriding the SavingChanges event in the BreakAway model. The handler updates the ModifiedDate property for every contact that is either modified or added. It also sets a default CustomerTypeReference when the contact is a customer. The example first grabs all of the Modified and Added entries without worrying whether they are for entities or relationships. Then, it identifies contacts and updates the AddDate and ModifiedDate fields. Next, it checks for customers and updates their CustomerTypeReference (a necessity pointed out in the preceding chapter) if it hasn’t been assigned yet.

Example 10-6. Setting default values in SavingChanges in VB

VB
Private Sub BAEntities_SavingChanges _
 (ByVal sender As Object, ByVal e As System.EventArgs) _
 Handles Me.SavingChanges

  Dim osm = Me.ObjectStateManager
 'get Added or Modified entries
  For Each entry In osm.GetObjectStateEntries _
   (EntityState.Added Or EntityState.Modified)
    If TypeOf entry.Entity Is Contact Then
      Dim con = CType(entry.Entity, Contact)
      With con
     'only update AddDate if it has not been set yet
      If .AddDate = DateTime.MinValue Then .AddDate = Now
        .ModifiedDate = Now
      End With
    End If

    If TypeOf entry.Entity Is Customer Then
      Dim cust = CType(entry.Entity, Customer)
      With cust
        If cust.CustomerTypeReference.EntityKey Is Nothing Then
          cust.CustomerTypeReference.EntityKey =  _
           New EntityKey _
           ("BAEntities.CustomerTypes", "CustomerTypeID", 1)
        End If
      End With
    End If
  Next
End Sub

In C#, you have to perform a few extra steps to wire up the SavingChanges event handler. You can do this by overriding the OnContextCreated partial method, as shown in Example 10-7.

Example 10-7. Setting default values in SavingChanges in C#

C#
partial void OnContextCreated()
{
  this.SavingChanges += new System.EventHandler(mySavingChanges);
}
public void mySavingChanges(object sender, System.EventArgs e)
{
  var osm = this.ObjectStateManager;
 //get Added | Modified entries;
  foreach (var entry in osm.GetObjectStateEntries
  (EntityState.Added | EntityState.Modified))
  {
    if (entry.Entity is Contact)
    {
      var con = (Contact)entry.Entity;
     //only update AddDate if it has not been set yet
      if (con.AddDate == DateTime.MinValue)
        con.AddDate = DateTime.Now;
      con.ModifiedDate = DateTime.Now;
    }

    if (entry.Entity is Customer)
    {
      var cust = (Customer)entry.Entity;
      if (cust.CustomerTypeReference.EntityKey == null)
        cust.CustomerTypeReference.EntityKey =
         new EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1);
    }
  }
}

One of the validations performed in Examples 10-6 and 10-7 involved setting a default value for a Customer’s CustomerType. In the sample database, the Customer’s CustomerTypeID foreign key is a non-nullable field. In the Entity Framework, although you can set defaults for scalar values, it’s a little more difficult to enforce constraints on EntityReferences or to have them automatically implemented. Taking care of this constraint during SavingChanges by providing a default so that the value is not null is a convenient way to solve the problem. Otherwise, if the CustomerTypeID had been left empty, an exception would be thrown.

The EntityObject.PropertyChanging and EntityObject.PropertyChanged Events

In addition to the Changing and Changed methods for the individual properties of a class, EntityObject has class-level PropertyChanged and PropertyChanging events as well. These two events fire anytime any property in the Address class changes.

The order of the Changing/Changed events

If you implement the class-level events as well as any of the specific property methods, both the method and the event will be hit when the particular property is modified. Here is the order in which the events are hit:

  1. Property-level On[Property]Changing method

  2. Class-level PropertyChanging event

  3. Class-level PropertyChanged event

  4. Property-level On[Property]Changed method

Event parameters

The Sender parameter of the PropertyChanged and PropertyChanging events contains the entity in its current state. You’ll have to cast Sender back to the actual type to access these values. The EventArgs for both events have a PropertyChanged property that is a string that defines the name of the changing/changed property. Unlike the property-level Changing method (e.g., AddressPropertyChanging), the PropertyChanging event does not provide the new value.

Implementing the class-level PropertyChanging and PropertyChanged events

Once again, the place to implement the PropertyChanging and PropertyChanged events is in an entity’s partial class.

In VB, these events are available in the IDE drop-downs. Using the Address class as an example again, in the Address partial class, select Address Events from the Class Name drop-down and then select OnPropertyChanged and OnPropertyChanging from the Method Name drop-down. The event handlers shown in Example 10-8 will automatically be created.

Example 10-8. Implementing PropertyChanged and PropertyChanging in VB

VB
Private Sub Address_PropertyChanged(ByVal sender As Object, _
  ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
  Handles Me.PropertyChanged
   Dim propBeingChanged As String = e.PropertyName
   'add your logic here
End Sub

Private Sub Address_PropertyChanging(ByVal sender As Object, _
  ByVal e As System.ComponentModel.PropertyChangingEventArgs) _
  Handles Me.PropertyChanging

  Dim propBeingChanged As String = e.PropertyName
  'add your logic here
End Sub

In C#, you’ll need to manually wire up the event handlers as you did for SavingChanges. You can do this by adding a constructor to the Partial class, as shown in Example 10-9. The PropertyChanged and Changing events derive from a different .NET namespace: System.ComponentModel, rather than the System.EventHandler that you used for SavingChanges.

Example 10-9. Implementing PropertyChanged and PropertyChanging in C#

C#
public partial class Address
{
 //wire up the events inside the Address class constructor
  public Address()
  {
  this.PropertyChanged +=
        new PropertyChangedEventHandler(Address_PropertyChanged);
  this.PropertyChanging +=
        new PropertyChangingEventHandler(Address_PropertyChanging);
}

 //create the methods that will be used to handle the events
  private void Address_PropertyChanged(object sender,
   System.ComponentModel.PropertyChangedEventArgs e)
  {
    string propBeingChanged = e.PropertyName;
    //add your logic here
  }
  private void Address_PropertyChanging(object sender,
   System.ComponentModel.PropertyChangingEventArgs e)
  {
    string propBeingChanged = e.PropertyName;
    //add your logic here
  }
}

Warning

In C#, if IntelliSense does not help you to build these partial classes, methods, and event handlers you may have forgotten to wrap the class in the namespace of the assembly. Be sure to use the same name as the namespace of the generated code class.

The AssociationChanged Event

Another critical data event exposed through Object Services is AssociationChanged.

Note

There is no AssociationChanging event handler.

You can create an AssociationChanged event for any navigation property of an entity. There is no way to have an overall event to capture all association changes in an ObjectContext.

You’ll need to wire this up manually in both VB and C#. Example 10-10 demonstrates creating an AssociationChanged event handler for the ContactReference property of the Address. In the partial class for the Address, create a method (here it’s called ContactRef_AssociationChanged) to execute the desired logic; then in the class constructor, add code to wire up the event handler to this method.

The implementation is the same for EntityReferences as it is for EntityCollection.

Event arguments

Both the EntityReference and EntityCollection implementations have CollectionChangeEventArgs in their parameters. This argument contains two properties: Action and Element.

The Action property can be one of the CollectionChangeAction enums: Add, Refresh, or Remove.

The Element property returns an object that is the entity on the other end of the relationship being changed. You can cast it back to the appropriate entity type if you want to work with that entity.

Example 10-10 shows an AssociationChanged event handler for the CustomerReference of the Address, followed by the opposite—an AssociationChanged handler for the Addresses property of the Customer.

Example 10-10. Implementing the AssociationChanged event

VB
Imports System.ComponentModel;
Partial Public Class Address

 'wire up the event handler in the class constructor
  Public Sub New()
    AddHandler Me.ContactReference.AssociationChanged, _
               AddressOf ContactRef_AssociationChanged
  End Sub

  the method that will be executed when the event is fired
  Private Sub ContactRef_AssociationChanged _
   (ByVal sender As Object, _
    ByVal e As ComponentModel.CollectionChangeEventArgs)
    Dim act As System.ComponentModel.CollectionChangeAction = e.Action
    Dim custOnOtherEnd = CType(e.Element, Customer)
    'add your logic here
  End Sub
End Class




Partial Public Class Customer

  Public Sub New()
    AddHandler Me.Addresses.AssociationChanged, _
     AddressOf Addresses_AssociationChanged
  End Sub

  Private Sub Addresses_AssociationChanged(ByVal sender As Object, _
   ByVal e As System.ComponentModel.CollectionChangeEventArgs)
    Dim act As CollectionChangeAction = e.Action
    Dim addressOnOtherEnd = CType(e.Element, Address)
    'add your logic here
  End Sub
End Class
C#
using System.ComponentModel;
namespace BAEntities
{
  public partial class Address
  {
    public Address()
    {
      this.CustomerReference.AssociationChanged +=
         new CollectionChangeEventArgs(Add_CustRefChanged);
    }
    private void Add_CustRefChanged
       (object sender,CollectionChangeEventArgs e)
    {
      CollectionChangeAction act  = e.Action;
      Customer custOnOtherEnd = (Customer)e.Element;
      //add your logic here
    }

  public partial class Customer
  {
    public Customer()
    {
      this.Addresses.AssociationChanged +=
         new CollectionChangeEventHandler(Addresses_AssociationChanged);
    }
    Private void Addresses_AssociationChanged
       (object sender, CollectionChangeEventArgs e)
    {
      CollectionChangeAction act  = e.Action;
      Address addOnOtherEnd = (Address)e.Element;
      //add your logic here
    }
  }
}

Other Opportunities for Customization

With partial classes, you can do more than override existing methods and events. You can also create your own class methods or properties.

Custom Properties

Partial classes are useful as a way to work around the limitations of the EDM. An example of such a limitation is the model’s inability to create properties as a result of an expression.

Note

This capability is on the list for the next version of the Entity Framework, which will be part of .NET 4.0/Visual Studio 2010. The feature is currently called DefiningExpressions.

For example, it would be very nice to be able to create a FullName property that is the result of concatenating FirstName and LastName, but the schema of the Entity Framework’s EDM doesn’t support this.

Note

Samples appearing later in the book will also use the FullName and TripDetails custom properties implemented in this section. If you are following along with the book’s walkthroughs, be sure to add these properties to the BreakAway model’s project.

Instead, you can create a FullName property directly in the partial class. Keep in mind that these properties, even if they are not marked as read-only, will not be included in updates or inserts when you call SaveChanges. A ReadOnly FullName property would be appropriate for display purposes. You could then write your code to modify the FirstName and LastName properties, and those would be sent to the database when SaveChanges is called.

Also handy would be a second property that you could use when you need to list full names alphabetically. The properties shown in Example 10-11 are part of the Contact partial class.

Example 10-11. Adding a new property to the Contact partial class

VB
Public ReadOnly Property FullName() As String
  Get
    Return Me.FirstName.Trim & " " & Me.LastName
  End Get
End Property
Public ReadOnly Property FullNameAlpha() As String
  Get
    Return Me.LastName.Trim & ", " & Me.FirstName
  End Get
End Property
C#
public string FullName
{
  get
  {
    return this.FirstName.Trim() + " " + this.LastName;
  }
}
public string FullNameAlpha
{
  get
  {
    return this.LastName.Trim() + ", " + this.FirstName;
  }
}

Note

You cannot use these custom properties in LINQ to Entities or Entity SQL queries, but you can use them in client-side queries, which are just LINQ to Objects queries.

Some properties that you will be able to use in a number of samples as the book progresses are those that display the details of a Reservation. The Reservation entity does not contain much interesting information in its scalar properties. The useful details are in the Trip navigation property (start and end dates, trip cost) and in the Destination property of the Trip (the destination name).

Rather than reconstruct this information over and over again, you can create a property of Reservation that will give this to you already concatenated. The same information is convenient to concatenate from the Trip entity. After adding a TripDetails property to the Trip entity, you can then add a TripDetails property to Reservation that reads the Trip.TripDetails.

The Trip.TripDetails property in Example 10-12 protects itself from an exception if the destination information hasn’t been loaded.

Example 10-12. A custom property to provide a commonly needed concatenation in the BreakAway application

VB
Partial Public Class Trip
  Public ReadOnly Property TripDetails() As String
    Get
      Try
        Return Destination.DestinationName.Trim  _
         & " (" & StartDate.ToShortDateString & "-" _
         & EndDate.ToShortDateString  _
         & ";  " & String.Format("{0:C}", TripCostUSD.Value) & ")"
      Catch ex As Exception
        Return Destination.DestinationName
      End Try
    End Get
  End Property
End Class

Partial Public Class Reservation
  Public ReadOnly Property TripDetails() As String
    Get
      Return Trip.TripDetails
    End Get
  End Property
End Class
C#
using System;
namespace BAGA
{
  public partial class Trip
  {
    public string TripDetails
    {
      get
      {
        try
        {
          return Destination.DestinationName.Trim()
                 + " (" + StartDate.ToShortDateString()
                 + "-" + EndDate.ToShortDateString() + ";  "
                 + string.Format("{0:C}", TripCostUSD.Value) + ")";
        }
        catch (Exception ex)
          {
            return Destination.DestinationName;
          }
        }
      }
    }
  }
  public partial class Reservation
  {
    public string TripDetails
    {
      get
      {
        return Trip.TripDetails;
      }
    }
  }
}

Custom Properties That Perform Calculations on Child Collections

In the BreakAway model, you could create custom read-only properties in the Reservation entity for TotalPaid and PaidinFull that would be calculated based on the sum of the payments for that reservation. As mentioned earlier in the discussion of computed columns, the data would be valid only if you could ensure that all of the payments are accounted for. If there is a chance that some of the payments have not been retrieved from the database, you shouldn’t use this.

Overloading Context and Entity Methods

.NET’s method extensions provide another way to add logic to your entity classes.

As an example, let’s overload an entity’s factory method. These are the Shared/Static methods for each entity that let you quickly create a new entity. The parameter list for these factory methods consists of all of the non-nullable properties in the class.

The entire set of non-nullable properties isn’t always the most desirable list of fields to populate when creating a new class. For example, in the BreakAway model classes, the Contact.CreateContact factory method has the signature shown in Example 10-13.

Example 10-13. Signature of the Contact.CreateContact factory method

VB
Public Shared Function CreateContact _
 (ByVal contactID As Integer, ByVal firstName As String, _
  ByVal lastName As String, ByVal addDate As Date, _
  ByVal modifiedDate As Date) As Contact
C#
public static Contact CreateContact
(int contactID, string firstName, string lastName,
 global::System.DateTime addDate, global::System.DateTime modifiedDate)

In most cases, the ContactID will be 0, and if your SavingChanges event handler takes care of the AddDate and ModifiedDate fields, why be forced to enter them when you create a new Contact? You may also have some of the other information available, which means that after calling CreateContact, you still have to set more properties.

Creating an overload of the method would be very convenient. You can place the new version of the method in the Contact’s partial class. Example 10-14 shows a more useful CreateContact method.

Example 10-14. Overriding the Create factory method

VB
Public Shared Function CreateContact(ByVal firstName As String,  _
     ByVal lastName As String) As Contact
  Dim contact As Contact = New Contact
  contact.FirstName = firstName
  contact.LastName = lastName
  Return contact
End Function
C#
public static Contact CreateContact(string firstName, string lastName)
{
  Contact contact = new Contact();
  contact.FirstName = firstName;
  contact.LastName = lastName;
  return contact;
}

When you call the CreateContact method, two signatures will be available, as shown in Figure 10-2.

The new CreateContact overload as shown by IntelliSense

Figure 10-2. The new CreateContact overload as shown by IntelliSense

As you are using the different methods that the entities inherit from EntityObject or ObjectContext, keep your mind open to the idea of being able to enhance them to suit your purposes.

Partial Classes Are for More Than Just Overriding Existing Methods and Events

You can, of course, create all kinds of new logic in partial classes, whether the logic pertains to properties or to methods.

For example, perhaps you want to perform some validation logic without saving changes, such as supplying default values for entities. You could place methods for this within an entity’s partial class and then call that class as needed. If you like the idea of having validation performed on a number of entities at once, or even on a variety of entity types (as SaveChanges can do), you could place the method in the ObjectContext’s partial class. Keep in mind that only attached entities will be available.

Other than creating your own classes, the partial classes are the primary mechanism in the Entity Framework for adding business logic to entities.

Custom Code Generation

It is possible to override EDM code generation completely so that you can define what the generated classes look like. As you’ll learn in Chapter 19, you will need to follow some rules.

The code generator is based on the System.Data.Entity.Design API, and you can use it directly. A sample code generator is available on the Entity Framework team’s Code Gallery page at http://code.msdn.com/adonetefx/ to get you started.

Creating Common Methods or Properties for All Entities

If you have a method or property that you would like to be able to perform on any entity, rather than adding that method to each entity’s partial class, you can create a common method. Here are a few ways in which you can do that:

  • Place the method into the ObjectContext’s partial class and pass the entity in as a parameter. This will work only for entities that are attached to the ObjectContext.

  • Create an extension method for EntityObject. This will then be an available method for any class that inherits from EntityObject.

  • Build a custom code generator for the model, inserting the logic into each generated class. Better yet, insert a call to this logic so that you can modify the logic without having to rebuild the model.

Summary

In this chapter, you learned how to use partial classes generated by the Entity Framework to insert your own business logic into entity classes and to the class that serves as your context. You can override events, add code to partial methods, and even add completely new methods and properties to the generated classes.

Although there are a lot of opportunities for customizing entities and the ObjectContext, sometimes you will find that these are not enough. The EntityObjects are designed to encapsulate schema and relationships, not behavior. The lack of an opportunity to tap into AssociationChanging when you do have access to AssociationChanged is an obvious example. In many places it would make a big difference in our applications if we could insert more logic, but we’ll have to wait for version 2 of the Entity Framework for this.

If you still want more out of these classes, you should consider using custom classes instead, which we will cover in Chapter 19.

Additionally, in a more traditional architecture, you might want to use the Entity Framework only in a data access layer of your application and then have a way to move data from the entities to your objects and back for updates.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.59.198