1

I have an DbContext and many entities. I want to cache one entity of them. I.e. I have an entity Address:

public partial class Address : BaseEntity
{
    public string Street { get; set; }
    public string City { get; set; }
    public string ZipPostalCode { get; set; }
    public int StateProvinceId { get; set; }
    public string ContactName { get; set; }
    public string ContactPhone { get; set; }
    public string ContactFaxNumber { get; set; }
    public string ContactEmail { get; set; }

    public virtual StateProvince StateProvince { get; set; }
}

and StateProvince:

public partial class StateProvince : BaseEntity
{
    public string Name { get; set; }
    public string Abbreviation { get; set; }
    public int TruckSpeedLimit { get; set; }
}

when I get address by Id, for example:

var address = _db.Addresses.Where(p=>p.Id == id).FirstOrDefault();

and then try to get State name:

var state = address.StateProvince.Name;

It creates one more request to DB. If I have a list of addresses, it creates count of list element additional requests

Of course, I can create DTO class and do projection in Linq query like:

var address = _db.Addresses.Where(p=>p.Id == id).Select(p=> new AddressDTO{ Id = p.Id, ..., StateName = p.StateProvince.Name..}).FirstOrDefault();

but my code architect does not like DTO classes at all and I also don't want to do duplicate classes for simple entities.

State list is static. How can I say to EF : "cache states, please!" and avoid to additional requests to db?

Oleg Sh
  • 8,496
  • 17
  • 89
  • 159

3 Answers3

1

This is best done outside of EF. Simply assign a static variable to the value

db.States.AsNoTracking().ToList()

And there is no mechanism in EF to force it to never refresh entities.

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • Nice short answer ;-) – Stefan Aug 27 '18 at 11:46
  • but I don't have `db.States.AsNoTracking().ToList()` query at all! I get State from `address.StateProvince.Name` ! – Oleg Sh Aug 27 '18 at 11:55
  • He means: something like: `your_static_list = _db.StateProvinces..AsNoTracking().ToList()`, somewhere in your DbContext you can query the states. – Stefan Aug 27 '18 at 12:09
  • and what should I do with this static variable? How is it linked with calling address.StateProvince.Name ? – Oleg Sh Aug 27 '18 at 12:32
  • You can attach the states as Unchanged entities in a DbContext, and the change tracker should "fix up" the Navigation Properties for your address entities. See https://learn.microsoft.com/en-us/ef/ef6/saving/change-tracking/entity-state – David Browne - Microsoft Aug 27 '18 at 15:15
0

In normal situations you would create a own caching mechanism.

You can start by defining a caching interface with some typical caching routines:

//note: using interface so implementation can be changed later
public interface ICache
{
     //store by key, use resolver if not present.
     T GetOrAdd(string key, Func<T> resolver);
     //invalidate all
     void Invalidate();
     //invalidate by key
     void Invalidate(string key);
}

So, now you need an implementation. Depending on your use case this could be a HttpCache or an other MemoryCache, even a hashtable. I'll leave it up to you.

Now, since it's cache, it's typical to use a singleton-like structure, to ensure you have only one cache. Typical this is managed by IoC containers like Unity or AutoFac.

Then, also depending on your other code, you can use this cache in your queries. Somepeople like repositories, other services. I am fond of Query and Command seperation.

If you have implemented that, than the use of cache is trivial and you can apply this to your states.

Example:

var address = _db.Addresses.Where(p=>p.Id == id).FirstOrDefault();
var state = cache.GetOrUpdate($"state-{address.StateProvinceId}", 
               () =>_db.StateProvinces.FirstOrDefault(c => c.Id == address.StateProvinceId));

But I must say; creating DTO's can be good when separating your data/domain layers. Furthermore; you can ask yourself what is the meaning of an address without a state, or what does a stateId means in the enterprise's context. Just to say; there are tons of solutions which all can be valid here.

Stefan
  • 17,448
  • 11
  • 60
  • 79
  • I like an approach with separating domain and data layers. But my project architect says, that it's difficult to support – Oleg Sh Aug 27 '18 at 11:43
  • I don't want to put you into an awkward position but normally it's the other way round, although it can depend on the use case ;-) Here is one about why to use DTO-like objects: https://stackoverflow.com/questions/23648832/viewmodels-in-mvc-mvvm-seperation-of-layers-best-practices (disclaimer, I answered it myself) – Stefan Aug 27 '18 at 11:45
  • I agree, that separated models (View/Domain/Data) is correct way to build an application – Oleg Sh Aug 27 '18 at 11:54
  • But still, using DTO only is not the answer to your caching/db-calls question. So maybe you can find a way without the DTO. The @DavidBrowne solution, in a static is clean (enough) and fast and easy to implement. – Stefan Aug 27 '18 at 11:56
  • I added comment under his answer – Oleg Sh Aug 27 '18 at 12:08
0

If you can live without pulling any relations then this would actually force EF to first look at any entities it has already loaded instead of going directly to the database for the item in question, if it does not find one locally then it will go out to the server. EF 6+

context.Set<T>().Find(params object[] keyvalues);
Paul Swetz
  • 2,234
  • 1
  • 11
  • 28