Most of the work that you will do in the Entity Framework will involve the objects that are based on the entities in your Entity Data Model (EDM). The Object Services API is the part of the framework that creates and manages these objects. Although you have worked with Object Services in much of the code you wrote in earlier chapters, and you have touched on a variety of its topics along the way, you haven’t yet seen the big picture. The API has a lot of tools that you can access directly to take charge of your entity objects.
This chapter is devoted to giving you a better understanding of the Object Services API: what it is responsible for, what it does under the covers, and some of the ways that you can take advantage of it.
You will learn about how queries are processed and turned into
objects, how these objects are managed during their life cycle, and how
Object Services is responsible for the way entities are related to each
other. You will see how the ObjectQuery
works and how it relates to LINQ to Entities queries under the covers.
This chapter will also give you a better understanding of how Object
Services manages an entity’s state, beyond what you learned in Chapter 5.
As you become more familiar with the purpose, features, and implementation of Object Services, you will be better prepared to solve some of the challenges you will face as you move from using the “drag-and-drop” application-building features that Visual Studio provides to building enterprise applications where you need to have much more control over how all of the pieces of the application interact with one another.
Object Services is at the top of the food chain in the Entity
Framework. The namespace for this API is System.Data.Objects
, and it provides all of
the necessary functionality for generating and interacting with the
objects that are shaped by the conceptual layer and are populated from a
data store.
As shown in Figure 9-1, Object Services
initially processes your LINQ to Entities and ObjectQuery
queries, as well as materializes
the query results into objects.
You can divide the core functionality of Object Services into seven areas:
Query processing
Object materialization
Object management
Object relationship management
Object state management
Database Manipulation Language (DML) command processing
Additional features
In the past few chapters, you saw that there are quite a few ways
to create queries in the Entity Framework. Object Services is used for
all of the query methods except EntityClient
, which uses a lower-level API.
Object Services sits on top of EntityClient
and leverages its
functionality on your behalf.
At a high level, query processing in the Entity Framework involves translating the LINQ or Entity SQL queries into queries that the data store can process. At a lower level, it first parses your query into a command tree of LINQ or Entity SQL query operators and functions, combined with the necessary entities and properties of your model. The command tree is a format that the various providers that have been designed to work with the Entity Framework will be expecting. Next, the provider API (Oracle, SQL Server, MySQL, etc.) transforms this tree into a new expression tree composed of the provider’s operators and functions and the database’s tables and columns. This tree is finally passed to the database.
LINQ to Entities leverages the LINQ parser to begin processing
the query, whereas ObjectQuery
uses
a different parser. After each has gone through its first transition,
they both follow the same path. Let’s take a look at how each query is
turned into the eventual store command.
Store command or native command refers to the command that the data store uses—for example, a T-SQL command for SQL Server.
LINQ starts its journey in the LINQ APIs and is then passed to the Object Services API. When you create a LINQ to Entities query, you are using syntax that is built into Visual Basic and C# that has enhancements that the Entity Framework has added. LINQ converts this query into a LINQ expression tree, which deconstructs the query into its common operators and functions. The LINQ expression tree is then passed to Object Services, which converts the expression tree to a command tree.
The ObjectQuery
class and
the query builder methods that you’ve been using are part of Object
Services. When building a query with ObjectQuery
, you write an Entity SQL
string to express the query. If you use query builder methods, those
methods will build an Entity SQL expression and an ObjectQuery
for you. The ObjectQuery
then passes the Entity SQL
string to the entity client’s parser, and this parser creates a
command tree.
Whether a query began as a LINQ to Entities query or as an
ObjectQuery
with Entity SQL, the
command trees are the same. From this point on, both types of
queries follow the same processing path.
The newly created command tree is still expressed in terms of
the entities in the model’s conceptual layer. So at this point, the
processor uses EDM mappings to transform the terms of the command
tree into the tables, columns, and other objects of the database.
This process might run through the command tree a number of times to
simplify the demands made in the query before it comes up with an
equivalent of the database’s tables and columns. Once this new
version of the tree has been created, it is sent to the store
provider (e.g., SqlClient
), which
will know how to convert the command tree into its native command
text.
Entity Framework provider writers use the common schema of a
command tree to create their functionality for generating SQL from
the command tree. For example, a SqlClient
provider will transform the tree
into T-SQL that SQL Server can execute; an Oracle provider will
transform the tree into a proper PL/SQL command.
Figure 9-2 shows the steps these queries take to get to the data store.
Writing Entity SQL is not always simple. Although the process is familiar to those who already write store commands, it is different enough that it will probably take some time before the syntax rolls naturally from your fingertips. Query builder methods can be quite useful, as the methods are discoverable through IntelliSense and take away some of the pain of remembering the exact syntax.
In Chapter 4,
you built a variety of queries using the CreateQuery
method with an Entity SQL
expression as its parameter. You also used query builder methods.
Examples 9-1 and 9-2 are intended to refresh your
memory.
Example 9-1. CreateQuery with Entity SQL
VB
Dim qStr = "SELECT VALUE c " & _
"FROM PEF.Contacts AS c " & _
"WHERE c.FirstName='Robert'"
Dim contacts = context.CreateQuery(Of Contact)(qStr)
C#
var queryStr = "SELECT VALUE c " +
"FROM PEF.Contacts AS c " +
"WHERE c.FirstName='Robert'";
var contacts = context.CreateQuery<Contact>(queryStr);
Example 9-2. Query builder method with Entity SQL parameters
VB
Dim contacts = context.Contacts _
.Where("it.FirstName = 'Robert'")
C#
var contacts = context.Contacts
.Where("it.FirstName = 'Robert'")
Both sets of code define the same ObjectQuery
, which searches for contacts
whose first name is Robert. Neither will actually return results until
something forces the query to be executed.
The query builder methods may still require that you write part
of the expression, such as the Where
predicate it.FirstName='Robert'
in Example 9-2, but they are
still a great deal easier than using the CreateQuery
method.
Query builder methods are methods of ObjectQuery
. How is it, then, that these
methods are available from context.Contacts
? The classes generated
from the model reveal the answer to this question. The preceding
queries are based on the first model you built and used in Chapters
3 and 4. context
is a variable that represents the
PEF
ObjectContext
, which is the wrapper
class that serves up the EntitySet
s of the various classes in the
model. (In Chapter 3 this was
called ProgrammingEFDB1Entities
,
but in Chapter 4 we
simplified it to PEF
.) Example 9-3 shows the
declaration of this class in the classes generated from the
model.
Example 9-3. Declaration of the ObjectContext class
VB
Partial Public Class PEF
Inherits Global.System.Data.Objects.ObjectContext
C#
public partial class PEF : global::System.Data.Objects.ObjectContext
This class has a property for each EntitySet
—for example, Contacts
. Each of these properties returns
an ObjectQuery(Of T)
/ObjectQuery<T>
of the entity type it
wraps. The Contacts
property
returns an ObjectQuery
of
Contact
entities, as shown in
Example 9-4.
Example 9-4. The ObjectContext.Contacts property
VB
<Global.System.ComponentModel.BrowsableAttribute(false)> _
Public ReadOnly Property Contacts() As _
Global.System.Data.Objects.ObjectQuery(Of Contact)
Get
If (Me._Contacts Is Nothing) Then
Me._Contacts = MyBase.CreateQuery(Of Contact)("[Contacts]")
End If
Return Me._Contacts
End Get
End Property
C#
public global::System.Data.Objects.ObjectQuery<Contact> Contacts
{
get
{
if ((this._Contacts == null))
this._Contacts = base.CreateQuery<Contact>("[Contacts]");
return this._Contacts;
}
}
Because the property returns an ObjectQuery
, it has all of the methods and
properties of an ObjectQuery
,
including the query builder methods: Select
, Where
, GroupBy
, and so forth.
Object Services uses the query builder methods and any
expressions, such as what is contained in a Where
clause, to build an Entity SQL
expression. The result is the same as though you had explicitly
created a Object
and typed in the Entity
SQL yourself. You can then use the expression to create a Query
in the same way you
would use a ObjectQuery
CreateQuery
method.
Query builder methods return an ObjectQuery
, and you can use a LINQ to
Entities method on an ObjectQuery
. Therefore, it’s possible to
compose a query such as the following:
context.Contacts.Where("it.FirstName='Robert'").Take(10)
The first part, context.Contacts.Where("it.FirstName='Robert'")
,
returns an ObjectQuery
. Then, LINQ’s Take
method is appended to that. Take
returns an IQueryable
; thus, the type of the
query that results will be a System.LINQ.IQueryable
—in other words, a
LINQ to Entities query.
You can’t go the other way, though, adding query builder
methods after a LINQ method. For instance, context.Contacts.Take(10)
returns a
System.LINQ.IQueryable
. You can
use query builder methods only on an ObjectQuery
and you cannot append a query
builder method to this IQueryable
without first casting the LINQ query to an ObjectQuery
and then appending the method.
Casting a LINQ to Entities query to ObjectQuery
is beneficial in a number of
scenarios, and you’ll see these as you move forward in this
chapter.
You have already seen some of the members of ObjectQuery
, such as the query builder
methods and the Include
method.
Additional methods and properties are available that will help you
better understand the role of ObjectQuery
. Here are some that you can see
when inspecting an ObjectQuery
in
the debugger.
Figure 9-3
shows an ObjectQuery
in debug mode
with its properties, as well as a way to access the results. Figure 9-4 shows a LINQ to
Entities query in the debugger; as you can see, LINQ to Entities
exposes the results directly, but also contains an ObjectQuery
.
If you want to get to ObjectQuery
properties and methods from a
LINQ to Entities query, you can cast the LINQ to Entities query to
ObjectQuery
.
One very helpful ObjectQuery
method is ToTraceString
, which displays the native
store command that will be created from your query. Figure 9-5 shows some
code that calls ToTraceString
and
the value the method returns at runtime.
Figure 9-5. Viewing the native command that will be generated from an ObjectQuery using the ToTraceString method while debugging
Example 9-5
demonstrates casting a LINQ to Entities query to an ObjectQuery
in order to call the ToTraceString
method.
Example 9-5. Casting a LINQ to Entities query to use ObjectQuery methods such as ToTraceString
VB
Dim contacts = From c In context.Contacts
Where c.FirstName = "Robert"
Dim str = CType(contacts, Objects.ObjectQuery).ToTraceString
C#
var contacts = from c in context.Contacts
where c.FirstName == "Robert"
select c;
var str = ((Objects.ObjectQuery)contacts).ToTraceString();
As with ADO.NET, CommandText
refers to the query string
being passed in for execution. Because of the different ways in
which you can build queries with the Entity Framework, CommandText
is represented in a variety of
ways, as shown in Table 9-1.
Table 9-1. CommandText values of various types of queries
In Chapter 4,
you saw how to build a parameterized query. Any parameters that you
created then will be listed in the ObjectQuery
’s Parameters
property.
The Context
property refers
to the instantiated ObjectContext
from which the ObjectQuery
is being run. The
ObjectContext
not only
coordinates the execution of queries and provides the mechanism for
SavingChanges
back to the data
store, but also plays a much bigger role as the manager of objects
in memory.
So far, the query has been defined but no data retrieval has actually occurred. Query execution occurs when the Entity Framework retrieves the data from the store. Queries can be executed implicitly or explicitly.
In previous chapters, you enumerated over the results of a query
(using VB’s For Each
or C#’s
foreach
). Enumerating over a query
will force a query to execute implicitly. You don’t need to
specifically say “go get the data.” The fact that you are attempting
to work with the query results will cause the Entity Framework to do
that for you.
Another way to force execution is to append the ToList
or ToArray
LINQ method to a query. Example 9-6 appends ToList
to the CreateQuery
method to execute the query
immediately and return a list of Contact
entities.
Example 9-6. Executing a query with ToList
VB
Dim contacts = context.CreateQuery(Of Contact)(queryStr).ToList()
C#
var c2 = context.CreateQuery<Contact>(queryStr).ToList();
A big difference between using ToList
or ToArray
rather than enumerating is that
these methods will force the complete results to be returned all at
once. When enumerating, depending on what you are doing with each
result as you get to it, it may take awhile before you get to the
end of the results. Until that time, the database connection will
remain open.
Object
Query
has an Execute
method, which also forces execution,
but it requires a parameter to define MergeOption
s for the objects that result, as
shown in the following code:
VB
Dim contacts = context.Contacts.Execute(MergeOption.AppendOnly)
C#
var contacts = context.Contacts.Execute(MergeOption.AppendOnly);
Four merge options influence how newly returned objects impact
objects that may already exist in memory. MergeOption
is also a property of the
ObjectQuery
, so you can set the
value directly even when you’re not using the Execute
method.
AppendOnly
is the default,
and it will be used when you don’t set the option directly while
executing queries without the Execute
method. However, with Execute
, you must set this parameter, even
if you just want the AppendOnly
default.
You used Execute
in the
preceding chapter for the Windows Forms data-binding example. Execute
does not return an ObjectQuery
, but rather a type called
ObjectResult
. An ObjectQuery
contains all of the metadata
about the query itself, the query expression, the connection
information, and more, as well as any results (after the query has
been executed). When you use the Execute
method, however, you have only the
results, not the metadata. Using Execute
is beneficial in some scenarios, but
in others, its limitations, such as the fact that you can enumerate
over ObjectResult
s only once, might
be a problem.
Because MergeOption
impacts
what happens with the returned data, its purpose will make more sense
after we have discussed some additional topics. We’ll return to
MergeOption
in
more detail later in this chapter.
By default, ObjectContext
will use the connection string defined in the application’s app.config file that has the same name as
the name of the context’s EntityContainer
. For example, when the
EntityContainer
name is BAEntities
, Object Services will search for
a connection string named BAEntities
in the app.config file. If no matching connection string is found and no
override is provided, an exception will be thrown at runtime. The
exception reads “The specified named connection is either not found in
the configuration, not intended to be used with the EntityClient
provider, or not valid.”
You can override the default behavior in a number of ways. One
way to override the default is to supply a different connection string
name in the constructor of the ObjectContext
. This string needs to be
available in the app.config file
as well. Example 9-7
uses the connection string named connString
to create an ObjectContext
.
Example 9-7. Specifying which EntityConnection string to use for a context
VB
Dim context=new PEF("Name=connString")
C#
var context = new PEF("Name=connString");
You can’t use only the connection string name. You also need
to add "Name="
as part of the
parameter.
Another way to override the default is to supply an EntityConnection
object instead. This would
be the same EntityConnection
that
is used with the EntityClient
provider. By creating an explicit EntityConnection
, you can manipulate that
EntityConnection
prior to
instantiating a context with it. Example 9-8 creates the
EntityConnection
but does not do
anything special with it. You will learn a lot more about manipulating
an EntityConnection
in Chapter 16.
Example 9-8. Explicitly creating a new EntityConnection to use with a context
VB
Dim econn = New EntityConnection("name=connString")
Dim context = New PEF(econn)
C#
var econn = new EntityConnection("name=connString");
var context = new PEF(econn);
The EntityConnection
gives
ObjectContext
three important
pieces of information: metadata, database connection
information, and the name of the ADO.NET data provider.
The metadata, which points to the Conceptual Schema Definition Layer (CSDL), Store Schema Definition Layer (SSDL), and Mapping Schema Layer (MSL) files, provides the context with the location of these files. You can embed them into an assembly or place them somewhere in the file system. The context needs access to the metadata files to begin the process of transforming the query into the store command.
ObjectContext
will pass the
database connection string onto the EntityClient
layer so that it will be able
to connect to the database and execute the command.
The last element of an EntityConnection
string is the name of the
data provider (e.g., System.Data.SqlClient
). This tells the
Entity Framework to which data provider to send the command tree for
part of the query processing.
So, what’s next? You’ve got your ObjectQuery
all set. You know the ObjectQuery
will do all of the work to
create a command tree. Somehow, the command tree gets handed off to
the EntityClient
provider along
with the database connection string provided by the ObjectContext
. If you dig into the Entity
Framework assemblies using Reflector, you will find that the ObjectContext
calls on EntityClient
to do the job of creating the
connection and executing the command on the data store.
As you saw with the EntityClient
queries in Chapter 3, EntityClient
returns an EntityDataReader
, not
objects.
After EntityClient
retrieves
the database results into an EntityDataReader
, it passes the EntityDataReader
back up the stack to Object
Services, which transforms, or materializes, the results into entity
objects. The data in EntityDataReader
is already structured to match the conceptual layer, so it’s just a
matter of those contents being cast to objects. If the query used a
projection and there is no matching entity, the results are materialized
into DbDataRecord
s instead of entity
objects, as you saw in many of the queries you wrote earlier.
Figure 9-6 demonstrates the path a query takes from the command tree to the database and then back to Object Services to be materialized into objects.
ObjectContext
is the core class
in Object Services. It performs a variety of functions:
It provides the connection and metadata information needed to compose and execute queries, as well as to persist data to the data store.
It acts as a caching container for objects in memory, whether
they have been retrieved from the database, created
programmatically, or brought from another ObjectContext
.
It maintains the state information of all of the objects it is managing, as well as retaining the original and current values of all of the objects.
It provides relationship information so that graphs can be created from related objects that it is managing.
You have actually seen the ObjectContext
perform all of these functions
in the code you wrote in previous chapters. Now it’s time to dig a
little deeper.
When objects are returned from queries, ObjectContext
creates pointers to each
entity, in effect, caching references to these entities. ObjectContext
not only keeps track of all of
these entities, but also keeps track of other information regarding
those entities, including their state, their original and current
values, and their relationships to one another.
Objects can be in memory without being managed by the ObjectContext
. That means that although
the object instance exists, the ObjectContext
is not aware of the object.
You can have an EntityObject
in
application memory that is not being tracked by the context, by
doing any one of the following:
Explicitly instruct the ObjectQuery
to return objects without
attaching them to the cache. You can do this by setting ObjectQuery.MergeOption
to the
NoTracking
option.
Use the ObjectContext.Detach
method to
explicitly detach an object from the ObjectContext
.
Create a new object in memory. Unless or until you
explicitly attach or add the object to the ObjectContext
or to an object that is
already in the cache (e.g., adding a Reservation
to a Customer
’s Reservation EntityCollection
property
or adding a Customer
as a
Reservation
’s CustomerReference
), it is not part of
the cache.
Deserialize entities that were serialized. Although the
act of serializing an entity or entities does not detach
entities from their ObjectContext
, the entities that are
in the serialized package will not be attached to an ObjectContext
when they are deserialized.
The EntityState
of an
object that is not in the cache is always Detached
.
Chapters 15 and 17 will provide much more insight
into controlling the ObjectContext
and the effect that
caching has on entities’ relationships and change tracking.
When objects are returned from queries, they are managed by the
ObjectContext
by default. The only
responsibility of an entity object is to know what its current state
is, that is, to know the current values of its properties.
An entity object that is being managed by the ObjectContext
is considered to be
“attached” to that context. This is when the ObjectContext
maintains an ObjectStateEntry
for that entity. You
first learned about the ObjectStateEntry
type in Chapter 5.
The properties of an entity class are the scalar and navigation
properties that define the schema of that entity. For example, the
BreakAway Contact
class has only
the following properties: ContactID
, FirstName
, LastName
, AddDate
, ModifiedDate
, Title
, Customer
, Addresses
, and Lodging
. These are the same properties that
are in the entity in the model.
You can look into the classes to see what the code looks like.
Example 9-9 shows the
Visual Basic code related to the FirstName
scalar property.
Example 9-9. The VB code related to the FirstName scalar property
VB
<Global.System.Data.Objects.DataClasses.EdmScalarPropertyAttribute _
(IsNullable:=false),_
Global.System.Runtime.Serialization.DataMemberAttribute()> _
Public Property FirstName() As String
Get
Return Me._FirstName
End Get
Set
Me.OnFirstNameChanging(value)
Me.ReportPropertyChanging("FirstName")
Me._FirstName = Global.System.Data.Objects.DataClasses. _
StructuralObject.SetValidValue(value, false, 50)
Me.ReportPropertyChanged("FirstName")
Me.OnFirstNameChanged
End Set
End Property
Private _FirstName As String
Partial Private Sub OnFirstNameChanging(ByVal value As String)
End Sub
Partial Private Sub OnFirstNameChanged()
End Sub
The code generator created two events for the FirstName
property: OnFirstNameChanged
and OnFirstNameChanging
. These partial methods
are defined in the class to enable you to add code to handle these
events. You will learn how to extend the classes’ event handling and
other code in Chapter 10.
Each entity class also has a single method whose name begins
with the word Create. These are factory methods
that allow you to create a new instance of that class. The arguments
of the Create
methods take values
for each non-nullable scalar property in the class. For the Contact
class, you may notice that the
Title
parameter is not being used
by the CreateContact
method shown
in Example 9-10. That’s because
Title
is a nullable
property.
Example 9-10. The CreateContact 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
Dim contact As Contact = New Contact
contact.ContactID = contactID
contact.FirstName = firstName
contact.LastName = lastName
contact.AddDate = addDate
contact.ModifiedDate = modifiedDate
Return contact
End Function
C#
public static Contact CreateContact(int contactID,
string firstName, string lastName, global::System.DateTime addDate,
global::System.DateTime modifiedDate)
{
Contact contact = new Contact();
contact.ContactID = contactID;
contact.FirstName = firstName;
contact.LastName = lastName;
contact.AddDate = addDate;
contact.ModifiedDate = modifiedDate;
return contact;
}
Once you have used this function to create a new contact, you can add additional properties in the code.
You won’t find much more when looking in the classes generated from the model. The entity class itself has no properties for keeping track of its state changes. It only maintains the current values of its properties.
The entity does, however, inherit from EntityObject
, and therefore it exposes two
additional properties that come from EntityObject
: EntityKey
and EntityState
.
EntityKey
is a critical class
for keeping track of individual entities. It contains the entity’s
identity value, which could be from a single property, such as
ContactID
, or could be a composite
key that depends on a number of the entity’s properties. Figure 9-7 shows an
EntityKey
for a BreakAway Contact
. It says that this entity belongs to
the BAEntities
container and to the Contacts
EntitySet
, and that its key property is composed of only one
property, ContactID
, whose value is
1
.
The ObjectContext
reads the
EntityKey
information to perform
many of its functions. For example, when the context merges objects,
locates entities in the cache, or creates EntityReference
values. The type information
is not included in the EntityKey
.
Instead, the EntitySetName
indicates to which EntitySet
the
object with this key belongs.
This little class is one of the most important classes in the Entity Framework. It acts as an object’s passport throughout the application’s runtime.
The EntityState
property of
an entity identifies whether the entity is:
An entity was instantiated at runtime and added to the
context. When SaveChanges
is called, Object Services
will create an Insert
command for this entity.
An entity is being managed by the cache and has been
marked for deletion. When SaveChanges
is called, Object
Services will create a Delete
command for this
entity.
The entity has been changed since it was attached to the context.
No changes have been made to the entity since it was attached to the context.
By default, anytime the ObjectContext
performs a query, if any of
the returned objects already exist in the cache the newly returned
copies of those objects are ignored. The EntityKey
s are instrumental in enabling this
to happen. The EntityKey
s of the
objects returned from a query are checked, and if an object with the
same EntityKey
(within the same
EntitySet
; e.g., Contacts
) already exists in the cache, the
existing object is left untouched. You can control this using an
ObjectQuery
property called
MergeOption
,
which was introduced briefly earlier in this chapter. The four
possibilities for MergeOption
are as follows:
AppendOnly
(default)Add only new entities to the cache. Existing entities are not modified.
OverwriteChanges
Replace the current values of existing entities with values coming from the store.
PreserveChanges
Replace original values of existing entities with values coming from the store. The current values are untouched, and therefore any changes the user makes will remain intact. This will make more sense after we discuss state management later in this chapter.
NoTracking
Objects returned by the query will not have their changes
tracked and will not be involved in SaveChanges
. Again, this will make
more sense after we discuss state management.
There are two ways to define MergeOption
s. The first is to use the
MergeOption
method of ObjectQuery
, as shown in the following
code:
VB
Dim contacts = context.CreateQuery(Of Contact)(queryString)
contacts.MergeOption = MergeOption.PreserveChanges
C#
var contacts = context.CreateQuery<Contact>(queryString);
contacts.MergeOption = MergeOption.PreserveChanges;
The second way to define a MergeOption
is as a parameter of Object
, as you saw earlier
in this chapter.Query
.Execute
Remember that you can cast a LINQ to Entities query to an
ObjectQuery
and use ObjectQuery
methods, including MergeOption
, as you did with ToTraceString
earlier in this
chapter.
In Chapter 5, you
learned that ObjectContext
manages
the state information for each of its objects. You were also introduced
to the ObjectStateEntry
classes that
ObjectContext
maintains—one for each entity and
relationship in its cache. Let’s look more closely at the ObjectStateEntry
classes that track the entity
objects.
You can retrieve an ObjectStateEntry
by passing an EntityKey
to the ObjectContext.ObjectStateManager.GetObjectStateEntry
method.
GetObjectStateEntry
has a
sibling method TryGetObjectStateEntry
. In this chapter, you
will get a high-level look at the ObjectStateManager
and ObjectStateEntry
classes. Chapter 15 will dig much
more deeply into these classes.
Debugging the ObjectStateEntry
won’t give you much insight into the object, but you can see that the
ObjectStateEntry
in Figure 9-8 is for the
Contact
whose ContactID
is 6
.
The more interesting information is returned from some of the
methods of the entry: CurrentValues
and OriginalValues
. These methods
return an array of the values for each scalar property. You do need to
know the index position of the property you are seeking; for example,
you can return the original value of FirstName
by calling contactEntry.OriginalValues(3)
in VB or
contactEntry.OriginalValues[3]
in
C#.
Also, metadata is available from the entry, so it is possible to find values by using the property names. This will take a bit more effort, and you’ll learn about navigating around these entries in Chapter 17.
Figures 9-9 and 9-10 use a custom utility to show the
ObjectStateEntry
information for an
entity before and after some changes have been made. I call the utility
the Object State Entry Visualizer and you will actually be writing it
yourself in Chapter 17.
Figure 9-10. The same viewer shown in Figure 9-9, but presenting the changes made to the entity as defined in its ObjectStateEntry
What is most important to understand right now is that CurrentValues
and OriginalValues
are tracked, but the
ObjectContext
maintains this
information.
The ObjectContext
knows about
the state of an object through change tracking. Every entity
implements the IEntityWithChangeTracker
interface. Recall
that the PropertyChanging
and PropertyChanged
events in the model classes
represent part of the change-tracking functionality. When an object’s
property is changed, the IEntityWithChangeTracker
interface reports
this change to the designated ChangeTracker
—that is, the current ObjectContext
, which updates the appropriate
value of that object’s ObjectStateEntry
. For this to work, the
Object
inherits internal functions
from IEntityWithChangeTracker
.
This interface has a public member—the SetChangeTracker
method—that allows you to
identify which ObjectContext
is
responsible for the change tracking. These methods are used
internally, and although they may seem tempting in some scenarios, you
will almost always be better off not using them directly.
Although objects know how to traverse from one to another, it is
the ObjectContext
that binds related
objects together.
This may not be evident, even if you perform a query that explicitly retrieves a graph, such as in the following:
context.Customers.Include("Reservations.Trip") .Include("Reservations.Payments")
Figure 9-11 depicts the graph that results from this query.
In fact, although it may look like your query is shaping the
returned data, the object graph is shaped by the ObjectContext
after the objects have been
materialized and attached to the
context. The ObjectContext
’s ability
to identify and implicitly join related entities is referred to as its
relationship span.
This chapter aims to give you a high-level understanding of relationships. However, relationships and associations are a very interesting topic, and we will cover them much more thoroughly in Chapter 15.
You can explicitly combine related entities in code. Here’s an
example of code that creates a new Reservation
object and then adds it to a
Customer
’s Reservations
property. The Reservations
property is an EntityCollection
, so this code adds the new
Reservation
not
to the Customer
, but to the
collection:
VB
Dim res = Reservation.CreateReservation(0, New Date(2008, 10, 1))
c.Reservations.Add(res)
C#
var res = Reservation.CreateReservation(0, new DateTime(2008, 10, 1));
c.Reservations.Add(res);
However, if you were to perform queries that returned Customer
s and Reservation
s separately, the ObjectContext
would identify those that are
related and make it possible for you to traverse through Customer.Reservations
or Reservation.Customer
with no effort. The
ObjectContext
takes care of that for
you through its relationship span capability.
When poking around the entity classes, you may have noticed that
the EntityCollection
properties, such as
Addresses
and Reservations
, were read-only. Because of the
way ObjectContext
works, you can’t
attach an EntityCollection
directly
to an entity. In other words, if you had a collection of Addresses
that belong to a contact, you can’t
just call Contact.Addresses=myAddressCollection
.
Instead, you must add the Address
entities to the Contact.Addresses
entity collection one at a time using context.Addresses.Add(myAddress)
. There
is also an Attach
method for
connecting related entities.
Chapter 15 is devoted to the ins and outs of relationships in the Entity Framework.
I have mentioned the topic of attaching and detaching objects a
number of times in this chapter. Objects whose changes and
relationships are being managed by the context are considered to be
attached. EntityObject
instances
that are in memory but are not being managed by the context are
considered to be detached, and even have their EntityState
value set to Detached
.
Attaching and detaching can happen implicitly thanks to the internal functionality of the Entity Framework, or explicitly by calling the methods in your code.
You have seen that an object that is attached to an ObjectContext
has its state and its
relationships managed by that context. You also know that an object
that is detached has no state. And you have dealt with many objects in
the coding samples that were automatically attached to the ObjectContext
as the result of executing a
query; you even added an object or two using the Add
and Attach
methods. Now you will look a little
more closely at explicitly attaching and detaching objects.
Use ObjectContext.Add
to
add newly created objects that do not exist in the store. The entity
will get an automatically generated temporary key and its EntityState
will be set to Added
. Therefore, when SaveChanges
is called, it will be clear to
the Entity Framework that this entity needs to be inserted into the
database.
Beware of added entities that are joined to other objects. Object Services will attempt to add the related objects to the database as well. You’ll learn more about this, and how to deal with this behavior when working with a WCF service, in Chapter 14.
To attach an object to a context, use the ObjectContext.Attach
method as shown in
the following code:
VB
context.Attach(myObject)
C#
context.Attach(myObject);
The Attach
method requires
an object that has an existing EntityKey
. An object will have an EntityKey
if it has
come from the data store or if you explicitly create the key. But
these objects that you are attaching are assumed to exist in the
data store. When you call SaveChanges
, the value of the EntityKey
is used to update (or delete)
the appropriate row by finding its matching ID (most often a primary
key) in the appropriate table.
When you attach an entity using the Attach
method, the object’s EntityState
becomes Unchanged
. This means nothing has happened
to the entity since the time it was attached.
What if you have made changes to an entity and then detached
it and attached it again? As stated earlier, the newly attached
entity will be Unchanged
and all
of the changes that you may have made earlier will be lost. This is
expected behavior for the Entity Framework, but to many developers
who are new to working with the Entity Framework, this is surprising
behavior. Remember that the object doesn’t own its state
information; an ObjectContext
does. If you have an object that is being tracked and has changes,
but then you detach the object, the ObjectStateEntry
for that object is
removed from the context. All of the state is gone, including the
original values. Poof!
When you Attach
to a
context, a brand-new ObjectStateEntry
is created. The property
values for the incoming object are used to populate the OriginalValues
and CurrentValues
arrays
of the ObjectStateEntry
.
An object needs an EntityKey
to be change-tracked and to be
involved in relationships. If you need to attach an object that does
not have an EntityKey
, you can
use the AttachTo
method, which
also requires that you indicate to which EntitySet
the object belongs. With the
name of the EntitySet
, the
Context
can dynamically create an
EntityKey
for the object. The following example shows how to use the AttachTo
method, where myContact
is an already instantiated
Contact
entity object:
VB
context.AttachTo("Contacts",myContact)
C#
context.AttachTo("Contacts",myContact);
In some cases, an object may not have an EntityKey
. For example, an EntityKey
is generally an indication that
the object has come from the data store and has some type of a
primary key field. Newly added objects are given temporary EntityKey
s. But what if you want to work
with an object whose data exists in the data store, but you are
creating that object on the fly in memory without actually
retrieving it first? In this case, this object will not have an
EntityKey
by default, and you’ll
need to create one yourself.
You can create an EntityKey
object using an EntitySet
name,
the name of the property that is the identifier, and the value of
that key. As mentioned earlier, it is possible to have composite
keys, so you would construct those by first creating KeyValuePair
s and then adding them to the
EntityKey
.
You may need to create an EntityKey
on the fly if, for example, you
are creating a new Customer
and
you need to identify the CustomerType
for the Customer
. Say, for instance, that
customers in our BreakAway example can be classified as “Standard,”
“Silver,” or “Gold.” A CustomerType
table is in the database and
a CustomerType
entity is in the
model. In some scenarios, you may not have CustomerType
entities already in memory,
and you won’t want to create a new CustomerType
because the Entity Framework
will attempt to add the type to the database. But the business rule
is that all customers start out as Standard customers and the
CustomerTypeID
for Standard
customers is 1
.
The EDM allows you to define default values for scalar
properties but not for navigation properties, so you may find
yourself frequently needing to create a default value for an
EntityReference
just as in this
example. In Chapter 10, you’ll learn
how to define this type of business logic for your entities so
that you don’t need to repeat it frequently in your code.
This is a great scenario for using an EntityKey
, which you can then use for
setting the CustomerTypeReference
of the new Customer
. The simplest
constructor for an EntityKey
takes a qualified EntitySet
name (the EntityContainer
name plus the EntitySet
name), the
name of the property that holds the key, and the value. Example 9-11 shows a new EntityKey
being created for a CustomerType
that is wrapped by the
CustomerType EntitySet
. The new
EntityKey
is then used for the
CustomerTypeReference
of a new
Customer
object.
Example 9-11. Creating a new EntityKey
VB
Dim typeEKey = _
New EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1)
newcust.CustomerTypeReference.EntityKey = typeEKey
C#
var typeEKey =
new EntityKey("BAEntities.CustomerTypes", "CustomerTypeID", 1);
newcust.CustomerTypeReference.EntityKey = typeEKey;
ApplyPropertyChanges
is an
extremely useful method of ObjectContext
because it so handily solves
the problem of using detached objects to perform updates. If you have
an object in the context and a copy of that object that is detached,
you can update the attached object with properties from the detached
object. The method needs the name of the EntitySet
to find the attached entity and
the object that will be used to update the attached entity. The
signature for ApplyPropertyChanges
is as follows:
VB
ApplyPropertyChanges(entitySetName as String, changed as Object)
C#
ApplyPropertyChanges(string entitySetName, Object changed)
You’ll see ApplyPropertyChanges
used in many of the
samples throughout this book where you need to work with detached
entities. For example, you’ll
find this used in both services applications in Chapter 14, and then
again in later chapters on ASP.NET and WCF services.
Here’s how it works. The context looks at the changed object
passed into the second parameter. If that object does not have an
EntityKey
, the context can build
one based on the metadata of the EntitySet
defined in the first parameter.
For example, the Contacts EntitySet
is defined to return Contact
entities, and the Contact
property
that is used for the EntityKey
is
ContactID
. That’s enough
information to construct the EntityKey
if necessary. With the EntityKey
in hand, the context looks for an
attached entity in the Contacts
EntitySet
with a matching EntityKey
. Once that is found, it compares
the scalar values of the attached entity to the detached entity and
updates any of the properties in the attached entity with new values
from the incoming object. As those changes are made, the ObjectStateEntry
and EntityState
are impacted. When it comes time
to call SaveChanges
, the new values
will be sent to the server.
ApplyPropertyChanges
will
update only scalar values. It does not touch navigation properties.
You will see in later chapters how to use ApplyPropertyChanges
in a graph.
Not only is Object Services focused on getting data from the database and managing those objects, it also manages the full life cycle of the objects, including persisting changes back to the database.
You spent a good deal of time learning about the ObjectContext.SaveChanges
method in action
in Chapter 5. This is
an important function of Object Services. Here we’ll take a look at a
few more features of SaveChanges
.
In Chapter 18, you’ll learn about optimistic concurrency and you’ll see when additional values are sent to the database for concurrency checking.
A little-known fact about the SaveChanges
method is that it returns an
integer representing the number of ObjectContext
objects that were
affected.
As you will learn in more detail later in this book, this number
includes not only entity objects, but also relationship objects that
the ObjectContext
maintains.
Therefore, if you create a new entity and then add that entity to the
EntityCollection
of another entity,
a relationship object is created to represent the relationship between
the two entities. Initially, that object has an EntityState
of Added
because it is a new object. If you
move a child entity from one parent to the other, the relationship
object that represented the first relationship is deleted and a new
one is created to reflect the new end (the new parent).
After the SaveChanges
call,
all of the changes will be accepted in the ObjectContext
and every object’s EntityState
will become Unchanged
. So, whether that object is an
entity or a relationship it will be counted in the number returned by
SaveChanges
.
ObjectContext
has one public
event, SavingChanges
, which occurs
when SaveChanges
is called. It is your only
opportunity to validate or impact the entities before the commands are
created.
The code you insert into SavingChanges
will run before the API
performs the actual SaveChanges
method.
In this single location, you can perform validation on all of
the entities that the ObjectContext
is managing. This could
be a little daunting, as you are more likely used to organizing
validation information within individual classes. Unfortunately, with
the Entity Framework, you’ll need to pile this business logic all into
one place, or at least make all of the calls to perform validation in
this one event.
You’ll learn how to implement SavingChanges
in the next chapter.
Data concurrency is the bane of any data access developer trying to answer the question “What happens if more than one person is editing the same record at the same time?”
The more fortunate among us deal with business rules that say “no problem, last one in wins.” In this case, concurrency is not a problem.
More likely, it’s not as simple as that and there is no silver bullet to solve every scenario at once.
Be default, the Entity Framework will take the path of “last one in wins,” meaning that the latest update is applied even if someone else updated the data between the time the user retrieved the data and the time he saved it. You can customize the behavior using a combination of attributes in the EDM and methods from Object Services.
Chapter 18 will deal with this topic in depth, but here is a short overview of the functionality provided.
The Entity Framework uses an optimistic concurrency model, which means it does not lock records in the database, making it possible for others to read and write data in between a user’s retrieval and update.
In the EDM, the scalar properties of an entity have an
attribute called ConcurrencyMode
.
By default, this is set to None
.
In a typical data application, usually particular pieces of data
raise concurrency concerns, not an entire row in a table. When you
set the ConcurrencyMode
of a
particular property to Fixed
,
Object Services will use the value of that property to determine
whether a change in the database row is something about which you
need to be informed.
When SaveChanges
is called,
if any of the flagged values have changed in the database, an
OptimisticConcurrency
exception
will be thrown. Chapter 18 will go into great
detail about handling these exceptions.
Object Services operations performed against the data store,
such as queries or the SaveChanges
method, are transactional by default. You can override the default
behavior using System.Transaction.TransactionScope
,
EntityTransaction
, or one of the
other System.Data.Common.DbTransaction
classes,
such as SqlClient.SqlTransaction
.
EntityTransaction
inherits from
DbTransaction
as well.
Transaction support works only with operations against the store, not with operations against objects.
By default, the last step of SaveChanges
is to accept all changes to the
objects. This means that in the ObjectStateEntry
, the original values are
replaced with the current values and the object’s EntityState
is set to Unchanged
. This is especially important with
respect to values that are generated on the server. This action will
use those returned values as well.
However, when SaveChanges
is
inside your transaction, the changes don’t come back from the server
until you call DbTransaction.Commit
or TransactionScope.Complete
.
Because of this, you need to set AcceptChangesDuringSave
, the SaveChanges
argument, to False
. Additionally, after the Commit
or Complete
is called, you will need to
manually call ObjectContext.AcceptAllChanges
.
You’ll find more information on transactions in Chapter 16.
Object Services’ core features revolve around query processing and managing objects, as you have now seen. However, Object Services works with entity objects in other ways as well. We’ll look at some of the more important of these features.
Data is serialized in order for it to be transmitted across boundaries and processes, most commonly with remote or message-based services.
Entity classes generated from the EDM are extended with the
Serializable
and DataContractAttribute
attributes, as shown in the following code:
<Global.System.Data.Objects.DataClasses.EdmEntityTypeAttribute _ (NamespaceName:="BAEntities", Name:="Contact"), _ Global.System.Runtime.Serialization.DataContractAttribute(), _ Global.System.Serializable()> _ Partial Public Class Contact Inherits Global.System.Data.Objects.DataClasses.EntityObject . . . End Class
System.Serializable
enables
the object to be binary-serialized or XML-serialized. Binary
serialization is used implicitly in ASP.NET, though in some scenarios
you may need to explicitly code the serialization to persist or stream
data. XML serialization is most commonly used to send messages to and
from web services. The DataContractAttribute
enables
serialization for exchanging data with Windows Communication
Foundation (WCF) services.
In addition, EntityKey
s are
serialized along with the object. This means the object can be
transmitted between applications and services, in some cases with very
little effort on the part of the developer.
It is very important to be aware that in version 1 of the
Entity Framework, ObjectContext
, ObjectStateEntry
, and ObjectStateManager
are not serializable.
This is one of the reasons I have emphasized the fact that objects
do not retain their own state information. Without writing your own
custom code, you cannot serialize or transport the change tracking
or state information of your objects. You will learn more about
this, and how to handle state when crossing process boundaries,
first in Chapter 14
and later in Chapters 21 and 22. These chapters deal with web
services and ASP.NET applications.
Anytime you pass an object or a set of objects as a parameter to a web or WCF service operation, the object will automatically be serialized as it is sent to the service. When it receives the serialized data, the service will automatically deserialize the object(s) and be able to work with it right away.
XML serialization is used for ASMX Web Services and can also be used with WCF. WCF more commonly uses data contract serialization, which does serialize into XML, but differently than XML serialization.
Aaron Skonnard compares the two in the MSDN Magazine article “Serialization in Windows Communication Foundation” (http://msdn.microsoft.com/en-us/magazine/cc163569.aspx/).
Whether you are using an ASMX Web Service or WCF, your entities are automatically serialized into XML when they are transmitted between a service operation and a client application.
You are getting only a quick overview of building and consuming web services and WCF services here. Chapter 14 provides detailed walkthroughs of these processes.
In the following example of a WCF service contract, the
GetContact
operation signature
indicates that a ContactID
must
be sent from the client and that a Contact
entity is returned to the
client, as shown in the following code:
VB
<OperationContract()> _
Function GetContact(ByVal contactID As Integer) As Contact
C#
[OperationContract()]
Contact GetContact(int contactID );
In the next code snippet, the function queries the EDM to
retrieve the data, and then returns the Contact
:
VB
Using context As New BreakAwayEntities
Dim contact = From c In Context.Contacts _
Where c.ContactID = contactID
Return contact.FirstOrDefault
End Using
C#
using (BreakAwayEntities context = new BreakAwayEntities())
{
var cust = from c in context.Contacts.Include("Customer")
where c.ContactID == contactID
select c;
return cust.FirstOrDefault();
}
There is no code here for serialization. The act of serialization is an inherent function of the service.
On the client side, again, no explicit deserialization is
occurring. .NET knows the payload is serialized and will
automatically deserialize it to a Customer
object:
Private Sub GetCustFromService() Dim proxy = New BreakAwayCommonService.BreakAwayCommonServiceClient Dim cust = proxy.GetCustomer(21) Console.WriteLine("{0} {1}", cust.FirstName.Trim, cust.LastName) End Sub
In Chapter 14, you will build a WCF client and service and see more regarding how this works.
In an ASP.NET website, you would use binary serialization to
store information in the session cache or in the page’s ViewState
. You can place objects directly
into these caches, and extract them without having to explicitly
serialize them since Object Services handles the serialization
automatically.
Since you are serializing only the objects and not the
context, the state data stored in the ObjectStateEntry
is not included. The
EntityState
of the objects in the
serialized form is Detached
; when
you deserialize the objects they remain in a Detached
state. If you attach them to an
ObjectContext
, whether it’s a new
ObjectContext
or the same one to
which they were previously attached, their state will become
Unchanged
. Your starting point
with those objects becomes the values used when the data was
originally retrieved from the database.
You can also use methods in the System.Runtime.Serialization
namespace to
serialize your objects explicitly. The Entity Framework
documentation has a great sample of serializing objects to a binary
stream and then deserializing them again. This works no differently
than serializing any other types of objects, and therefore is not
specific to the Entity Framework. Look for the topic titled “How To:
Serialize and Deserialize Objects” in the Entity Framework
documentation for more information.
EntityCollection
and ObjectQuery
both implement IListSource
, which enables them to bind to
data-bound controls. Because the objects implement INotifyPropertyChanges
, you can use them in
two-way binding, which means that updates made in the control can be
sent back to the objects automatically.
In Chapter 8,
you wrote a Windows Forms application that bound data to a BindingSource
that in
turn tied the data to various binding controls. You also performed
data binding with WPF objects. In both applications, when updating the
form’s controls those changes were automatically made in the objects.
This occurred thanks to the IListSource
.
ASP.NET data-bound and list controls also support data binding.
Because of the nature of web pages, however, you’ll need to pay
attention to postbacks and their impact on change tracking. You can
bind directly to the DataSource
properties of the controls, or use a client-side EntityDataSource
control. Although LINQDataSource
does support read-only use of
LINQ to Entities queries, it is more closely focused on LINQ to SQL
and doesn’t support everything in LINQ to Entities. Therefore, it’s
best to use EntityDataSource
instead.
In the next chapter, you will focus on using the ASP.NET
EntityDataSource
to build
data-bound web pages. Some of the chapters appearing later in the book
will demonstrate how to use business layers with Windows Forms and
ASP.NET applications.
You can use your own classes in Object Services in two ways. The
simplest way is to inherit from EntityObject
, which tightly binds your class
to the Entity Framework. This is how the default code-generated
classes are defined. In scenarios where the EntityObject
constrains your classes
and architecture too much, you can directly implement IEntityChangeTracker
, IEntityWithKey
, and IEntityWithRelationships
interfaces to take
full advantage of Object Services.
Chapter 19 focuses on building custom classes in the Entity Framework.
The use of custom classes with these interfaces is referred to
as IPOCO, which adds the term
Interface to the Plain Old CLR Objects (POCO)
acronym. POCO describes classes that are not bound to .NET classes.
The Entity Framework does not fully support POCO, but with interfaces,
it is more loosely coupled than when the classes inherit from EntityObject
.
When you implement IEntityWithChangeTracker
, changes to your
objects will be tracked, and SaveChanges
will persist these changes to
the store.
Implementing IEntityWithKey
will ensure that the ObjectContext
manages objects efficiently.
Without this implementation, there will be more overhead and more
resources will be used to perform operations that rely on the
EntityKey
.
Entities that have navigation properties and therefore depend
on associations must implement IEntityWithRelationships
so that Object
Services is able to manage the relationships.
Chapter 19 will further demonstrate techniques for implementing custom classes in the Entity Framework.
In this chapter, you got an overview of the Object Services
features, and now you should have the big picture of what Object
Services is all about. As you can see, it is the core of the Entity
Framework APIs. The only time you will not be using Object Services is
when you work with the EntityClient
directly to retrieve streamed, read-only data.
Except for working with EntityClient
, nearly everything you will learn
in the rest of this book will be dependent on Object Services. As I
noted throughout this chapter, many of the later chapters in this book
will more thoroughly cover the individual topics highlighted
here.
3.144.113.55