Scenario:

In order to make applications perform it makes a lot of sense to cache commonly used reference data.

Good examples of reference data include things like States, Countries, Departments etc.

Generally you want to have this data readily at hand, so you can populate dropdown boxes etc.

A good example where caching reference data might be handy is a web page for signing up new customers, part of the form collects the customers address, including their State. In this example you need the reference data for two things:

  1. To build the State drop down for the form.
  2. To assign the State to the resulting Customer Record.

So how do you support this sort of scenario using the Entity Framework?

Solution:

When designing the solution we need to remember 2 key points.

  1. An Entity, at least in .NET 3.5 SP1, can only be attached to one ObjectContext at a time.
  2. You are probably going to use your cached reference data from lots of threads (read ObjectContexts) simultaneously.

Essentially these two points are at odds with one another.

The solution is to clone Entities whenever we read from the Cache, this way attaching clones won't affect any other threads.

If this was a webforms solution we would probably want to write something like this:

var customer = new Customer{
   Firstname = txtFirstname.Text,
   Surname = txtSurname.Text,
   Email = txtEmail.Text,
   Street = txtStreet.Text,
   City = txtCity.Text,

   State = statesCache.GetSingle(
      s => s.ID = Int32.Parse(ddState.SelectedValue)
   ),
   Zip = txtZip.Text
}
ctx.AddToCustomers(customer);
ctx.SaveChanges();

But this has one big problem. When you Add the customer to the ObjectContext the cloned State is added too. If we do this the Entity Framework thinks it needs to insert the State into the database. Which we don't want to do.

So we have to tell the Entity Framework that the cloned State is already in the database by using AttachTo(...):

var state = statesCache.GetSingle(
     s => s.ID = Int32.Parse(ddState.SelectedValue)
);

// See Tip 13 to avoid specifying the EntitySet
// as a
string
ctx.AttachTo("States", s);

Then we can go ahead and build the customer:

var customer = new Customer{
   Firstname = txtFirstname.Text,
   Surname = txtSurname.Text,
   Email = txtEmail.Text,
   Street = txtStreet.Text,
   City = txtCity.Text,

   State = state,
   Zip = txtZip.Text
}
ctx.SaveChanges();

If you are alert you may have noticed that I no longer call AddToCustomers(...).

Why? Well when you build a relationship to something that is already in the context (State = state), the customer gets added automatically.

Now, at the point SaveChanges() is called, only the Customer is saved to the database. The State isn't persisted at all, because the Entity Framework is convinced it hasn't changed.

Interestingly we can use the fact that the State isn't persisted to our advantage.

Because the key property of the State is all the Entity Framework actually needs to build the relationships, it doesn't matter if all the other properties are wrong, that key property is all we actually need to clone.

I.e. our cloning code can be very shallow:

public State Clone(State state)
{
   return new State {ID = state.ID};
}

Or as a lambda something like this:

var cloner = (State s) => new State {ID = s.ID};

So long as we don't intend to modify the clone, this is all we actually need.

Now we know what we want, it's pretty easy to write a very simple generic class that provides caching and 'clone on read' services:

public class CloningCache<T> where T : class
{
    private List<T> _innerData;
    private Func<T, T> _cloner;

    public CloningCache(IEnumerable<T> source, Func<T, T> cloner)
    {
        _innerData = source.ToList();
        _cloner = cloner;
    }
    public T GetSingle(Func<T, bool> predicate)
    {
        lock (_innerData)
        {
            return _innerData
                        .Where(predicate)
                        .Select(s => _cloner(s))
                        .Single();
        }
    }
}

Notice that the GetSingle(...) method clones the results it finds.

And using this cloning cache is very simple:

var statesCache = new CloningCache<State>(
      ctx.States,
      (State s) => new State {ID = s.ID}
);

The first parameter to the constructor is the data to cache (i.e. all the States in the database), and the second parameter is how to implement the cloning we need to safely use the cache across multiple ObjectContexts.

Once you've initialized this cache (probably in the Global.asax) you can dump it in a static variable for wherever you need to access this reference data.

Let me know if anything is unclear or you have any questions.