首页 > 代码库 > Entity Framework 4.1 Change Tracker EF状态跟踪函数解析
Entity Framework 4.1 Change Tracker EF状态跟踪函数解析
Entity Framework 4.1 Change Tracker
Julie Lerman
http://thedatafarm.com
Published: June 2011
Download the code for this article
- C# (VS2010)
- Visual Basic (VS2010)
The Entity Framework 4.1 DbContext keeps track of the state of objects it‘s managing and provides a number of ways for you to access that state information whether your entities come from the Code First, Model First or Database First workflow. DbContext also gives you access to features like explicitly loading related data.
In this article, I’ll be using two simple classes, Blog and Post, that are managed by the BlogContext class which inherits from DbContext.
public class Blog
{
public Blog() {
Posts = new List<Post>();
}
public int Id { get; set; }
public string Title { get; set; }
public string BloggerName { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
Accessing State Information of a Single Object
The Blog and Post class are only aware of the current values of their properties. They know nothing about their state or their history. The DbContext class keeps track of the state information in a DbEntityEntry object for each object it’s managing.
DbContext.Entry( ) is the entry point to the state information for a particular object instance. Just pass in an object to return its corresponding DbEntityEntry. If the object is not yet being tracked, the context will begin tracking it as a result of this call.
Here is some code that instantiates a new BlogContext, and uses that to retrieve a single blog.
var db = new BlogContext();
var post = db.Posts.First();
With the post in hand, you can grab it’s entry:
var stateInfo = db.Entry(post);
Inspecting the stateInfo in the debugger, you can see that it exposes not only a State (which is Unchanged) but CurrentValues, OriginalValues and a pointer back to the instance of the Post it represents.
Figure 1 |
I’ll modify a property of the Post.
post.Title = "What‘s happening at " + DateTime.Now.ToShortTimeString();
Inspecting the stateInfo variable will reveal that it has not changed. The reason is that the DbContext needs to take another look at post before it will be aware of the changes. You can do that with:
db.ChangeTracker.DetectChanges();
The stateInfo.State value will now be Modified as you can see in Figure 2.
Figure 1 |
Inspecting what’s in the CurrentValues and OriginalValues isn’t quite as obvious. The DbPropertyValues that they both inherit from is a read-only hash set of property names and values. It exposes the names through its PropertyNames property and then you can use those to get to the actual values.
Here’s a simple method which will take any DbPropertyValues object, iterate through its PropertyNames, and then display that property name along with its associated.
private static void IterateValues(DbPropertyValues propertyValues)
{
foreach (var propertyName in propertyValues.PropertyNames)
{
Console.WriteLine(" {0} = {1}",
propertyName, propertyValues[propertyName]);
}
}
Passing the stateInfo.OriginalValues into this method results in a list of the original values of the Post object.
Id = 1
Title = ForeignKeyAttribute Annotation
DateCreated = 3/15/2011 12:00:00 AM
Content = Mark navigation property with ForeignKey
BlogId = 1
The CurrentValues property results in:
Id = 1
Title = What‘s happening at 2:06 PM
DateCreated = 3/15/2011 12:00:00 AM
Content = Mark navigation property with ForeignKey
BlogId = 1
Notice that the Title now reflects the change that was made in the code above.
Retrieving the Current Database Values for an Object
In addition to the OriginalValues and CurrentValues property, Entry exposes a method called GetDatabaseValues. This triggers a query of the database to retrieve the current database values for that object and returns them as a DbPropertyValues type. You can pass that type to the IterateValues method to inspect it.
IterateValues(stateInfo.GetDatabaseValues);
The method outputs the following data:
Id = 1
Title = ForeignKeyAttribute Annotation
DateCreated = 3/15/2011 12:00:00 AM
Content = Mark navigation property with ForeignKey
BlogId = 1
In this case, nobody has modified the post since it was retrieved from the database, so the database values match the original values of the post instance.
Working with Multiple State Entries
DbContext.Entry() allows you to get the state information for a single, known object. But what if you want to look at all of the entries that the context is tracking?
For example, here‘s some code that retrieves all blogs then makes edits— modifying the title of one blog, deleting another and then I adding a new one.
var blogs = db.Blogs.Include("Posts").ToList();
blogs[0].Title = "Rebranding the Blog";
db.Blogs.Remove(blogs[1]);
var newBlog = new Blog { BloggerName = "Liz", Title = "The Marrying Kind" };
db.Blogs.Add(newBlog);
In order to look at the universe of DbEntityEntry types that the context is managing, you need to leverage the DbContext.ChangeTracker property. It has a method to return all of the entries, Entries().
Here is some code that iterates through all of the entries and writes out the title and state of each entry. Recall that in Figure 1, you saw that DbEntityEntry provides a pointer back to the object that it represents, the Entity property.
foreach (var entry in db.ChangeTracker.Entries<Blog>())
{
Console.WriteLine("Blog: Title {0}, State: {1}",
((Blog)entry.Entity).Title, entry.State);
}
This iteration outputs the following, including a blog that was left untouched during the edits.
- Blog: Title The Marrying Kind, State: Added
- Blog: Title Rebranding the Blog, State: Modified
- Blog: Title My Life as a Blog, State: Deleted
- Blog: Title Tweeting for Dogs, State: Unchanged
Entries optionally uses generics to let you specify a type to retrieve. If you have other objects besides Blog being tracked but you are only interested in Blog types, you can specify that.
A handy result of this is that now EF is quite sure that all of the entries returned are for Blog types and you’ll find that Entity becomes strongly typed. You can change the above code to
foreach (var entry in db.ChangeTracker.Entries<Blog>())
{
Console.WriteLine("Blog: Title {0}, State: {1}",
entry.Entity.Title, entry.State);
}
Because Entries is enumerable you can also perform LINQ to Objects queries. Here, the query filters out all of the Unchanged Blog entities.
foreach (var entry in db.ChangeTracker.Entries<Blog>()
.Where(e => e.State != EntityState.Unchanged))
Explicitly Load Related Data with Entry.Collection
In addition to the state information, there are also methods to access information about the properties of an entry’s entity. Figure 2 shows all of the properties and methods of the DbEntityEntry class.
Figure 3 |
With an object instance, you can use strong typing to navigate to related data.
var blog = db.Blogs.First();
var posts = blog.Posts();
But if you didn’t have access to the object instance you can use the DbEntityEntry.Collection method to write generic code to access a collection property.
Continuing the example that retrieves all of the Blog entries then iterates through them, you could use the Collection method to access the Posts of each blog. Collection.CurrentValue returns a generic list of the objects, so you need to do a little digging to get at the actual posts. Here I’ve cast the CurrentValue to a List<Post> from which I can get a count.
foreach (var entry in db.ChangeTracker.Entries<Blog>())
{
var posts = entry.Collection(b=>b.Posts);
Console.WriteLine("{0}", ((List<Post>)posts.CurrentValue).Count)
}
If the entries are not strongly typed, removing the <Blog> typing, you replace the lambda expression that represents the property with a string.
foreach (var entry in db.ChangeTracker.Entries())
{
var posts = entry.Collection("Posts");
Console.WriteLine("{0}", ((List<Post>)posts.CurrentValue).Count)
}
Not only can you access the collection, but through this ChangeTracker, you can also load the collection from the database if you are not relying on lazy loading. You might want to check that it hasn’t been loaded before doing that so you won’t waste the trip to the database. If IsLoaded is false, then Entity Framework will execute a query on the fly and grab the posts for the particular blog from the database.
Note that Collection.Load will fail with entities that are Deleted or Added. Therefore they are both filtered out in this example.
foreach (var entry in db.ChangeTracker.Entries<Blog>()
.Where(e=>e.State != EntityState.Deleted && e.State != EntityState.Added))
{
Console.WriteLine(entry.Entity.Title);
var posts = entry.Collection b=>b.Posts);
if (posts.IsLoaded == false)
{
posts.Load();
}
Console.WriteLine("# Posts: {0}", ((List<Post>)posts.CurrentValue).Count);
}
Filtering and Sorting Explicitly Loaded Related Data
Explicit loading has been available in Entity Framework since .NET 3.5. However, the Load features in Entity Framework 3.5 and 4.0 does not provide any support for fine-tuning what is loaded. It is an all or nothing gambit — you can only load the complete collection.
Loading through the ChangeTracker, however, does provide you with the ability to query when loading. Collection has a method called Query which returns the query that Entity Framework is going to use to get that related data from the database. You can modify that query with LINQ.
Here is an example of asking the query to only bring back Posts which have the word “Annotation” in their title.
posts.Query().Where(p => p.Title.Contains("Annotation")).Load();
Note that if you use the string parameter to specify the collection, the strong-typing that LINQ depends on for this will not work.
Notice what’s available from Intellisense when using the string parameter as shown in Figure 4.
Figure 4 |
You can fix this by strongly typing the collection. Notice that in Figure 5, LINQ methods are now available from the Query method.
Figure 5 |
Summary
This article has demonstrated a substantial set of the features that you can take advantage of in the Entity Framework 4.1 Change Tracker API. Along the way, you’ve also learned some tricks you’ll need to know as you use the features in more advanced scenarios.
Entity Framework 4.1 Change Tracker EF状态跟踪函数解析