Freigeben über


Working with Sets of Self-Tracking Entities


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.


Self-tracking entities have several utility methods to do things like AcceptChanges and StartTracking which work over a single entity. However, there are cases where it would be nice to be able to do similar operations but on a set of entities instead of just one.  In this post, I’ll describe two options for performing operations on sets of entities, one that works with entities that are attached to an ObjectContext, and one more general graph iterator solution based on a T4 template. Both options can be applied to all kinds of entities including self-tracking entities, POCOs, and EntityObject entities, but the samples I show will target common utility methods that can be used with self-tracking entities such as AcceptAllChanges and StartTrackingAll.

 

The ObjectStateManager Option

Many operations on sets of entities can be accomplished just by using the Entity Framework’s ObjectStateManager. The ObjectContext class uses an ObjectStateManager to track changes to entities and to do identity resolution, and exposes the ObjectStateManager as a public property. You can get access to the entities in the ObjectStateManager via the GetObjectStateEntries method which returns an IEnumerable<ObjectStateEntry>. An easy way to get at all of the entities is to write extension methods that use LINQ to Objects to perform queries against the entries in the ObjectStateManager.

One example of when this is useful is to turn on change tracking for every self-tracking entity that is being tracked by an ObjectContext. To do this, we’ll query the ObjectStateManager and cast each entity to an IObjectWithChangeTracker and call StartTracking on it:

 

 

public static void StartTrackingAll(this ObjectContext ctx)

{

    var allEntites = from e in ctx.ObjectStateManager

    .GetObjectStateEntries(~EntityState.Detached)

                    where e.Entity != null && e.Entity is IObjectWithChangeTracker

                    select e.Entity as IObjectWithChangeTracker;

    allEntites.ToList().ForEach(x => x.StartTracking());

}

 

These extension methods that do LINQ to Objects queries over the ObjectStateManager can be quite powerful, but can often be difficult to write and as I mentioned, can only be used if everything is tracked by an ObjectContext. The next section describes an alternative approach using a code generated graph iterator that does not involve the ObjectContext or ObjectStateManager.

 

 

The Graph Iterator Option

While the ObjectStateManager option is useful when entities are attached to an ObjectContext, it is not useful when you want to perform an operation on the client where creating an ObjectContext is just extra overhead (or worse, not even available like in the case of Silverlight). What we’d really like is a lightweight class that knows about all entities in a graph of related entities. I have created such a class as part of a T4 template that you can download using the link at the bottom of this post, and describe how it works and why it’s a T4 template below. What you need to do to get started is to download the above template, add it to your C# Visual Studio project, and point it at your edmx model file. Then you’ll be able to write extension methods like AcceptAllChanges to call AcceptChanges on every entity, HasChanges to tell if there are any changes in any entity in the set, or StartTrackingAll to turn change tracking on for all entities. For example:

 

 

void ClientUpdate(Customer c)

{

    if (c.HasChanges()) // Does c or any related entity have changes?

    {

        MyServiceClient client = new MyServiceClient();

        client.Update(c);

        c.AcceptAllChanges(); // Call AcceptChanges on c and any related entities

    }

}

 

The following sections describe how the template works in detail.

The NorthwindEntityIterator Class

The basic iterator class has a few core components:

· A private list to store entities

· A mechanism to traverse different kinds of entities and add them to the list of entities

· Methods to construct an iterator and execute operations over the set of entities in the iterator list

First let’s look at the main iterator class, which I’ve called NorthwindEntitiesIterator because it will help iterate over entities from the Northwind model. There is a private field _items for the list, and the class exposes two views of this list as properties: one public property as a read-only view, and one protected property as a writable view (so the contents of list cannot be modified after the list is populated):

public class NorthwindEntitiesIterator : IEnumerable<object>

{

    protected List<object> _items;

   

    #region Properties

   

    public ReadOnlyCollection<object> Items

    {

        get

        {

            return WritableItems.AsReadOnly();

        }

    }

  

    protected List<object> WritableItems

    {

        get

        {

            if (_items == null)

            {

                _items = new List<object>();

            }

            return _items;

        }

    }

   

    #endregion

   

    #region IEnumerable Implementation

   

    IEnumerator<object> IEnumerable<object>.GetEnumerator()

    {

        return Items.GetEnumerator();

    }

   

    IEnumerator IEnumerable.GetEnumerator()

    {

        return Items.GetEnumerator();

    }

   

    #endregion

}

 

To populate the list of entities, the NorthwindEntitiesIterator uses a variation of the visitor design pattern that takes advantage of some new language features in .NET 4.0. Most visitor implementations require some change to the entity classes, but with the dynamic feature in .NET 4.0, this isn’t necessary.  The key requirement for the visitor mechanism is to traverse the related entity graph and add each entity to the list, but to only add each entity once. It is fairly common for a graph or related entities to have cycles: a Customer has an Orders collection and each Order has a reference to a Customer, and we want to be able to stop traversing when we detect such a cycle.

 

