Recentelly Başlangıçta çok şüpheli görünmeyen bir sorun buldum. Çevremdeki büyük resmi açıklayarak başlayayım:Önbelleğe alma etki alanı modeli verileri
Entity Framework 6.x kullanılarak yazılmış bir veri erişim katmanı tüketen Tablo Modülü mimarisine uygun bir etki alanı modelim var. Uygulamam bir windows form uygulaması, hem etki alanı modeli hem de veri erişim katmanı istemcide çalışır ve .NET 4.0 kullanıyorum (fortunatelly, EF 6 hala .NET 4.0 ile uyumludur)
Hedefim: Bir önbellek oluşturma Birleşik kutular/aramalarda yaygın olarak kullanılan commmon nomenclators. Bu önbellek, kullanıcılarımız tarafından talep üzerine tazelenecektir (önbelleği yenileyebilen bir gösterici sunan her kontrolün sağında bir düğme vardır).
Şimdiye kadar çok iyi. Bu önbelleği yazmaya başladım. Sadece birkaç kelimede, önbelleğim bir TableCaches < T> topluluğundan oluşuyor ve her biri bir listeyi hafızadan veya veritabanından alabilir (bir şey değişmişse). , Koleksiyon benim "Konteyner" bazen sahte koleksiyonları verir böylece bir hile yapabilirim Ne: Bir ideea büyümeye başladı
Zihnimdepublic 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();
}
}
}
:
Sonraki, böyle bir iş olduğunu düşünün önbelleğimde bulundu? Ama burada en büyük sorunu buldum: .NET derleyicisi derleme zamanında zor bir şey yapar: Koleksiyonlarınızın IQueriable < OfSomething> olup olmadığını kontrol eder. Doğruysa, IL kod çağrılarının içine, ifade ağacı gibi aramalarla ilgilenen genişletme yöntemlerine girer, aksi takdirde LINQ to Objects uzantı yöntemlerini çağırır. Ben de bu (sadece araştırma amaçlı) çalıştı:
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();
}
}
ve benim modülünde yazdı:
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);
}
}
}
Neden bu denedim? Çalışma zamanının, jenerik tip parametrelerinin aslında IQueryable < T> olduğunu gördüğünde, doğru MSIL genişletme yöntemleri çağrılarını göndereceğini umuyordum. Ancak bu naif nafaka, CLR ve .NET derleyicisinin nasıl çalıştığıyla ilgili bazı derin şeyleri unuttuğumu kanıtladı. .NET dünyasında, iki adımda derlemeyi beklemeliyim: 1. adım, sözdizimsel şeker çözünürlüğünü içeren normal derlemedir (tür çıkarımı çözülür, anonim türler üretilir, anonim işlevler bazı anonim türlerde gerçek yöntemlere dönüştürülür) ya da belki türlerimizde, vb.). Benim için maalesef, bu kategoride tüm LINQ ifadeleri bulunur.
CLR çeşitli nedenlerden kaynaklanan bazı ek MSIL kod ihraç edilmesi yapar, ikinci aşama, zamanında bulunur: bir yeni genel tipi, yayılan alır ifade ağaçlarını derlenir, kullanıcı kodu vs. zamanında yeni tip/yöntemleri,
oluştururDenediğim son şey ... Tamam dedim Tüm koleksiyonları IQueryable olarak ele alacağım. Güzel olan şey, ne yapacağınız önemli değil (veritabanı çağrıları veya hafıza çağrılarında) derleyicinin İfade ağaçları LINQ uzatma yöntemlerini çağırmasıdır. İşe yarayacak, ama yavaş yavaş, çünkü ifade, her seferinde (bellek koleksiyonlarında bile) her zaman derleniyor. kod aşağıda: çalıştığını ancak .. benim kod çoğaltmak, kahretsin altında
Sonundapublic 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();
}
}
, kodu yazdım !!!
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();
}
}
ne yapmalıyım?. Ayrıca EF'in bir önbellek yaptığını biliyorum, ancak ömür boyu kısadır (yalnızca içerik örneğinizin ömrü boyunca) ve sorgu düzeyinde değil, yalnızca satır düzeyinde. Eğer Yanlışsam beni düzelt!
Şimdiden teşekkürler. IEnumerable
den