缓存域模型数据

时间:2015-10-20 11:56:54

标签: c# entity-framework linq caching

Recentelly我发现一个问题,一开始并没有显得如此可疑。首先让我描述一下环境的大局:

我有一个关于表模块体系结构的域模型,它使用使用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();
        }
    }
}

在我看来,一个想法开始成长:如果我可以做一个技巧,以便我的&#34;容器&#34;有时会提供虚假的集合,在我的缓存中找到的集合?但在这里我发现了最大的问题:.NET编译器在编译时做了一些棘手的事情:它检查你的集合是否是IQueriable&lt; OfSomething&gt;。如果为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();

    }
}

并在我的模块中写道:

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

为什么我试过这个?我只希望运行时只要看到泛型类型参数实际上是IQueryable&lt;就会发出正确的MSIL扩展方法调用。 T&GT ;.但不幸的是,这个天真的尝试证明了我忘记了一些与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();
    }
}

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

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

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

提前致谢。

2 个答案:

答案 0 :(得分:1)

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

EF+ Query 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();
}

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

答案 1 :(得分:0)

IQueryable继承自IEnumerable所以你可以做得有点简单:

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

但如果您想要的只是缓存查询,那么ET可以做到这一点,并且在ET6中的成本很低。看到这两篇文章12他们可以帮助你。