The NorthwindEntitiesIterator’s visitor mechanism starts with a Visit method that takes an entity as a dynamic object. In C# in .NET 4.0, all object types can be cast to a dynamic so the method can accept any of the entity types in the model. The trick though is that once the entity is cast as a dynamic, it can be passed back to statically typed methods as long as such a method exists at runtime. To illustrate this, let’s look at the implementation of the Visit method:

internal void Visit(dynamic entity)

{

    if (entity != null && !WritableItems.Contains(entity))

    {

        WritableItems.Add(entity);

        NorthwindEntitiesExtensions.Traverse(entity, this);

    }

}

 

The Visit method first checks to see if the entity has already been visited by looking in the WritableItems collection which is how it detects cycles and knows how to stop traversing the entity graph.  If the entity has not yet been visited, the entity (which is a dynamic object in this method) is passed to the NorthwindEntitiesExtensions.Traverse method.The NorthwindEntitiesExtenions class is a static class with a bunch of static Traverse extension methods. The Traverse method is responsible for knowing how to get from an entity to its related entities, so there is a Traverse extension method for each entity type. This is where the power of T4 templates comes in because the Traverse methods can be generated by the template since the template knows all the entity types and their navigation properties. Here is an example of a Traverse method for the Territory entity in the Northwind model which has a reference to a Region entity and a collection of Employee entities:

 

internal static void Traverse(this Territory entity, NorthwindEntitiesIterator visitor)

{

    if (entity.Region != null)

    {

        visitor.Visit(entity.Region);

    }

    if (entity.Employees != null)

    {

        foreach (var value in entity.Employees)

        {

            visitor.Visit(value);

        }

    }

}

 

All the Traverse method does is look at the navigation properties on the Territory and call the NorthwindEntitiesIterator.Visit method on them. Once the first Visit method call returns, all entities in the graph have been added to the _items collection and we can start executing operations on them.

 

The NorthwindEntitiesIterator has a few methods to make creating the iterator and executing operations over it easy. First, there is a factory method which creates an iterator and populates its list with entities:

public static NorthwindEntitiesIterator Create<T>(T entity)

{

    NorthwindEntitiesIterator iterator = new NorthwindEntitiesIterator();

    iterator.Visit(entity);

    return iterator;

}

 

There are also several Execute methods that take an iterator and an Action<T> and execute the action on all entities of the type T. For example:

 

public void Execute<TFilter>(Action<TFilter> action)

{

    foreach (var item in Items.OfType<TFilter>())

    {

        action(item);

    }

}

 

Writing Self-Tracking Entity Utility Methods

Now that we have the NorthwindEntitiesIterator, we can write some utility methods for common self-tracking entities tasks. Sometimes it is useful to start and stop change tracking on all related entities. This is typical when the scenario calls for you to perform change tracking when serialization is not involved such as in a service or workflow method. These methods can be written with just a few lines of code using the NorthwindEntitiesIterator.Execute method: simply create an iterator instance for the entity graph and execute an action on all IObjectWithChangeTracker instances to StartTracking or StopTracking:

public static void StartTrackingAll(this IObjectWithChangeTracker entity)

{

    NorthwindEntitiesIterator.Execute(entity,

                         (IObjectWithChangeTracker e) => { e.StartTracking(); });

}

public static void StopTrackingAll(this IObjectWithChangeTracker entity)

{

    NorthwindEntitiesIterator.Execute(entity,

                         (IObjectWithChangeTracker e) => { e.StopTracking(); });

}

 

To use the StartTrackingAll method, you could then write something like this:

using (var ctx = new NorthwindEntities())

{

    Customer c = ctx.Customers

                      .Include("Orders.Order_Details")

                      .Single(x => x.CustomerID == "ALFKI");

    c.StartTrackingAll();

    // Now c, all of its Orders, and all of those Orders' Order_Details

    // are being change tracked

    ...

}

 

In client-server applications, a common usage pattern with self-tracking entities is to re-use entities on the client after a service call to Update a set of entities. To re-use an entity on the client after it has been updated, you need to call AcceptChanges on the entity. To re-use a graph of related entities, you need to call AcceptChanges on all the related entities and this is where the NorthwindEntitiesIterator can help to write an AcceptAllChanges extension method. The implementation is very similar to StartAllTracking:

public static void AcceptAllChanges(this IObjectWithChangeTracker entity)

{

    NorthwindEntitiesIterator.Execute(entity,

                         (IObjectWithChangeTracker e) => { e.AcceptChanges(); });

}

 

