缓存域模型数据

Geo*_*ica 6 c# linq caching entity-framework

接下来我发现了一个问题,一开始并没有出现如此可疑的问题.首先让我描述一下环境的大局:

我有一个关于表模块体系结构的域模型,它使用了使用Entity Framework 6.x编写的数据访问层.我的应用程序是一个Windows窗体应用程序,域模型和数据访问层都在客户端上运行,我使用的是.NET 4.0(幸运的是,EF 6仍然与.NET 4.0兼容)

我的目标:创建一个常用于组合框/查找的commmon命名符缓存.我们的用户将根据需要刷新此缓存(每个控件右侧都有一个按钮,提供可以刷新缓存的命名符).

到现在为止还挺好.我已经开始编写这个缓存了.简而言之,我的缓存由一组TableCaches <T>实例组成,每个实例都能够从内存或数据库中获取List(如果已经更改了某些内容).

接下来,假设您有这样的业务:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我看来,一个ideea开始成长:如果我可以做一个技巧,以便我的"容器"有时会提供虚假的集合,在我的缓存中找到的集合怎么办?但在这里我发现了最大的问题:.NET编译器在编译时做了一些棘手的事情:它检查你的集合是否是IQueriable <OfSomething>.如果为true,它会在IL代码调用内部烧掉处理表达式树的扩展方法,就像调用一样,否则它将简单地调用LINQ to Objects扩展方法.我也试过(仅用于研究目的)这个:

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();

    }
}
Run Code Online (Sandbox Code Playgroud)

并在我的模块中写道:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么我试过这个?我只希望运行时只要看到泛型类型参数实际上是IQueryable <T>就会发出正确的MSIL扩展方法调用.但不幸的是,这个天真的尝试证明了我忘记了一些与CLR和.NET编译器如何工作有关的深层次事情.我记得在.NET世界中,您应该分两步进行编译:第1步是正常编译,它还包含语法糖分辨率(解析类型推断,生成匿名类型,匿名函数转换为某些匿名类型的实际方法)或者也许是我们的类型等).不幸的是,在这个类别中找到了所有LINQ表达式.

第二步是在运行时找到,当CLR由于各种原因执行一些额外的MSIL代码emition:发出新的泛型类型,编译表达式树,用户代码在运行时创建新的类型/方法等.

我尝试过的最后一件事是......我说好的我会把所有的收藏都当作IQueryable.好处是,无论您将做什么(数据库调用或内存调用),编译器都会发出对Expression树LINQ扩展方法的调用.它工作但是它很慢,因为最终表达式每次都被编译(即使在内存集合中).代码如下:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,我写了下面的代码,但是..该死的,我复制我的代码!

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我该怎么办?.我也知道EF确实创建了一个缓存,但是生命周期很短(仅适用于上下文实例的生命周期),并且它不是在查询级别而是仅在行级别.如果我错了,请纠正我!

提前致谢.

Jon*_*nan 1

为什么不使用现有的带有缓存的库来创建自己的库?

EF+ 查询缓存

支持

  • 缓存
  • 缓存异步
  • 缓存标签
  • 内存过期

该库是开源的,因此如果您仍然想实现自己的缓存,您可能会找到一些好的信息。

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();
}
Run Code Online (Sandbox Code Playgroud)

免责声明:我是GitHub 上EF+项目的所有者