Chapter 6. Understanding the entity lifecycle

 

This chapter covers

  • Understanding entity states
  • Understanding transitions between states
  • Automatic and manual state changes

 

At this point in the book, you have two-thirds of the basics you need to implement Entity Framework–based data access code. You have learned how to create an object model, how to map it against the database using the EDM, how to perform queries to retrieve data, and how to transform data in objects. What you’re still missing is an understanding of the entity lifecycle and how to persist modifications made to an entity into the database. This chapter is about the first part of that: the entity lifecycle.

In this chapter, you’ll learn about the different states an entity can be in and what can be done in each one of them. This is a central subject because the changes you make to an entity affect its state, which in turn affects the way those modifications are later persisted.

Let’s start our discussion by analyzing the lifecycle of an entity and its several states.

6.1. The entity lifecycle

During its lifetime, an entity has a state. Before looking at how to retrieve entity state, let’s take a look at what entity state is. The state is an enum of type System.Data.EntityState that declares the following values:

  • Added—The entity is marked as added.
  • Deleted—The entity is marked as deleted.
  • Modified—The entity has been modified.
  • Unchanged—The entity hasn’t been modified.
  • Detached—The entity isn’t tracked.

What do these states represent? What is the state related to? How can an entity pass from one state to another? Does the state affect the database? To answer these questions, we’ll have to look at the concepts behind the object lifecycle.

6.1.1. Understanding entity state

As explained in chapter 3, the context holds a reference to all objects retrieved from the database. What’s more important for our discussion, the context holds the state of an entity and maintains modifications made to the properties of the entity. This feature is known as change tracking (or object tracking).

If you create an entity outside the context, its status is Detached because the context can’t track it.

 

Note

More precisely, an entity outside the context has no state. We referred to Detached status because in Entity Framework 1.0 that’s the state an entity is in when it’s not tracked by a context.

 

If you attach an entity to the context (more on this in section 6.2), it will move to the Unchanged state. If you retrieve an entity from the database and then get rid of the context, the entity state will be Detached. If you retrieve an entity, dispose of the context, and create a new context and add the entity to it, the entity will be in the Added state (again, this will be explained in section 6.2). As these examples make clear, the state is the relationship between the entity and the context that holds a reference to it.

At first, you might have thought that the entity state reflected the state of the entity compared to the corresponding row in the database, but it’s not like that. The context is the application database; this is why entities relate their state to the context and not to the database.

 

Note

The fact that the state of an entity relates to the context instead of the database often confuses people when they first approach Entity Framework. It’s important to understand this point.

 

Let’s look at an example. Suppose you have two methods in the OrderIT web service: the first retrieves data about a customer, and the second updates the data. The client uses the first method to retrieve the data and display it in a form. In this method, you create a context to retrieve the data and then destroy the context.

The user modifies some of the data, such as the shipping address, and then saves it, invoking the second method and passing in the updated customer data. In the web-service method, you create a new context and attach the entity to it. The new context can’t know what data has been modified by the user (if any) unless it goes to the database and performs a comparison.

Going to the database would be too expensive, so it’s not performed automatically. This means that when the entity is attached to the context, it enters the Unchanged state, because the context knows nothing about modifications. If the entity state reflected the actual state against the database, it would be Modified, but that’s not the case. This example is simple, but it explains why the state indicates the relationship between the entity and the context, and not the entity and the database.

Naturally, the state of an entity affects the way it’s persisted. Not surprisingly, when you persist entities, those in the Added, Modified, or Deleted state are saved using the INSERT, UPDATE, or DELETE command in the database.

6.1.2. How entity state affects the database

The state represents not only the state of the entity in the context, but also how the data will be persisted into the database. For each state, there is a corresponding SQL command.

An entity in Added state is persisted using an INSERT command to create a new row in the mapped tables. An entity that’s Modified already has a correspondence with a table row, so it will be persisted using an UPDATE command. An entity in Deleted state has a correspondence with a table row, but it triggers a DELETE instead of an UPDATE.

The Detached and Unchanged states have no impact on the database: a detached entity isn’t tracked by a context, so it can’t be persisted, whereas an unchanged one has no modifications to persist.

During its lifetime, an entity can change its state. Let’s look at how this happens and what API you can to use to make it happen manually.

6.1.3. State changes in the entity lifecycle

The state of an entity is sometimes set automatically by the context and can also be modified manually by the developer. Although all the combinations of switches from one state to another are possible, some of them are pointless. For instance, there’s no point in moving an Added entity to the Deleted state, or vice versa. Figure 6.1 shows all the states and how an entity can pass from one to another.

Figure 6.1. The different entity states and the context methods that make them change

The figure is quite clear. The only thing we need to add is that all the methods shown belong to the ObjectContext or to the ObjectSet<T> classes. Now, let’s look more closely at the different states.

Detached State

When an entity is Detached, it isn’t bound to the context, so its state isn’t tracked. It can be disposed of, modified, used in combination with other classes, or used in any other way you might need. Because there is no context tracking it, it has no meaning to Entity Framework.

As a consequence, Detached is the default state of a newly created entity (one created using the new constructor) because the context can’t track the creation of any object in your code. This is true even if you instantiate the entity inside a using block of the context. Detached is even the state of entities retrieved from the database when tracking is disabled.

Unchanged State

When an entity is Unchanged, it’s bound to the context but it hasn’t been modified. By default, an entity retrieved from the database is in this state.

