Recentelly我發現一個問題,在一開始沒有出現如此可疑。我首先描述一下我的環境的大圖:緩存域模型數據
我有一個領域模型,涉及使用實體框架6.x編寫的使用數據訪問層的表模塊體系結構。我的應用程序是一個Windows窗體應用程序,域模型和數據訪問層都運行在客戶端上,我使用.NET 4.0(幸運的是,EF 6仍與.NET 4.0兼容)。在組合框/查找中常用的通用命名符。該緩存將根據用戶的需求進行刷新(每個控件的右側都有一個按鈕,提供可以刷新緩存的命名器)。
到目前爲止,這麼好。我已經開始寫這個緩存。簡而言之,我的緩存由一組TableCaches集合組成,它們中的每一個都能夠從內存或數據庫中獲取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();
}
}
}
在我心中的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();
}
}
和我的模塊中寫道:
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);
}
}
}
爲什麼我已經試過嗎?我只是希望運行時會發現正確的MSIL擴展方法調用,只要它看到泛型類型參數實際上是IQueryable < T>。但不幸的是,這個天真的嘗試證明了我忘記了一些與CLR和.NET編譯器工作有關的深層次的東西。我記得在.NET世界中,你應該期待編譯分兩步:第一步是包含語法糖解析的正常編譯(解析類型推斷,生成匿名類型,匿名函數轉化爲一些匿名類型的真實方法或者可能在我們的類型上等)。不幸的是,在這個類別中,我發現了所有的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確實創建了一個緩存,但是生命週期很短(僅限於您的上下文實例的生命週期),它不在查詢級別,而只在行級別。糾正我,如果我錯了!
在此先感謝。從IEnumerable