Data Caching with .Net 4.0 and Asp.Net MVC – part 2

This is the second article in a series about using the .Net 4.0 Caching objects in Asp.Net MVC – catch up first with part 1 of the series, in case you missed it.

In this post I’m going to cover how we can invalidate our repository cache in a more fine-grained, efficient way; instead of simply dropping and re-creating the cache every time something is written to the database, we’re essentially going to replace updated items directly into the cache just after we write them to the database. This will involved hooking into Entity Framework’s change-set and picking out the entities we’re interested in. I’m not going to cover the use of SqlDependancy here and the new wrappers within .Net 4.0.

Grab the code for this part from Github if you would like to follow along with the article. The sample project (and this article) continue on from where part 1 in this series left off.

Over the course of this article, we’re going to do the following:

  • Retrieve the objects which are being inserted, modified or deleted whenever we save changes
  • Use the changeset to identify entities which need to be added, updated or removed from the cache
  • Create a form to add and edit entities from the UI

So this means we’re basically doing our own invalidation, and the only way the cache will be updated is to update entities through our UI. Any changes made to the data outside of the UI (for example, in Management Studio) will not be picked up unless the cache is explicitly invalidated.

Lets start by making the changes to our IVehicleRepository class.

Here we add support for updating an entity, adding a new entity and persisting the changes to the data store. The new interface looks like this:

public interface IVehicleRepository {  
    void ClearCache(); 
    IEnumerable GetVehicles(); 
    void Insert(Vehicle vehicle); 
    void Update(Vehicle vehicle); 
    void SaveChanges(); 
}

We then add the implementation methods. Inserts and updates are fairly standard implementations. For updates, we have to mark the entity as being changed and attach it to the context. For inserts, we simply insert the entity into the Vehicles entity set.

public void Update(Vehicle vehicle) {  
 if (vehicle.EntityState == EntityState.Detached) {
  DataContext.AttachTo("Vehicles", vehicle);
 }

 DataContext.ObjectStateManager.ChangeObjectState(vehicle, EntityState.Modified);
}

public void Insert(Vehicle vehicle) {  
 DataContext.AddToVehicles(vehicle);
}

For the implementation of SaveChanges() to work efficiently, we need to make a small change to GetVehicles(). The change is essentially to store a Dictionary of vehicles into the cache instead of a plain list. We can (and should) still return a flat list from the method however to satisfy the interface contract. Here’s the method containing this revision:

public IEnumerable < Vehicle > GetVehicles() { // First, check the cache  
 var vehicleData = Cache.Get("vehicles") as IDictionary < Guid,
  Vehicle > ; // If it's not in the cache, we need to read it from the repository 

 if (vehicleData == null) { // Get the repository data 
  vehicleData = DataContext.Vehicles.ToDictionary(v => v.Id);
  if (vehicleData.Any()) { // Put this data into the cache for 30 minutes 
   Cache.Set("vehicles", vehicleData, 30);
  }
 }

 return vehicleData.Values;
}

Note also that the previous version placed the sorting code here, at the point when the data is read from the database. I’ve simply moved the sorting code outside to the controller, since any entities which are added to the cache could cause it to become unsorted again.

Next, SaveChanges(). Here we simply retrieve the list of changed entities (updated or added), then call SaveChanges() on the underlying data context. Then, we run through the list of changed entities and update the cache directly with the changes. Because the cache now stores a Dictionary, we can simply update or add entities by indexing the dictionary with the ID of the vehicle. The dictionary itself will work out whether or not the entity needs to be added or updated depending on whether it is already in the dictionary or not.

public void SaveChanges() { // Update or add new/existing entities from the changeset  
 var changeset = DataContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified);
 DataContext.SaveChanges();

 var cacheData = Cache.Get("vehicles") as Dictionary < Guid,
  Vehicle >

  if (cacheData != null) {
   foreach(var item in changeset) {
    var vehicle = item.Entity as Vehicle;
    cacheData[vehicle.Id] = vehicle;
   }
  }
}