When an entity is attached to the context (with the Attach method), it similarly is in the Unchanged state. The context can’t track changes to objects that it doesn’t reference, so when they’re attached it assumes they’re Unchanged.

Added State

When an entity is in the Added state, you have few options. In fact, you can only detach it from the context using Detach.

Naturally, even if you modify some property, the state remains Added, because moving it to Modified, Unchanged, or Deleted would be nonsense—it’s a new entity and has no correspondence with a row in the database. This is a fundamental prerequisite for being in one of those states (but this rule isn’t enforced by the context).

Modified State

When an entity is Modified, that means it was in Unchanged state and then some property was changed.

After an entity enters the Modified state, it can move to the Detached or Deleted state, but it can’t roll back to the Unchanged state even if you manually restore the original values (unless you detach and reattach the entity to the context). It can’t even be changed to Added (unless you detach and add the entity to the context, because a row with this ID already exists in the database, and you would get a runtime exception when persisting it.

 

Note

The preceding rules can be overridden using an API we’ll discuss in section 6.2.6.

 

Deleted State

An entity enters the Deleted state because it was Unchanged or Modified and then the DeleteObject method was used. This is the most restrictive state, because it’s pointless changing from this state to any other value but Detached.

That covers what each state means and how an entity can have its status changed. Now, let’s take the methods that are responsible for this process and put them into action.

6.2. Managing entity state

The change from the Unchanged to the Modified state is the only one that’s automatically handled by the context. All other changes must be made explicitly using proper methods:

  • AddObject—Adds an entity to the context in the Added state
  • Attach—Attaches an entity to the context in the Unchanged state
  • ApplyCurrentValues and ApplyOriginalValues—Change the state to Modified, comparing the tracked entity with another one
  • DeleteObject—Marks an entity as Deleted
  • AcceptAllChanges—Marks all entities as Unchanged
  • ChangeState and ChangeObjectState—Change an entity from one state to another without any restrictions (except for Detached)
  • Detach—Removes an entity from the context

These methods are exposed by the ObjectContext and ObjectSet<T> classes, with the exception of AttachTo, which is defined only by the context, and ChangeState, which is defined by the state manager (we’ll look at this component shortly). The Object-Set<T> methods internally invoke the context methods, so there’s no difference between them.

In the first release of Entity Framework, only the ObjectContext methods existed. The interface for these methods is muddy, so the team added the new ones to ObjectSet<T>, improving usability and simplifying code readability. Because of these advantages, we strongly recommend using the ObjectSet<T> methods and consider the context methods obsolete.

Let’s look at each of these methods, starting with the one that adds an entity to the context.

6.2.1. The AddObject method

AddObject allows you to add an entity to the context in the Added state. When the entity is added to the context, it’s added to those tracked by the context for modifications. When the persistence process is triggered, the context saves the object as a new row in the tables using INSERT commands. In the OrderIT example, persisting an order will cause an INSERT into the Order table, whereas persisting a shirt will cause an INSERT into the Product and the Shirt tables.

Let’s look at the code. We’ll first analyze the context method, and then we’ll look at the entity-set method so that you understand why a new API was desirable.

The AddObject context method receives two arguments, the entity set name and the entity:

C#

public void AddObject(string entitySetName, object entity)

VB

Public Sub AddObject(ByVal entitySetName As String,
  ByVal entity As Object)

There are at least two weaknesses in this code. First, the entity set name is passed as a string. If you type it incorrectly, you’ll get an exception only at runtime. Here’s an example:

C#

ctx.AddObject("Companis", new Customer() { ... });

VB

ctx.AddObject("Companis", New Customer() With { ... })

Second, the entity argument is of type Object, meaning you can potentially pass any CLR object and receive an exception only at runtime if the object isn’t correct. Here’s an example that compiles, despite having the wrong object and wrong entity set name. It produces a runtime exception:

C#

ctx.AddObject("Companis", String.Empty);

VB

ctx.AddObject("Companis", String.Empty)

The following listing shows the correct code.

Listing 6.1. Adding an entity to the context via the ObjectContext method

C#

ctx.AddObject("Companies", new Customer() { ... });

VB

ctx.AddObject("Companies", New Customer() With { ... })

In the strong-typing era, such an API is unbearable. To overcome this bad design, the Entity Framework team introduced an equivalent API in the entity set interface. It accepts as input only the object that needs to be added:

C#

public void AddObject(TEntity entity)

VB

Public Sub AddObject(ByVal entity As TEntity)

TEntity is the type of the entity maintained by the entity set (remember that an entity set is an instance of a type implementing the IObjectSet<T> interface). Due to strong typing, you have no chance to pass an object of the incorrect type to the method. Furthermore, because the entity set knows its name, there’s no need to specify it.

Listing 6.2 shows the code that uses the new method.

Listing 6.2. Adding an entity to the context with the ObjectSet<T> class method

C#

ctx.Companies.AddObject(new Product() { ... });
ctx.Companies.AddObject(new Customer() { ... });

VB

ctx.Companies.AddObject(New Product() With { ... })
ctx.Companies.AddObject(New Customer() With { ... })

In each case, the first line doesn’t compile; Product isn’t allowed. The second line compiles, and Customer inherits from Company.

Now, look at listings 6.1 and 6.2. Didn’t you fall in love with the new syntax?

Adding an entity to the context using the method exposed by the entity set property of the context class is pretty straightforward. Later in the chapter, we’ll deal with relationships, and you’ll discover there are subtleties that can complicate your life.

Adding an entity to the context is useful when you need to persist the object as a new row in the database. But when the object already has a correspondence with a row in the database, it must be persisted using an UPDATE command. The flow changes, and you have to use the Attach method.

6.2.2. The Attach method

The Attach method attaches an object to the context in an Unchanged state. When the entity is attached to the context, it’s tracked by the context for modifications made to scalar properties.

In a real-world application, there are plenty of situations where an entity needs to be attached. Web applications and web services are typical examples. Let’s go back to the example from section 6.1, where a customer updates its data. In the method that retrieves the customer, you create a context, perform a query, return the object to the client, and then dispose of the context. In the update method, you create a brand-new context and then attach the customer to it. Finally, you persist the customer. (There’s also something else to do, as you’ll discover later in this chapter.)

Let’s get back to the Attach method. The Attach context method receives two arguments: the entity-set name and the entity. This method suffers from the same limitations of AddObject and so has become obsolete. In its place, you can use the Attach entity-set method, which needs only the object to be attached, as you can see in the next listing.

Listing 6.3. Attaching an entity with the Attach method

C#

var c = new Customer { CompanyId = 1 };
...
ctx.Companies.Attach(c);

VB

Dim c as New Customer With { .CompanyId = 1 }
...
ctx.Companies.Attach(c)

You attach an object to the context because you want its data to be updated (or deleted, as you’ll see later) in the database. The object must have a correspondence with a row in the database, and this correspondence is identified by the primary-key properties and columns.

 

Note

As you have learned before, the context doesn’t go to the database to check whether the object’s primary key has a correspondence with a database row.

 

When you attach an object to the context, the primary-key columns must be set, or you’ll get an InvalidOperationException at runtime. Furthermore, during the persistence phase, if the UPDATE command doesn’t affect any row, the context throws an exception.

Because an attached entity goes to the Unchanged state, you must find a way to mark it as Modified to persist it in the database. The next methods we’ll discuss can do this.

6.2.3. The ApplyCurrentValues and ApplyOriginalValues methods

In our customer web-service example, when you have attached the object, you need to persist it. The problem is that after it’s attached, it’s in Unchanged state, so you need to find a way to change it to Modified state. The simplest way is to query the database for the latest data, and compare it with the input entity.

You know that the object context keeps a reference to each entity read from the database or that is added through the AddObject or Attach method. What we haven’t mentioned is that it keeps in memory both the original values and the current values of the scalar properties when the entity is bound to the context (you’ll learn more about this in section 6.3).

The ApplyOriginalValues method takes an entity as input (the one coming from the database, in this case). The method then retrieves in the context’s memory an object of the same type and with the same key as the input entity (we’ll refer to this entity as the context entity). Finally, the method copies the values of the input entity’s scalar properties into the original value of the context entity’s scalar properties. At this point, the scalar properties’ original values held in the context contain data from the database, whereas the scalar properties’ current values held in the context contain the values of the entity from the web service. If original values are different from current values, the entity is set to Modified state; otherwise it remains Unchanged.

You can follow the opposite path too. Instead of attaching the entity and querying the database, you can query the database and then apply modifications from the web service’s entity. This is what the ApplyCurrentValues method does. It takes an entity as input (the one from the web service, in this case). The method then retrieves in the context’s memory an object of the same type and with the same key as the input entity (again, we’ll call this the context entity). Finally, the method copies the values of the input entity’s scalar properties into the current value of the context entity’s scalar properties. At this point, the current values held in the context contain data from the web service entity, and the original values are the values from the database. If they are different, the entity is set to Modified state; otherwise it remains Unchanged.

When persistence is triggered, if the entity is in Modified state, it’s persisted using UPDATE commands.

Like the previous methods we’ve discussed, the ApplyOriginalValues and Apply-CurrentValues methods belong to the ObjectContext and ObjectSet<T> classes, and we recommend using those exposed by the latter, as shown here.

Listing 6.4. Changing state with ApplyOriginalValues and ApplyCurrentValues

C#

Dim entityFromDb = GetEntityFromDb(entityFromService.CompanyId);
ctx.Companies.Attach(entityFromService);
ctx.Companies.ApplyOriginalValues(entityFromDb);

ctx.Companies.First(c => c.CompanyId == entityFromService.CompanyId);
ctx.Companies.ApplyCurrentValues(entityFromService);

VB

Dim entityFromDb = GetEntityFromDb(entityFromService.CompanyId)
ctx.Companies.Attach(entityFromService)
ctx.Companies.ApplyOriginalValues(entityFromDb)

ctx.Companies.First(Function(c) c.CompanyId = entityFromService.CompanyId)
ctx.Companies.ApplyCurrentValues(entityFromService)

You must be aware of a little trap here. Both methods only care about scalar and complex properties of the input entity. If a scalar property in an associated entity changes, or if a new line is added, removed, or changed in an associated collection, it won’t be detected.

The other persistence operation in a database is DELETE. The DeleteObject method marks an entity for deletion.

6.2.4. The DeleteObject method

The DeleteObject method marks an entity as Deleted. The only caveat that you have to keep in mind is that the entity passed to this method must be attached to the context. The object must come from a query or have been attached to the context using the Attach method.

If the object isn’t found in the context, an InvalidOperationException is thrown with this message: “The object cannot be deleted because it was not found in the ObjectStateManager.”

The next listing shows how easy it is to use the DeleteObject method exposed by the ObjectSet<T> class.

Listing 6.5. Deleting an entity

When DeleteObject is invoked, the entity isn’t deleted from the context; it’s marked as deleted. When persistence is triggered, the entity is removed from the context, and DELETE commands are issued to delete it from the database.

The next method we’ll explore is used to commit entity modification inside the context.

6.2.5. The AcceptAllChanges method

The AcceptAllChanges method takes all entities that are in Added and Modified states and marks them as Unchanged. It then detaches entities in Deleted state and eventually updates the ObjectStateManager entries (more on this in section 6.3).

AcceptAllChanges is exposed only by the ObjectContext and doesn’t have a counterpart in the ObjectSet<T> class. That’s why you need to use this code:

C#

ctx.AcceptAllChanges();

VB

ctx.AcceptAllChanges()

The methods you’ve seen so far are very strict. They change the entity state to a specific value. The next methods we’ll look at are different because they give you the freedom to switch to any state.

6.2.6. The ChangeState and ChangeObjectState methods

The ChangeState and ChangeObjectState methods are the most flexible state-changing methods. They allow you to change the state of an entity to any other possible state (except Detached). When working with one entity, these methods are useful. But their importance grows when dealing with complex object graphs, as you’ll discover later in this chapter.

ChangeState is exposed by the ObjectStateEntry class, whereas ChangeObject-State is exposed by the ObjectStateManager class (more on these classes in section 6.3). ChangeState only needs the new state, because the ObjectStateEntry instance already refers to an entity. ChangeObjectState accepts the entity and the new state as arguments. Both methods are shown in the following listing.

Listing 6.6. Changing object state using ChangeObjectState and ChangeState

C#

var osm = ctx.ObjectStateManager;
osm.ChangeObjectState(entity, EntityState.Unchanged);
osm.GetObjectStateEntry(entity).ChangeState(EntityState.Unchanged);

VB

Dim osm = ctx.ObjectStateManager
osm.ChangeObjectState(entity, EntityState.Unchanged)
osm.GetObjectStateEntry(entity).ChangeState(EntityState.Unchanged)

These methods don’t always physically change the entity state; sometimes they resort to using the previous methods. For instance, moving an entity to the Unchanged state means calling the AcceptChanges method of the ObjectStateEntry class (AcceptChanges is internally invoked by the AcceptAllChanges method we discussed in section 6.2.5). In contrast, changing the state from Unchanged to Added means changing the state.

Sometimes you don’t need the entities to be persisted or to be tracked for modification by the context. In that case, you can remove the entities from the context by detaching them.

6.2.7. The Detach method

The Detach method removes the entity from the list of entities tracked by the context. Whatever the state of the entity, it becomes Detached, but entities referenced by the detached one aren’t detached.

Invoking this method is pretty simple, as you see in listing 6.7, because it accepts only the entity that must be detached. (We show only the entity set method, which is the recommended one.)

Listing 6.7. Detaching an entity

C#

ctx.Companies.Detach(c);

VB

ctx.Companies.Detach(c)

The precondition for successful detaching is that the entity must already be attached to the context. If that’s not the case, you’ll get an InvalidOperationException with the message, “The object cannot be detached because it is not attached to the Object-StateManager.”

You’ve now learned how to move an entity from one state to another, but there’s still one missing piece. So far, we’ve said that the context is able to track modifications to the objects it references. This is true, but only to a certain extent. The truth is that the context delegates change-tracking management to another inner component: ObjectStateManager.

6.3. Managing change tracking with ObjectStateManager

The ObjectStateManager component (state manager from now on) is one of Entity Framework’s hidden gems. It’s responsible for everything related to object tracking in the context:

  • When you add, attach to, or delete an entity from the context, you actually do that against the state manager.
  • When we say that the context keeps an in-memory collection of all entities read from the database, it’s the state manager that holds this data.
  • When the context performs an identity-map check (discussed in chapter 3), it’s really the state manager that performs the check.
  • When we say that the context keeps track of relationships between entities, it’s the state manager that keeps track of everything.

 

Note

Because ObjectContext wraps the state manager, it’s still valid to say that the context performs the tracking.

 

Tracking changes to entities is only one of the state manager’s tasks. It also provides APIs for retrieving entity state and manipulating it.

The state manager isn’t directly accessible. Because it’s an inner component of the context, it’s exposed as a property, named ObjectStateManager, of the ObjectContext class. This is the code you use to access the state manager:

C#

var osm = ctx.ObjectStateManager;

VB

Dim osm = ctx.ObjectStateManager

 

Note

For brevity, we’ll use the variable osm to identify the state manager in the code.

 

The context is responsible for the lifecycle of the state manager. It handles its instantiation and disposal, and there’s absolutely nothing you can do to change this behavior (nor is there any reason why you should).

Now that you know what the purpose of the state manager is, let’s go deeper and look at how it accomplishes its tasks. Let’s first investigate what data it manages.

6.3.1. The ObjectStateEntry class

When you query the state manager to retrieve an entity tracked by the context, it replies with an ObjectStateEntry object (entry from now on). This object contains more than the simple tracked entity. It holds data that fully represents the entity and its history inside the state manager. It also holds the relationships between the entity and other entities it references by navigation properties. More specifically, it exposes two types of members: properties and methods that reveal data about the entry (shown in table 6.1) and methods that manipulate entry data (discussed in section 6.3.3).

Table 6.1. Members exposed by the ObjectStateEntry class that give access to data in the entry

Member

Description

Entity property Entity tracked by the state manager
EntityKey property Key of the entity
EntitySet property Entity set that the entity belongs to
EntityState property State of the entity
OriginalValues property Value of each entity property when it was attached
CurrentValues property Current value of each entity property
GetModifiedProperties method Properties modified since the entity was tracked
IsRelationship property Flag that specifies whether the entry contains data about an entity or a relationship

This is a lot of information. The most important members for you are likely EntityState, OriginalValues, and CurrentValues; the others are there mainly because Entity Framework itself is their first consumer.

 

Note

OriginalValues and CurrentValues are of type DbDataRecord.

 

ObjectStateEntry is an abstract class that acts as a base class for two other classes: EntityEntry and RelationshipEntry. They’re internal classes, so you’ll never work with them directly. As their names suggest, EntityEntry contains data about an entity, and RelationshipEntry contains information about a relationship between entities. EntityEntry needs no explanation, but we’ll get back to RelationshipEntry in section 6.3.5.

The EntityKey property is vital because it represents the key of the entity inside the state manager. The EntityKey concept is a little bit tricky, so it’s worth looking into it.

Understanding how the State Manager Identifies an Object by its Key

The EntityKey property is what the state manager uses to ensure that only one entity of a given type with a given ID is tracked. The identity-map check is performed by inspecting the entity’s EntityKey property and not the entity’s key properties. EntityKey contains two important properties: the entity set and the values of the properties that compose the entity’s primary key.

When you add an object to the context, it’s added to the state manager with a temporary entity key, because Entity Framework knows it must persist the object as a new row. This temporary key isn’t evaluated by the identity-map check, so if you add the another object of the same type and with the same ID, it’s added with another temporary key. When persistence is triggered, two INSERT commands are issued.

If the row ID is autogenerated on the database (as it is in OrderIT), persistence works fine. If you use natural keys (such as the SSN), the persistence process will throw a duplicate-key exception because the second INSERT command uses the same ID, causing a primary-key violation on the database.

When you attach an entity, the state manager automatically creates an EntityKey object and saves it in the entry created for the entity. This EntityKey object isn’t temporary and is used by the identity-map check.

 

Note

When you call ChangeState or ChangeObjectState to change the state from Added to Unchanged or Deleted, the EntityKey is regenerated. The new EntityKey isn’t temporary and is filled with the key properties of the entity. If there’s already an entity with that key, the state change triggers an exception.

 

Not only does ObjectStateEntry contain data, but it also incorporates behavior. It allows you to change the state of an entity and override its original and current values. In the rest of this section and chapter, we’ll look at how to use the power of ObjectStateEntry to solve problems.

The only way to obtain an ObjectStateEntry instance is to query the state manager. In the next section, you’ll see how to do this and why this is a great feature.

6.3.2. Retrieving entries

You know perfectly well whether you’re adding, attaching, or deleting entities, so why would you ever need to query the context for entity state? There are two situations where this is useful: first, Entity Framework itself needs to query object state; and second, you may want to report the entity state in some sort of generic logging or other scenario.

Suppose you want to log each persistence operation triggered by your application. One way to do this might be to create an extension method that performs an attach, add, or delete and then adds an entry to the logging storage. This implementation is naive because you might need to halt the persistence process for some reason, and you’d end up logging operations that hadn’t happened. (Yes, you could roll back the logging, but that’s not the point here.)

Another approach might be subscribing to the SavingChanges event, which is triggered before the persistence process begins (SaveChanges), retrieving entities in the Added, Modified, and Deleted states, and writing entries in the log. This solution, shown in the next listing, better serves your needs; it doesn’t even require you to create and use extension methods.

Listing 6.8. Logging persisted objects

The first step is hooking up the SavingChanges event . Next, in the handler, you retrieve all entries in a specific state by using the ObjectStateManager class’s GetObjectStateEntries method . It accepts an EntityState parameter representing the state to look for, and it returns a collection of all entries in that state. If you need entries in different states, you can combine them using the flag syntax. Eventually, you invoke the logger method to write the entry .

Often, you’ll only need to retrieve a single entry. GetObjectStateEntries is unusable in such scenario. What you need is another method that lets you pass an entity and get the corresponding state-manager entry. The state manager has a method that serves this purpose.

Retrieving a Single Entry

To retrieve the entry related to a single entity, you can use the GetObjectStateEntry method, passing the entity as argument, as shown here:

Listing 6.9. Retrieving an entry from the ObjectStateManager

C#

var entry = osm.GetObjectStateEntry(entity);

VB

Dim entry = osm.GetObjectStateEntry(entity)

The input entity must have the key properties set, because when the state manager tries to retrieve the entry, it creates an EntityKey using them and performs a lookup. If no entry contains this EntityKey, the method throws an InvalidOperationException with the message, “The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object of type ‘type’.”

To avoid the exception, you can use TryGetObjectStateEntry. It performs the same task as GetObjectStateEntry; but following the design guidelines of the .NET Framework, this method accepts the entity and an output parameter representing the entry found, and it returns a Boolean value indicating whether the entry was found. Should it return false, the output parameter is null. The following listing contains code using this method.

Listing 6.10. Safely retrieving an entry from ObjectStateManager

C#

ObjectStateEntry entry;
var found = osm.TryGetObjectStateEntry(c, out entry);

VB

Dim entry As ObjectStateEntry
Dim found = osm.TryGetObjectStateEntry(c, entry)

With the ObjectStateEntry class, you can modify the state of the entity, as you’ve seen before, by using the ChangeState method. But that’s not your only option. In the next section, you’ll discover other methods that allow you to change entity state.

6.3.3. Modifying entity state from the entry

When you have the entry, you can modify the state of the related entity because the context methods we looked at earlier in this chapter internally invoke the methods in the ObjectStateEntry class. These methods are listed in table 6.2.

Table 6.2. Methods exposed by the ObjectStateEntry class that modify entity state

Method

Description

Delete Marks the entity as deleted. This method is also invoked by DeleteObject and ChangeState when moving to the Deleted state.
SetModified Marks the entity and all of its properties as Modified. This method is internally invoked by ChangeState when moving to the Modified state.
SetModifiedProperty Marks a property as Modified, and consequently marks the entity too.
AcceptChanges Changes the entity’s state to Unchanged and overrides the original values of the entry with the current ones.
ChangeState Changes the entity’s state to the input value.

 

Note

Technically, talking about entity state is incorrect. The actual state of an entity is the state of its entry in the state manager. However, the term entity state is intuitive and more commonly understood than entry state, so we’ll continue using it.

 

These methods are pretty simple to use because most of them don’t accept any parameters. The only exceptions are SetModifiedProperty, which accepts the name of the property to set as Modified, and ChangeState, which accepts the new state the entity should be changed to.

Previously we said that the only state change that’s automatically performed by the state manager is from Unchanged to Modified when you modify a property, but we said that it’s not always the case. In the next section, we’ll dig deep into the object-tracking mechanism, discuss its internals, and see how to avoid its traps.

6.3.4. Understanding object tracking

Technically speaking, the state manager can’t monitor modifications to properties inside an entity; it’s the entity that notifies the state manager when a change is made. This notification mechanism isn’t always in place—it depends on how you instantiate the entity. You can create the following kinds of entities:

  • POCO entity not wrapped by a proxy (a plain entity)
  • POCO entity wrapped by a proxy (a proxied entity)

A wrappable entity is a class written to enable extensibility via proxy. You have already discovered that a class is wrappable when it isn’t closed to inheritance and its properties are virtual. In particular, a wrappable class enables change tracking if all scalar properties are virtual. A wrapped (or proxied) entity is an instance of such an entity that has been wrapped inside a virtual proxy.

The state manager doesn’t care whether a class is wrappable or not. What’s important to it is whether the entity has been instantiated as a proxy or as a pure POCO class. Let’s look at some examples to clarify the distinctions in the preceding list.

Change-Tracking of an Entity Not Wrapped Inside a Proxy

An entity might be obtained from a web service, from the deserialization of the ASP.NET ViewState in a web page, from a query where the context has proxy creation disabled, or from instantiating it with a constructor. These objects aren’t wrapped by a proxy, because only the context with proxy-creation enabled (the default setting) can create a wrapped entity. What’s more, an entity may not be wrappable, so even if it comes from a context, it may not be proxied.

As you saw in chapter 5, entities’ property setters have no knowledge of the state manager, so how can the state manager learn when a property is modified? You may be surprised to discover that it can’t.

Let’s look at an example. Suppose you need to modify a customer. You query the database to retrieve the customer, and you modify a property, such as the name, and then persist it. Because the state manager doesn’t know that you have modified a property, the state of the entity remains Unchanged, as you can see here.

Listing 6.11. Modifying a customer using an entity not wrapped inside a proxy

When the SaveChanges method is invoked, the modifications are persisted into the database even if the state is Unchanged! How can this work? Why are modifications persisted if the state manager knows nothing about them?

The magic is buried inside the ObjectStateManager class’s DetectChanges method, which is internally invoked by the SaveChanges method. This method iterates over all state-manager entries, and for each one compares the original values with those stored in the entity. When it detects that a property has been modified—the customer name, in this example—it marks the property as Modified, which in turn marks the entity as Modified and updates the current value of the entry. When DetectChanges has finished its job, the entities and their entries in the state manager are perfectly in sync, and SaveChanges can move on to persistence.

Because the state manager isn’t automatically synchronized with the entities, whenever you need to work with its APIs, you must invoke the DetectChanges method to avoid retrieving stale data, as in the previous listing. You can do this as in the next snippet.

Listing 6.12. Invoking the DetectChanges method

DetectChanges isn’t problem-free. It iterates over all entities and evaluates all their properties. If lots of entities are tracked, the iteration may be expensive. Use it, but don’t abuse it.

Change Tracking Wrapped Inside a Proxy

When an entity is wrapped in a proxy, there is more magic under the covers. A proxied entity enables automatic change tracking, meaning that it immediately notifies the state manager when a property is modified. This happens because the proxy overrides property setters, injecting code that notifies the state manager about property changes. Figure 6.2 contains a simplified version of the code inside the proxy.

Figure 6.2. The code injected by the proxy notifies the state manager about changes in properties.

This feature is fantastic because, without any effort, you get automatic synchronization between the state manager entry and the entity. This behavior is clear in the next listing.

Listing 6.13. Modifying a customer inside a proxy

In chapter 3, you learned that proxies enable lazy loading. In this chapter, you’ve learned that proxies also enable automatic change tracking. In Entity Framework 1.0, these features required tons of code, and now you get them almost for free. This is another huge step ahead in Entity Framework 4.0.

Not only can the context track single entities, but it also tracks entities’ relationships. You might expect this to be done using the primary-key properties of associated objects or foreign keys; sometimes it’s done that way, but sometimes it’s done in other ways.

6.3.5. Understanding relationship tracking

When an entity is attached to the context, a new entry is added in the state manager. After that, the context scans the navigation properties to find associated entities. Those that aren’t null are automatically attached. The same behavior holds true when adding entities.

When a related entity is attached, if the relationship is via an independent association, a new RelationshipEntry is added to the state manager, containing information about the related entities. For instance, if you attach an order that has a reference to a customer and many order details, the state manager will contain the order, its details, and its customer entries, along with their association entries. Figure 6.3 shows what the state manager looks like after such an attach process.

Figure 6.3. The state manager entries after an order with independent associations is attached (shown in the QuickWatch window). There are six entities (one for the order, one for the customer, two for the order details, and two for their related products) and five entries for the relationships between the entities (one for the relationship between the order and the customer, two for the relationship between the order and its details, and two for the relationship between the order details and their product).

If the order is loaded using a query, things are slightly different. The state manager creates one entry for the order, one for the customer, and one for their relationship (order details are ignored because collection associations are ignored). This happens even if you don’t retrieve the customer with the order. The customer entry contains only the primary key (taken from the CustomerId column in the Order table), whereas the relationship points to both entities, so the state manager has everything it needs.

A relationship can be in the Added or Deleted state but can’t be in the Modified state. Usually, you won’t need to modify relationship state, because it’s handled by the state manager. In the rare cases when you need to modify the relationship state, you can use the ObjectStateManager class’s ChangeRelationshipState method or the ObjectStateEntry class’s ChangeState method. Naturally, if you try to change the relationship state to Modified, you’ll get a runtime exception.

If foreign-key associations are used, no relationship entries are created because you don’t need the associated entity, just the foreign-key property. The result is that after attaching the same order as before, the state manager looks like what you see in figure 6.4.

Figure 6.4. The state manager after an order with foreign-key associations is attached. There are three entities (one for the order and two for the details). The relationship entries don’t exist because foreign keys are used.

When an entity is retrieved from the database, neither the entity entries nor the relationship entries are created in the state manager for the associated entities. Furthermore, changing a relationship is trivial because you only need to change the foreign-key property. As you can see, foreign-key associations make things simple and reduce the state manager’s work.

Now that you know how the state manager tracks entities and relationships, let’s investigate a couple of caveats.

Changes are Tracked Only when Entities are Tracked by Context

When entities are outside the scope of the context, changes made to them aren’t tracked. If you create an order, add a detail, or change its associated customer, and then attach the order to the context, the context will never know what happened. The order and the relationship entry will be in the Unchanged state when they’re attached.

State Manager doesn’t Support Partially Loaded Graphs

When you attach an entity, the context scans all navigation properties and attaches related objects as well. (The same happens if you add an object to the context.) All entities are put in Unchanged state if they’re attached, and in Added state if they’re added.

If the context already tracks an entity of the same type and with the same key as an entity in the graph, it raises an InvalidOperationException because it can’t hold two objects of the same type with the same key.

When adding a graph, there’s no risk of an exception because the entity key associated with the objects is temporary. A problem arises if the entity is later marked as Unchanged (which does happen, as you’ll discover later in this chapter). In such a case, the EntityKey is regenerated and becomes permanent, and if there’s already an entity with the same key, an InvalidOperationException is thrown with the message, “AcceptChanges cannot continue because the object’s key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.”

How Relationships Change in Single-Reference Properties

Suppose you have to change the customer associated with an order. You can find yourself in two situations:

  • Customer is already attached to the context. If a foreign key association is in place, the property is synchronized and the Order object becomes Modified. If you use an independent association, only the RelationshipEntry is created between the entities.
  • Customer isn’t attached to the context. The Customer is added to the context (remember it doesn’t support partial graphs) in the Added state. If the association is kept using a foreign-key association, the property is synchronized and the Order object becomes Modified. If you use an independent association, only the RelationshipEntry is created between the entities.

 

Note

If the instances are proxies, the state change is automatic. If the instances are plain objects, you’ll see state changes only after a call to DetectChanges.

 

Suppose you have an order without a customer. If you use a foreign-key association, setting the foreign-key property to null makes the association between the order and the customer disappear. If you use an independent association, setting the Customer property to null leads to the same result (the RelationshipEntry becomes Deleted).

Keep in mind that only the association between the customer and the order is removed. None of the objects are deleted.

How Relationships Change in Collection Properties

A call to the Remove method of a collection property causes the reference to the master to be removed from the detail. For instance, when you remove a detail from an order, its Order property is set to null, and its status is set as Modified. Because the foreign-key property (OrderId) isn’t nullable, during persistence an Invalid-OperationException occurs with this message: “The operation failed: The relationship could not be changed because one or more of the foreign-key properties is nonnullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.”

Although the message might look encrypted, its quite clear. The OrderId property of the detail can’t be null because you can’t have an orphan order detail. The detail must always be assigned to an order. If you supported orphan details, the OrderId column in the OrderDetail table would be nullable. Similarly, the OrderID property in the OrderDetail class would be nullable. In this case, persistence wouldn’t throw any exception, so the order would be persisted and the order detail would become an orphan. (Of course, there’s no point in having an orphan detail—we mention this only for completeness.)

If you use an independent association, you’ll get a different message: “A relationship from the ‘OrderOrderDetail’ AssociationSet is in the ‘Deleted’ state. Given multiplicity constraints, a corresponding ‘OrderDetail’ must also in the ‘Deleted’ state.” This means that the RelationshipEntry in the state manager is Deleted, but the entity is Modified, and that isn’t allowed because orphan order details aren’t allowed; the detail must be deleted too.

The solution to the problem is to invoke the context’s DeleteObject method instead of the collection property’s Remove method.

You’re probably wondering why Entity Framework doesn’t automatically delete the entity instead of simply removing the reference. The answer is that in other situations this isn’t the correct behavior. Think about the many-to-many relationships between suppliers and products. In that case, you don’t have to delete a product if you remove it from those sold by a supplier. You just have to delete the reference in the Product-Supplier table. Due to these different behaviors, the Entity Framework team decided conservatively to let you explicitly choose what to do.

 

Note

We hope that in the next release, the Entity Framework team will allow us to map what to do and add an element in the EDM that specifies whether the entity must be deleted or just the reference, when the entity is removed from a collection property. Having to explicitly call the DeleteObject method sounds less fluent than removing an element from the collection.

 

When you add an entity to a collection property (such as adding an order detail to an order), you can find yourself in two different situations depending on whether the entity is attached to the context or not:

  • The detail is attached to the context. If foreign-key association is in place, the property must be synchronized with the ID of the order. If you use an independent association, only the RelationshipEntry is created between the entities.
  • The detail isn’t attached to the context. The Customer is added to the context in Added state (remember, the context doesn’t support partial graphs). If the association is kept using a foreign-key association, the property must be synchronized with the ID of the order. If you use an independent association, a RelationshipEntry between the entities is also created.

There are a lot of rules, and knowing them in advance makes manipulating graphs easier and less scary.

Do you remember MergeOption? It’s a way to specify how an entity that’s tracked by the context is merged with an entity with the same key coming from a query. We looked at all the options in chapter 3, but we only analyzed them from a querying point of view. Now let’s look at them from the state manager’s point of view.

6.3.6. Change tracking and MergeOption

MergeOption is a property of the ObjectSet<T> class. It’s an enum of type System.Data.Objects.MergeOption that contains the following values:

  • AppendOnly
  • NoTracking
  • OverwriteChanges
  • PreserveChanges

During an object’s materialization, when AppendOnly is used (the default setting), the state manager checks whether there’s already an entry with the same key. If so, the entity related to the entry is returned and data from the database is discarded. If not, the entity is materialized and attached to the context. In this case, the state manager creates the entry using the original and current values of the materialized entity. Eventually, the materialized entity is returned.

When NoTracking is used, the context doesn’t perform the identity-map check, so data from the database is always materialized and returned, even if there’s already a corresponding entity in the state manager. The entities returned when the NoTracking option is enabled are in Detached state, so they’re not tracked by the context.

When OverwriteChanges is used, if the identity-map check doesn’t find an entry in the state manager, the entity is materialized, attached to the context, and returned. If the entry is found, the related entity state is set to Unchanged, and both the current and original values are updated with values from the database.

When PreserveChanges is used, if the identity-map check doesn’t find an entry in the state manager, the entity is materialized, attached to the context, and returned. If the identity-map check does find an entry in the state manager, there are a few things that can happen:

  • If the state of the entity is Unchanged, the current and original values in the entry are overwritten with database values. The state of the entity remains Unchanged.
  • If the state of the entity is Modified, the current values of the modified properties aren’t overwritten with database values. The original values of the unmodified properties are overwritten with database values.
  • If the current values of the unmodified properties are different from the values from the database, the properties are marked as Modified. This is a breaking change from version 1.0, because in that version properties aren’t marked as Modified. If you need to restore the version 1.0 behavior, set the UseLegacyPreserveChangesBehavior property to true as follows: C#
    ctx.ContextOptions.UseLegacyPreserveChangesBehavior = true; VB
    ctx.ContextOptions.UseLegacyPreserveChangesBehavior = true

Now you know all there is to know about MergeOption’s behavior. It’s an important aspect of any application, and it’s often misused or underestimated. In chapter 19, you’ll discover that it dramatically affects performance.

6.4. Summary

Understanding the entity lifecycle will enable you to manage objects efficiently and avoid surprises. Because an entity’s state affects how modifications are persisted into the database, its vital that you fully master this aspect of Entity Framework.

Correctly managing the various states and their transitions is very important and will prevent problems when objects are later persisted. This is particularly true when you’re working with object graphs, where Entity Framework will automatically attach and add related objects.

You must also pay attention to tracking. When working with plain POCO entities, you have to remember that they aren’t in sync with the state manager. If you plan to search entities in the state manager, always remember to synchronize it with the actual entities.

When you fully master these subjects, persistence will become much easier, as you’ll discover in the next chapter.

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

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