Chapter 9. Working with Object Services

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.

Where Does Object Services Fit into the Framework?

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.

Object Services as it relates to the rest of the Entity Framework stack

Figure 9-1. Object Services as it relates to the rest of the Entity Framework stack

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

Query Processing

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.

From Query to Command Tree to SQL

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.

Note

Store command or native command refers to the command that the data store uses—for example, a T-SQL command for SQL Server.

From a LINQ to Entities query to a command tree

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.

From Entity SQL and query builder methods 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.

Note

For the sake of comparison, when you query using EntityClient, its Entity SQL expression is also parsed into a command tree, enters the query path at this stage of the process, and is treated the same as the command trees that were created from LINQ to Entities and ObjectQuery queries.

How EntityClient turns command trees into store commands

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.

How the various query styles get to the data store

Figure 9-2. How the various query styles get to the data store

A Better Understanding of Query Builder Methods

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 and EntitySets

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 EntitySets 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.

From query builder methods to Entity SQL expressions

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 ObjectQuery and typed in the Entity SQL yourself. You can then use the expression to create a ObjectQuery in the same way you would use a CreateQuery method.

Combining LINQ methods and query builder methods

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.

Breaking Apart the ObjectQuery

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.

An ObjectQuery called contacts in the debugger, showing the various properties of ObjectQuery

Figure 9-3. An ObjectQuery called contacts in the debugger, showing the various properties of ObjectQuery

A LINQ to Entities query in the debugger, showing that the LINQ to Entities query contains an ObjectQuery

Figure 9-4. A LINQ to Entities query in the debugger, showing that the LINQ to Entities query 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.

ToTraceString

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.

Viewing the native command that will be generated from an ObjectQuery using the ToTraceString method while debugging

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();

ObjectQuery.CommandText

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

Query method

Query

ObjectQuery.CommandText

ObjectQuery

Context.Contacts

[Contacts]

ObjectQuery with Entity SQL

context.CreateQuery(Of Contact)
("SELECT VALUE c
    FROM PEF.Contacts AS c
    WHERE c.FirstName='Robert'")
SELECT  VALUE c
FROM PEF.Contacts AS c
WHERE c.FirstName='Robert'

Query builder

context.Contacts
.Where("it.FirstName = 'Robert'")
.OrderBy("it.LastName")
SELECT VALUE it
FROM (
SELECT VALUE it
FROM (
[Contacts]
) AS it
WHERE
it.FirstName = 'Robert'
) AS it
ORDER BY
it.LastName

LINQ to Entities

From c In context.Contacts
Where c.FirstName = "Robert"

(empty)

ObjectQuery.Parameters

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.

ObjectQuery.Context

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.

Query Execution with the ToList or ToArray Method

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();

Note

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.

Query Execution with the Execute Method

ObjectQuery has an Execute method, which also forces execution, but it requires a parameter to define MergeOptions 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 ObjectResults 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.

ObjectContext.Connection

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");

Note

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);

Why does the context need the EntityConnection?

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.

Handling Command Execution with EntityClient

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.

Object Materialization

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 DbDataRecords 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.

The EntityClient providing the command execution functions for an ObjectQuery

Figure 9-6. The EntityClient providing the command execution functions for an ObjectQuery

The ObjectContext

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.

ObjectContext Is a Cache for In-Memory Objects

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 are not required to be in the ObjectContext cache

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.

Entity Objects

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.

Note

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.

EntityKey and EntityState

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.

An object’s EntityKey, which includes critical identity information for each object

Figure 9-7. An object’s EntityKey, which includes critical identity information for each object

The EntityState enums

The EntityState property of an entity identifies whether the entity is:

Added

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.

Deleted

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.

Detached

The ObjectContext is not tracking the entity.

Modified

The entity has been changed since it was attached to the context.

Unchanged

No changes have been made to the entity since it was attached to the context.

Merging Results into the Cache

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 EntityKeys are instrumental in enabling this to happen. The EntityKeys 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 MergeOptions. 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 ObjectQuery.Execute, as you saw earlier in this chapter.

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.

State Management and ObjectStateEntry

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.

Note

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 ObjectStateEntry for a Contact whose ContactID is 6

Figure 9-8. The ObjectStateEntry for a 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.

Inspecting and displaying information from an unchanged entity’s ObjectStateEntry

Figure 9-9. Inspecting and displaying information from an unchanged entity’s ObjectStateEntry

The same viewer shown in Figure 9-9, but presenting the changes made to the entity as defined in its ObjectStateEntry

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.

Change Tracking

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.

Relationship Management

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.

A Customer graph including Reservations and other related objects

Figure 9-11. A Customer graph including Reservations and other related objects

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.

Note

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 Customers and Reservations 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.

Attaching and Detaching Objects from the ObjectContext

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.

ObjectContext.Add

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.

Warning

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.

ObjectContext.Attach

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.

ObjectContext.AttachTo

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 EntityKeys. 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 KeyValuePairs and then adding them to the EntityKey.

Creating an EntityKey on the fly

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.

Note

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;

ObjectContext.ApplyPropertyChanges: A Handy Method for Updating Entities

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)

Note

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.

Sending Changes Back to the Database

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.

ObjectContext.SaveChanges

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.

Note

In Chapter 18, you’ll learn about optimistic concurrency and you’ll see when additional values are sent to the database for concurrency checking.

SaveChanges Returns an Integer

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.

Data Validation with the SavingChanges Event

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.

Concurrency Management

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.

Optimistic concurrency

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.

ConcurrencyMode

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.

OptimisticConcurrencyException

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.

Transaction Support

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.

Note

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.

Additional Features

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.

Object Services Supports XML and Binary Serialization

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, EntityKeys 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.

ObjectContext, ObjectStateManager, and ObjectStateEntry are not serializable

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.

Automatic serialization

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 and DataContract serialization

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.

Note

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.

Note

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.

Binary serialization

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.

Serialization and object state

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.

Explicit serialization

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.

Object Services Supports Data Binding

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.

Object Services Supports Custom Classes

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.

Note

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.

IEntityWithChangeTracker

When you implement IEntityWithChangeTracker, changes to your objects will be tracked, and SaveChanges will persist these changes to the store.

IEntityWithKey

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.

IEntityWithRelationships

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.

Summary

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.

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

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