There are a couple of important things to note here:

  • By calling SaveChanges() on the data context, we basically wipe out the changed entities. Therefore we need to get the list of changes entities before we actually process them and commit them to the data store.
  • We call SaveChanges() before updating the cache because, if the data commit was to fail for whatever reason (validation or some other connection problem) then the cache will still reflect our unchanged data. We didn’t update the cache whenever Update() or Insert() is called for this reason also. We only want the cache to update when the data is actually persisted.

Updating the user interface

All of our code changes regarding data access is now complete. Turning our attention to the UI, we need to create a way to allow the user to insert and update entities via the site. To this end, I am going to create a MVC partial view containing the form, and use it from both the Create and Edit action views.

Firstly, create actions within in the HomeController class called Create and Edit. Note here that we actually create two methods for each action – a GET and a POST action. The actions which have been attributed with [HttpPost] will be called when the form posts back after the submit button has been clicked.

public ActionResult Edit(Guid id) {  
 var vehicle = Repository.GetVehicles().Single(v => v.Id == id);
 return View(vehicle);
}

[HttpPost] 
public ActionResult Edit(Vehicle vehicle) {  
 Repository.Update(vehicle);
 Repository.SaveChanges();
 return RedirectToAction("Index");
}

public ActionResult Create() {  
 var newVehicle = new Vehicle() {
  Id = Guid.NewGuid()
 };
 return View(newVehicle);
}

[HttpPost] 
public ActionResult Create(Vehicle vehicle) {  
 Repository.Insert(vehicle);
 Repository.SaveChanges();
 return RedirectToAction("Index");
}

These actions do pretty much as you would expect – when creating, we simply create a new instance of Vehicle and pass it to the view. When editing, we ask the repository to retrieve an instance of Vehicle for us, which in turn uses the cache if available.

Next we need to create the two views; one for creating and one for editing. Create each view by right-clicking on the GET action (the action without the HttpPost attribute) and use the dialogs to create your views. Remember to make them strongly-typed and accept a type of CachingDemo.Web.Models.Vehicle, as we’d like to pass instances of Vehicle to the views and be able to use them in a strongly-typed way from inside the view.

Open the Create view and put in some code to call a partial view named VehicleForm (created next) and pass it the model. If you’ve left the site using the standard template with the master page, the following code should appear within the MainContent placeholder:

<h2>Create</h2> <% Html.RenderPartial("VehicleForm", Model); %>  
<p><%: Html.ActionLink("Back to list", "Index") %></p>  

Open the Edit view next copy in exactly the same code as the Create view. The partial view we create next is going to do all the work for us.

Create a partial view inside the /Views/Home folder called “VehicleForm”:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<CachingDemo.Web.Models.Data.Vehicle>" %> <% using (Html.BeginForm()) {%> 

<%: Html.ValidationSummary(true) %>  
<%: Html.HiddenFor(m => m.Id) %>  
<div> <%: Html.LabelFor(model => model.Name) %> </div>  
<div> <%: Html.TextBoxFor(model => model.Name) %>  
  <%: Html.ValidationMessageFor(model => model.Name) %> 
</div> 

<div> <%: Html.LabelFor(model => model.Price) %> </div>  
<div> <%: Html.TextBoxFor(model => model.Price, String.Format("{0:F}", Model.Price)) %>  
  <%: Html.ValidationMessageFor(model => model.Price) %> 
</div> 

<p> <input type="submit" value="Save" /> </p>  
<% } %>  

You can also achieve this of course by right-clicking the folder, selecting ‘View’ and then creating a partial view using the dialog. Just remember to make it strongly-typed as we’re going to be passing it an instance of Vehicle as its model from the parent view.

When you’re done, you should now be able to debug the application and watch as single items are retrieved from the cache and replaced in-situ when they are updated. This is more efficient than simply invalidating the whole cache and will scale better when dealing with more users and more data.

In the next part we look at how we can apply these caching patterns to repositories that use plain old SQL instead of using something like Entity Framework.