2015-10-20 21 views
6

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ı

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

:

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şturur

Denediğ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

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

, 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

cevap

1

Neden varolan bir kütüphaneyi önbelleğe almak yerine kullanmıyorsunuz?

EF+ Query Cache

Destek

  • Önbellek
  • Önbellek zaman uyumsuz
  • Önbellek Etiketi
  • Bellek Bitiş

Kütüphane açık kaynak olduğundan, kendi önbelleğinizi hala uygulamak istiyorsanız, bazı iyi bilgileri bulabilirsiniz.

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

Yasal Uyarı: Ben GitHub'dan

projesi EF+ sahibi değilim
0

IQueryable devralır böylece bunu biraz simplar yapabilirsiniz:

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

ama ne istediğinizi tüm sorguyu önbelleğe ise, ET bunu yapabilir ve ET6 düşük maliyetle içindedir. 1, 2 bu iki makaleyi görmelerine yardımcı olabilirler.