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确实会生成缓存,但生命周期很短(仅适用于上下文实例的生命周期),并且它不在查询级别,而只在行级别。如果我错了,请纠正我!
提前致谢。
答案 0 :(得分:1)
为什么不使用现有的带缓存的库来制作自己的库?
<强>支持强>
该库是开源的,因此如果您仍想要实现自己的缓存,可能会找到一些好的信息。
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();
}