In this recipe, we are going to look at how to get your application on a change to get the most current data it can, so that when you are modifying objects, you get the most current one back.
We will be using the Nuget
Package Manager to install the Entity Framework 4.1 assemblies.
The package installer can be found at http://nuget.codeplex.com/.
We will also be using a database for connecting to the data and updating it.
Open the Improving Refreshing Data on Save solution in the included source code examples.
using System; using System.Linq; using BusinessLogic; using DataAccess; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Test { [TestClass] public class QueryTests { [TestMethod] public void ShouldRefreshDataOnSave() { var blogContext = new BlogContext(); Database.SetInitializer(new Initializer()); blogContext.Database.Initialize(true); var repo = new BlogRepository(new BlogContext()); var repo2 = new BlogRepository(new BlogContext()); var blog = repo.Set<Blog>().FirstOrDefault(); var blog2 = repo2.Set<Blog>().FirstOrDefault(); blog.Title = "Something new"; blog2.Rating = 3; repo2.SaveChanges(); repo.SaveChanges(); Assert.AreEqual(blog.Rating, blog2.Rating); } } }
Blog
object as a new C# file named Blog.cs
to our BusinessLogic
project, so that we have an example object to connect to, with the following code:using System; namespace BusinessLogic { public class Blog { public int Id { get; set; } public DateTime Creationdate { get; set; } public string ShortDescription { get; set; } public string Title { get; set; } public double Rating { get; set; } public bool IsDeleted { get; set; } } }
BlogMapping
to the DataAccess
project with the following code:using System.ComponentModel.DataAnnotations; using System.Data.Entity.ModelConfiguration; using BusinessLogic; namespace DataAccess.Mappings { public class BlogMapping : EntityTypeConfiguration<Blog> { public BlogMapping() { this.ToTable("Blogs"); this.HasKey(x => x.Id); this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("BlogId"); this.Property(x => x.Title).IsRequired().HasMaxLength(250); this.Property(x => x.Creationdate).HasColumnName("CreationDate").IsRequired(); this.Property(x => x.ShortDescription).HasColumnType("Text").IsMaxLength().IsOptional().HasColumnName("Description"); this.Property(x => x.IsDeleted).HasColumnName("DeletedFlag"); } } }
BlogContext
and make the context change the commit
behavior with the following code:using System.Data; using System.Data.Entity; using System.Linq; using DataAccess.Mappings; namespace DataAccess { public class BlogContext : DbContext, IUnitOfWork { public void Add<T>(T entity) where T : class { this.Set<T>().Add(entity); } public void Commit() { var itemsToBeSaved = this.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified).ToList(); SaveChanges(); foreach (var dbEntityEntry in itemsToBeSaved) { dbEntityEntry.Reload(); } } public IQueryable<T> Find<T>() where T : class { return this.Set<T>(); } public void Refresh() { this.ChangeTracker.Entries().ToList().ForEach(x => x.Rload()); } public void Remove<T>(T entity) where T : class { this.Set<T>().Remove(entity); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new BlogMapping()); base.OnModelCreating(modelBuilder); } } }
We start with a test that explicitly defines our goals. In this case, we want the data on a save operation to be the most current version possible. We define a test where we select an object back from two contexts. We update the objects differently, and call a save operation on both. The second object should get saved, and then updated.
We then create our Blog
object that we want to update, and the mapping that will tie it into the database context. This will give us the ability to interact with the database, and make sure that the object is truly refreshed.
We then get to the SaveChange
method in the context that will allow us to save the objects as we would normally do, but we also want to store the change tracking entries from before we save, so that we know which objects to refresh. This allows us to leverage the change tracker to reload the object from the database.
The change tracker in Entity Framework code first is complex and worth understanding a bit more.
The EntityState
is what the change tracker uses to know if it cares about the object and how it has been modified. When we add, delete, or modify a tracked object, the state is changed to reflect this operation. We are given access to this by passing in the EntityKey
, or the entity itself to the Entry
method, and getting back the object that represents the entity to the change tracker.
There is a secondary purpose to the change tracker, and that is to know which objects to ignore. When we need the change tracker to ignore an object, we have the ability to detach it, and then, at a later time, reattach it.
Chapter 7, Using Concurrent and Parallel Processing