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确实创建了一个缓存,但是生命周期很短(仅适用于上下文实例的生命周期),并且它不是在查询级别而是仅在行级别.如果我错了,请纠正我!
提前致谢.
为什么不使用现有的带有缓存的库来创建自己的库?
支持
该库是开源的,因此如果您仍然想实现自己的缓存,您可能会找到一些好的信息。
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+项目的所有者