One might use this method like this on the client to call an Update service method and then accept all the changes on that entity graph:

void ClientUpdate(Customer c)

{

    MyServiceClient client = new MyServiceClient();

    client.Update(c);

    c.AcceptAllChanges();

}

 

As a final example, one optimization to the client-server ClientUpdate example above is to only actually call the service’s Update method if the Customer c or any of its related entities have changes in them. To do this, we need a method that looks over every entity in the graph and checks whether there are any changes. The NorthwindEntitiesIterator can again help us implement this method:

public static bool HasChanges(this IObjectWithChangeTracker entity)

{

    NorthwindEntitiesIterator entities = NorthwindEntitiesIterator.Create(entity);

    foreach (IObjectWithChangeTracker e in entities.OfType<IObjectWithChangeTracker>())

    {

        if (e.ChangeTracker.State != ObjectState.Unchanged)

        {

            return true;

        }

    }

    return false;

}

 

First we create a NorthwindEntitiesIterator and then we can iterate over the contents and check whether any of the entities have a state other than Unchanged. This makes the optimization to our ClientUpdate method straightforward:

void ClientUpdate(Customer c)

{

    if (c.HasChanges())

    {

      MyServiceClient client = new MyServiceClient();

        client.Update(c);

        c.AcceptAllChanges();

    }

}

 

In Summary

There are a lot of good reasons to perform operations on a set of related entities and so having a good graph iterator solution is very useful when implementing these methods. When you have things attached to an ObjectContext, it’s possible to use LINQ to Objects to query the ObjectStateManager.

 

A more general solution is to use the graph iterator template (see link below, you'll need to rename it to "Iterator.tt"), which can be used anywhere the entities are used, even when there is no ObjectContext, but does require you to include the template in your project. In an upcoming blog post, I’ll outline some additional uses for the template based iterator that helps with re-using self-tracking entities on the client even when you need to deal with server generated values during update service calls.

 

 If there are more self-tracking entities scenarios you’d like to hear about, just let me know.

 

Jeff Derstadt

Entity Framework Team

Iterator.tt.txt

Comments

  • Anonymous
    June 03, 2010
    The comment has been removed
  • Anonymous
    June 09, 2010
    @Ashref ApplyChanges is not a general purpose "merge"method and for the most part expects the ObjectContext to be empty. However, if you need this to be done, you can do one of the following:
  1. Use multiple contexts as the forum thread suggests (there isn't much overhead with creating an ObjectContext).
  2. If possible, call ApplyChanges before querying for the ExistingUser. You can use one of the MergeOptions: PreserveChanges or OverwriteChanges if you want merge semantics on the entity that is retrieved. For example: dc.Users.ApplyChanges(user); var existingUserQuery = dc.Users.Where(i => i.UserId == 20); existingUserQuery.MergeOption = MergeOption.PreserveChanges; // or MergeOption.OverwriteChanges...depends on your business logic. var existingUser = exisintgUserQuery.First(); dc.SaveChanges(); Jeff
  • Anonymous
    February 15, 2011
    Thanks for the article. However there is problem when using n to m relations. In Case i am updating a relation, the HasChanges function will return false, because the entity itself is in a state of Unchanged, but the collection in the ChangeTracker (ObjectsAddedToCollectionProperties or  ObjectsRemovedFromCollectionProperties) has some objects. You should update your function and check if in the collections there are some objects that needs to be deleted or added. In that case we should update the entity. Here is a sample of the new HasChanges function.        public static bool HasChanges(this IObjectWithChangeTracker entity)        {            JobnetBOEntitiesIterator entities = JobnetBOEntitiesIterator.Create(entity);            foreach (IObjectWithChangeTracker e in entities.OfType<IObjectWithChangeTracker>())            {                if (e.ChangeTracker.State != ObjectState.Unchanged ||                    e.ChangeTracker.ObjectsAddedToCollectionProperties.Count > 0 ||                    e.ChangeTracker.ObjectsRemovedFromCollectionProperties.Count > 0)                {                    return true;                }            }            return false;        } Omry

  • Anonymous
    March 06, 2011
    Is there a Iterator.tt file in VB.NET

  • Anonymous
    April 11, 2011
    The comment has been removed

  • Anonymous
    May 11, 2011
    Hi, Really like the Iterator template, however I also found it quite slow with large object graphs. This is because of the number of Contains() operations that are performed on the _items collection. As this is of type List<T> Contains() performs a linear search. Swapping this out for a HashSet<T> will dramatically improve the performance. You just need to change the field type and the WriteableItems Property and change the return statement of the Items property to: WritableItems.ToList().AsReadOnly(); Do this in your TT file so that future changes to your EDMX will not result in this being lost.