Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Caching domain model data

Recentelly I found an issue that at the beginning didn't appeared so dubious. Let me start by describing the big picture of my environment:

I have a domain model respecting the Table Module architecture that consumes a data access layer written using Entity Framework 6.x. My application is a windows forms application, both domain model and data access layer runs on client and I am using .NET 4.0 (fortunatelly, EF 6 is still compatible with .NET 4.0)

My goal: Create a cache of commmon nomenclators that are commonly used in combo boxes / lookups. This cache will be refreshed on demand by our users (there is a button on the right of every control that presents a nomenclator that can refresh the cache).

So far, so good. I've started to write this cache. In just a few words, my cache consists of a collection of TableCaches< T> instances and each of them is able to get a List from memory or from database (if something has been changed).

Next, imagine that you have a business like this:

public class PersonsModule
{
    public List<Person> GetAllByCityId(int cityId)
    {
        using (var ctx = new Container())
        {
            return (from p in ctx.Persons
                join addr in ctx.Addresses
                    on p.AddressId equals addr.Id
                where addr.CityId == cityId
                select p
                ).ToList();
        }
    }
}

In my mind an ideea started to grow: What if I can do a trick so that my "Container" sometimes gives fake collections, collections found in my cache?. But here I found the biggest issue: The .NET compiler does something tricky at compile time: It checks whether your collections are IQueriable < OfSomething >. If true, it burns inside IL code calls to extention methods that deals with expression tree like calls, otherwise it will simple call LINQ to Objects extention methods. I also tried (just for research purposes) this:

public class Determinator<TCollectionTypePersons, TCollectionTypeAddresses> 
    where TCollectionTypePersons : IEnumerable<Person>
    where TCollectionTypeAddresses : IEnumerable<Address>
{

    public List<Person> GetInternal(TCollectionTypePersons persons, TCollectionTypeAddresses addresses, int cityId)
    {
        return (from p in persons
                join addr in addresses
                    on p.AddressId equals addr.Id
                where addr.CityId == cityId
                select p
              ).ToList();

    }
}

and wrote in my module:

public class PersonsModule
{
    private ICache _cache;
    public PersonsModule(ICache cache)
    {
        _cache = cache;
    }

    public PersonsModule()
    {

    }

    public List<Person> GetAllByCityId(int cityId)
    {
        if (_cache == null)
        {
            using (var ctx = new Container())
            {
                var determinator = new Determinator<IQueryable<Person>, IQueryable<Address>>();
                return determinator.GetInternal(ctx.Persons, ctx.Addresses, cityId);
            }
        }
        else
        {
            var determinator = new Determinator<IEnumerable<Person>, IEnumerable<Address>>();
            return determinator.GetInternal(_cache.Persons, _cache.Addresses, cityId);
        }
    }
}

Why I've tried this? I just hoped that the runtime will emit correct MSIL extention methods calls whenever it sees that the generic type parameters are actually IQueryable< T>. But unfortunatelly this naive try proved me that I forgot some of the deep things related to how CLR and .NET compiler works. I remembered that in .NET world, you should expect compilation in two steps: step 1 is the normal compilation that contains also syntactic sugar resolution (type inference is resolved, anonymous types are generated, anonymous functions are transformed into real methods on some anonymous types or maybe on our types, etc. ). Unfortunatelly for me, in this category are found all LINQ expressions.

The second step is found at runtime, when CLR does some additional MSIL code emition from various reasons : A new generic type gets emitted, Expression trees are compiled, user code creates new types / methods at runtime, etc.

The last thing I've tried is ... I said OK I will treat all collections as IQueryable. The nice thing is that no matter what you will do (database calls or in memory calls) the compiler will emit calls to Expression trees LINQ extention methods. It works BUT it is veeeeery slow because in the end the expression gets compiled every time (even for in memory collections). The code is below:

public class PersonsModuleHelper
{
    private IQueryable<Person> _persons;
    private IQueryable<Address> _addresses;

    public PersonsModuleHelper(IEnumerable<Person> persons, IEnumerable<Address> addresses)## Heading ##        {
        _persons = persons.AsQueryable ();
        _addresses = addresses.AsQueryable ();
    }

    private List<Person> GetPersonsByCityId(int cityId)
    {
        return (from p in _persons
                join addr in _addresses
                    on p.AddressId equals addr.Id
                where addr.CityId == cityId
                select p
              ).ToList();
    }
}

In the end, I wrote the code below that works but.. damn it, I duplicate my code!!!

public class PersonsModuleHelper
{
    private bool _usecache;
    private IEnumerable<Person> _persons;
    private IEnumerable<Address> _addresses;
    public PersonsModuleHelper(bool useCache, IEnumerable<Person> persons, IEnumerable<Address> addresses)
    {
        _usecache = useCache;
        _persons = persons;
        _addresses = addresses;
    }

    private List<Person> GetPersonsByCityId(int cityId)
    {
        if (_usecache)
        {
            return GetPersonsByCityIdUsingEnumerable(cityId);
        }
        else
        {
            return GetPersonsByCityIdUsingQueriable(cityId, _persons.AsQueryable(), _addresses.AsQueryable());
        }
    }

    private List<Person> GetPersonsByCityIdUsingEnumerable(int cityId)
    {
        return (from p in _persons
                join addr in _addresses
                    on p.AddressId equals addr.Id
                where addr.CityId == cityId
                select p
              ).ToList();
    }

    private List<Person> GetPersonsByCityIdUsingQueriable(int cityId, IQueryable <Person> persons, IQueryable <Address> addresses)
    {
        return (from p in persons
                join addr in addresses
                    on p.AddressId equals addr.Id
                where addr.CityId == cityId
                select p
              ).ToList();
    }
}

What should I do? . I also know that EF does make a cache but the lifetime is short (only for the lifetime of your context instance) and it's not at query level but only at row level. Correct me if I am wrong!

Thanks in advance.

like image 761
George Lica Avatar asked Oct 20 '15 11:10

George Lica


1 Answers

Why not using an existing library with caching instead to make your own?

EF+ Query Cache

Support

  • Cache
  • Cache Async
  • Cache Tag
  • Memory Expiration

The library is open source, so you might find some good information if you still want to implement your own cache.

private List<Person> GetPersonsByCityIdUsingQueriable(int cityId, IQueryable <Person> persons, IQueryable <Address> addresses)
{
    return (from p in persons
            join addr in addresses
                on p.AddressId equals addr.Id
            where addr.CityId == cityId
            select p
          ).FromCache().ToList();
}

Disclaimer: I'm the owner of the project EF+ on GitHub

like image 129
Jonathan Magnan Avatar answered Oct 25 '22 14:10

Jonathan Magnan