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.
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.
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
{
}
}
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 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.
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.
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
}
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.
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(); } }
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.
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.
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
.
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 StoreGeneratedValue
s.
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.
You can override only a few event handlers in your applications:
ObjectContext.SavingChanges
EntityObject.PropertyChanging
EntityObject.PropertyChanged
RelatedEnd.AssociationChanged
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
.
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.
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 EntityReference
s 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.
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.
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:
Property-level On[Property]Changing
method
Class-level PropertyChanging
event
Class-level PropertyChanged
event
Property-level On[Property]Changed
method
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.
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
}
}
Another critical data event exposed through Object Services is
AssociationChanged
.
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 EntityReference
s as it is for EntityCollection
.
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
}
}
}
With partial classes, you can do more than override existing methods and events. You can also create your own class methods or 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.
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.
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;
}
}
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;
}
}
}
}
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.
.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.
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.
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.
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.
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.
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 EntityObject
s 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.
3.147.59.198