Depo modelini KULLANMAYIN, ORM'yi olduğu gibi kullanın (EF)


96

Her zaman Depo modelini kullandım, ancak son projem için onun kullanımını ve "Unit Of Work" uygulamamı mükemmelleştirip mükemmelleştiremeyeceğimi görmek istedim. Araştırmaya başladıkça kendime şu soruyu sormaya başladım: "Gerçekten buna ihtiyacım var mı?"

Şimdi bunların hepsi Stackoverflow üzerine birkaç yorumla başlıyor ve Ayende Rahien'in blogundaki yazısının izini sürüyor.

Bu muhtemelen sonsuza dek konuşulabilir ve farklı uygulamalara bağlıdır. Bilmek istediğim şey

  1. bu yaklaşım bir Entity Framework projesi için uygun olur mu?
  2. Bu yaklaşımı kullanan iş mantığı hala bir hizmet katmanında mı yoksa uzantı yöntemlerinde mi (aşağıda açıklandığı gibi, uzatma yönteminin NHib oturumunu kullandığını biliyorum)?

Bu, uzantı yöntemleri kullanılarak kolayca yapılır. Temiz, basit ve tekrar kullanılabilir.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

Bu yaklaşımı kullanarak ve NinjectDI olarak, Contextbir arayüz oluşturmam ve bunu kontrolörlerime enjekte etmem gerekir mi?

Yanıtlar:


105

Pek çok yoldan gittim ve farklı projelerde birçok depo uygulaması yaptım ve ... Havluyu attım ve bundan vazgeçtim, işte nedeni.

İstisna için kodlama

Veritabanınızın bir teknolojiden diğerine değişmesi için% 1 şans için kod yazıyor musunuz? İşletmenizin gelecekteki durumunu düşünüyorsanız ve evet derseniz, bu bir olasılık o zaman a) başka bir DB teknolojisine geçiş yapmak için çok paraları olmalı veya b) eğlence için bir DB teknolojisi seçiyorsunuz veya c ) kullanmaya karar verdiğiniz ilk teknolojide bir şeyler korkunç bir şekilde ters gitti.

Zengin LINQ sözdizimini neden bir kenara atalım?

LINQ ve EF, nesne grafiklerini okumak ve üzerinde gezinmek için onunla düzgün şeyler yapabilmeniz için geliştirildi. Bunu yapmak için size aynı esnekliği verebilecek bir depo oluşturmak ve sürdürmek korkunç bir görevdir. Deneyimlerime göre, herhangi bir depo oluşturduğumda , sorguları daha performanslı hale getirmek ve / veya veritabanına yapılan isabet sayısını azaltmak için HER ZAMAN depo katmanına iş mantığı sızıntısı yaşadım .

Yazmam gereken bir sorgunun her bir permütasyonu için bir yöntem oluşturmak istemiyorum. Saklanmış prosedürler de yazabilirim. Ben istemiyorum GetOrder, GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity, GetOrderByUserId, vb ... Sadece ana varlık ve traversler almak istiyorum gibi nesne grafiği dahil Öyle ediniz.

Depo örneklerinin çoğu saçmalıktır

Bir blog gibi GERÇEKTEN çıplak kemikler geliştirmediğiniz sürece, sorgularınız hiçbir zaman internette arşiv modelini çevreleyen örneklerin% 90'ı kadar basit olmayacaktır. Bunu yeterince vurgulamıyorum! Bu, anlamak için çamurda sürünmesi gereken bir şey. Yarattığınız mükemmel düşünülmüş deponuzu / çözümü bozan bir sorgu her zaman olacaktır ve kendinizi ikinci kez tahmin ettiğiniz ve teknik borcun / erozyonun başladığı noktaya kadar değil.

Beni birim test etme kardeşim

Peki bir depom yoksa birim testi ne olacak? Nasıl dalga geçeceğim? Basit değilsin. Her iki açıdan da bakalım:

Depo yok - DbContextBir IDbContextveya diğer bazı hileleri kullanarak alay edebilirsiniz, ancak o zaman LINQ to Objects değil, LINQ to Objects birim testi yaparsınız. çünkü sorgu çalışma zamanında belirlenir ... Tamam, bu iyi değil! Yani şimdi bunu kapsamak entegrasyon testine kalmış.

Depo ile - Artık depolarınızla alay edebilir ve aradaki katmanları birim test edebilirsiniz. Harika değil mi? Aslında değil ... Sorguları daha performanslı ve / veya veritabanına daha az isabetli hale getirmek için depo katmanına mantığı sızdırmanız gereken yukarıdaki durumlarda, birim testleriniz bunu nasıl kapsayabilir? Artık repo katmanında ve test etmek istemiyorsun IQueryable<T>değil mi? Ayrıca dürüst olalım, birim testleriniz 20 satır .Where()cümlesine sahip sorguları kapsamaz ve.Include()'bir grup ilişki ve tüm diğer şeyleri yapmak için veritabanını tekrar vurur, blah, blah, blah her neyse, çünkü sorgu çalışma zamanında üretilir. Ayrıca, üst katmanların kalıcılığını cahil tutmak için bir depo oluşturduğunuzdan, eğer şimdi veritabanı teknolojinizi değiştirmek istiyorsanız, üzgünüm birim testleriniz çalışma zamanında aynı sonuçları garanti etmeyecek, entegrasyon testlerine geri dönecek. Yani deponun tüm noktası tuhaf görünüyor ..

2 sent

EF'yi düz saklı yordamlar (toplu girişler, toplu silmeler, CTE'ler vb.) Üzerinden kullanırken zaten çok sayıda işlevsellik ve sözdizimi kaybediyoruz, ancak aynı zamanda C # kodluyorum, bu nedenle ikili yazmam gerekmiyor. EF'i kullanıyoruz, böylece farklı sağlayıcıları kullanma ve birçok şeyin yanı sıra nesne grafikleriyle güzel bir şekilde ilişkili bir şekilde çalışma olanağına sahip olabiliriz. Bazı soyutlamalar faydalıdır ve bazıları değildir.


18
Bunları birim test edebilmek için havuzlar oluşturmazsınız. İş mantığını birim testi yapabilmek için havuzlar oluşturursunuz . Sorguların çalıştığından emin olmaya gelince: sadece mantık içerdiğinden ve herhangi bir iş içermediğinden depolar için entegrasyon testleri yazmak çok daha kolaydır.
jgauffin

16
Coding for the exception: Depoları kullanmak veritabanı motorunu değiştiremez. Bu, işi kalıcılıktan ayırmakla ilgili.
jgauffin

2
Bunların hepsi, arkasında büyük bir gerçek olan çok geçerli noktalardır. Ancak eksik olan şey, LINQ'nun tutarlı bir konumla sınırlandırılmak yerine bir uygulama hakkında yayıldığının kod temeli sayfalarda SQL çağrılarının EF eşdeğerini oluşturduğunun fark edilmesidir. Her LINQ sorgusu, bir uygulamadaki potansiyel bir bakım noktasıdır ve ne kadar çok varsa (ve ne kadar yaygınsa), bakım maliyetleri ve riskleri o kadar yüksek olur. Bir varlığa 'silinmiş' bir bayrak eklediğinizi ve varlığın sorgulandığı büyük bir uygulamadaki her bir yeri, her birini değiştirmek zorunda olduğunuzu hayal edin ...
DVK

2
Bunun kısa görüşlü ve yorgun olduğunu düşünüyorum. Neden depoya mantık sızdırıyorsun? Ve eğer öyleyse, neden önemli olsun? Bu bir veri uygulamasıdır. Tek yaptığımız LINQ'yu deponun arkasına saklayarak kodun geri kalanından duvar örmek. Test etme diyorsun ama sonra test edememeyi yapmaya karşı bir argüman olarak kullanıyorsun. Bu yüzden repo yapın, IQueryable'ı ifşa etmeyin ve test etmeyin. En azından diğer her şeyi veri uygulamasından ayrı olarak test edebilirsiniz. Ve bu% 1'lik bir db değişim şansı hala $ cinsinden çok yüksek.
Sinaesthetic

5
Bu cevap için +1. Entity Framework Core ile gerçekten depolara ihtiyacımız olmadığını anladım. DbSetOlduğu depo ve DbContextbir İş birimi . ORM bunu bizim için zaten yaptığında neden havuz modelini uyguluyoruz! Test için, sağlayıcıyı olarak değiştirmeniz yeterlidir InMemory. Ve testlerinizi yapın! MSDN'de iyi belgelenmiştir.
Muhammed Nureldin

49

Depo modeli bir soyutlamadır . Amacı, karmaşıklığı azaltmak ve kodun geri kalanını cahil kılmaktır. Bonus olarak entegrasyon yerine birim testleri yazmanıza izin verir testleri .

Sorun şu ki, birçok geliştirici modelin amacını anlamıyor ve arayan kişiye kadar (tipik olarak açığa çıkararak IQueryable<T>) kalıcı spesifik bilgileri sızdıran depolar oluşturuyor . Bunu yaparak, OR / M'yi doğrudan kullanmaktan hiçbir fayda sağlamazlar.

Başka bir yanıtı ele almak için güncelleyin

İstisna için kodlama

Depoları kullanmak, kalıcılık teknolojisini değiştirebilmekle ilgili değildir (yani veritabanını değiştirmek veya bunun yerine bir web hizmeti kullanmak vb.). Karmaşıklığı ve birleştirmeyi azaltmak için iş mantığını kalıcılıktan ayırmakla ilgilidir.

Birim testleri ve entegrasyon testleri

Depolar için birim testleri yazmazsınız. dönem.

Ancak depoları (veya ısrarla iş arasında başka herhangi bir soyutlama katmanını) tanıtarak iş mantığı için birim testleri yazabilirsiniz. yani, yanlış yapılandırılmış bir veritabanı nedeniyle testlerinizin başarısız olması konusunda endişelenmenize gerek yoktur.

Sorgulara gelince. LINQ kullanıyorsanız, tıpkı depolarla yaptığınız gibi sorgularınızın da çalıştığından emin olmalısınız. ve bu entegrasyon testleri kullanılarak yapılır.

Aradaki fark, işinizi LINQ ifadeleriyle karıştırmadıysanız, bunun sizin kalıcılık kodunuzun başarısız olduğundan ve başka bir şey olmadığından% 100 emin olabilirsiniz.

Testlerinizi analiz ederseniz, karışık endişeleriniz yoksa (yani LINQ + İş mantığı) çok daha temiz olduklarını göreceksiniz.

Depo örnekleri

Örneklerin çoğu saçmalık. bu çok doğru. Bununla birlikte, herhangi bir tasarım desenini Google'da ararsanız, birçok berbat örnek bulacaksınız. Bu, bir kalıp kullanmaktan kaçınmanın bir nedeni değildir.

Doğru bir depo uygulaması oluşturmak çok kolaydır. Aslında, yalnızca tek bir kurala uymanız gerekir:

İhtiyaç duyduğunuz ana kadar arşiv sınıfına hiçbir şey eklemeyin.

Kodlayıcılar Bir çok genel bir depo yapmak için tembel ve çalışır ve bunda da yöntemlerin bir çok temel bir sınıfını kullanın olabilir gerekir. YAGNI. Depo sınıfını bir kez yazarsınız ve uygulama yaşadığı sürece saklarsınız (yıllar olabilir). Neden tembel olarak mahvediyorsun? Herhangi bir temel sınıf mirası olmadan temiz tutun. Okumayı ve bakımını çok daha kolay hale getirecek.

(Yukarıdaki ifade bir kılavuzdur ve bir yasa değildir. Temel bir sınıf çok iyi motive edilebilir. Bunu eklemeden önce düşünün, böylece doğru nedenlerle ekleyin)

Eski şeyler

Sonuç:

İşletme kodunuzda LINQ ifadeleri olmasını istemiyorsanız veya birim testleri umursamıyorsanız, Entity Framework'ü doğrudan kullanmamak için bir neden göremiyorum.

Güncelleme

Hem depo kalıbı hem de "soyutlamanın" gerçekte ne anlama geldiği hakkında blog yazdım: http://blog.gauffin.org/2013/01/repository-pattern-done-right/

Güncelleme 2

20'den fazla alan içeren tek varlık türü için, herhangi bir permütasyon kombinasyonunu desteklemek için sorgu yöntemini nasıl tasarlayacaksınız? Aramayı sadece isme göre sınırlamak istemezsiniz, navigasyon özellikleriyle aramaya ne dersiniz, tüm siparişleri belirli fiyat kodlu ürünle listeleyin, 3 seviye navigasyon özelliği araması. IQueryableİcat edilmesinin tüm nedeni , veri tabanına karşı herhangi bir arama kombinasyonu oluşturabilmekti. Teoride her şey harika görünüyor, ancak kullanıcının ihtiyacı teorinin üstüne çıkıyor.

Yine: 20'den fazla alana sahip bir varlık yanlış bir şekilde modellenmiştir. Bu bir TANRI varlığı. Yerle bir etmek.

Bunun IQueryablesorgulamak için yapılmadığını tartışmıyorum. Sızdıran olduğu için Depo deseni gibi bir soyutlama katmanı için doğru olmadığını söylüyorum . % 100 eksiksiz LINQ To Sql sağlayıcısı yoktur (EF gibi).

Hepsinde istekli / yavaş yüklemenin nasıl kullanılacağı veya SQL "IN" ifadelerinin nasıl yapılacağı gibi uygulamaya özgü şeyler vardır. Arşivde açığa IQueryableçıkmak, kullanıcıyı tüm bunları bilmeye zorlar. Dolayısıyla, veri kaynağını soyutlama girişiminin tamamı tam bir başarısızlıktır. Doğrudan OR / M'yi kullanmaya göre herhangi bir fayda sağlamadan karmaşıklık katarsınız.

Depo modelini doğru uygulayın veya hiç kullanmayın.

(Eğer gerçekten büyük varlıkları ele almak istiyorsanız, Depo modelini Spesifikasyon modeli ile birleştirebilirsiniz . Bu size aynı zamanda test edilebilir tam bir soyutlama sağlar.)


6
IQueryable'ın açığa çıkmaması sınırlı aramaya yol açar ve insanlar farklı türden sorgular için daha fazla Get yöntemi oluşturur ve sonunda depoyu daha karmaşık hale getirir.
Akash Kava

3
Temel sorunu hiç ele almadınız: IQueryable'ı bir depo aracılığıyla açığa çıkarmak tam bir soyutlama değildir.
jgauffin

1
Yürütülecek tüm gerekli altyapıyı içeren bir sorgu nesnesine sahip olmak, imo yapmanın yoludur. Ona arama terimleri olan alanları verirsiniz ve sonuçların bir listesini verir. QO ile içsel olarak istediğinizi yapabilirsiniz. Ve bir arayüz olduğu için çok kolay test edilir. Yukarıdaki yazıma bakın. Bu en iyisi.
h.alex

2
Kişisel olarak, IQueryable <T> arayüzünü bir Repository sınıfına uygulamanın, üyelerinden birinde temel kümeyi ifşa etmek yerine mantıklı olduğunu düşünüyorum.
dark_perfect

3
@yat: Toplam kök başına bir depo. Ama imho, toplu kök ve tabloların toplamı değil, yalnızca kök ve toplamaları bir araya getirir . Gerçek depolama yalnızca bir veya çok sayıda tablo kullanabilir, yani her bir toplam ve bir tablo arasında bire bir eşleme olmayabilir. Karmaşıklığı azaltmak ve temeldeki depolamanın tüm bağımlılıklarını kaldırmak için depoları kullanıyorum.
jgauffin

29

IMO, hem Repositorysoyutlama hem de soyutlamanın UnitOfWorkanlamlı herhangi bir gelişmede çok değerli bir yere sahiptir. İnsanlar uygulama ayrıntılarını tartışacaklar, ancak bir kedinin derisini yüzmenin birçok yolu olduğu gibi, bir soyutlamayı uygulamanın da birçok yolu vardır.

Sorunuz özellikle kullanıp kullanmamak ve nedenidir.

Eğer hiç şüphesiz fark var gibi zaten, İdare Framework yerleşik hem bu kalıpları var DbContextolduğunu UnitOfWorkve DbSetbir Repository. Genellikle sınıflarınız ve temeldeki veri erişim uygulamaları arasında kolaylık sağladıkları için UnitOfWorkveya Repositorykendilerini tek tek test etmeniz gerekmez . Kendinizi tekrar tekrar yapmanız gereken şey, hizmetlerinizin mantığını birim test ederken bu iki soyutlamayla dalga geçmektir.

Testi yapan mantık ile test edilen mantık arasına kod bağımlılıkları katmanları ekleyerek (sizin kontrol etmediğiniz) harici kütüphanelerle alay edebilir, sahte olabilir veya her şeyi yapabilirsiniz .

Küçük noktaya Yani olmasıdır için kendi soyutlama sahip UnitOfWorkve Repositoryünite testleri alay size maksimum kontrol ve esneklik sağlar.

Hepsi çok iyi, ama benim için bu soyutlamaların gerçek gücü, Görünüşe Dayalı Programlama tekniklerini uygulamak ve SOLID ilkelerine bağlı kalmak için basit bir yol sağlamasıdır .

Yani sizde IRepository:

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

Ve uygulaması:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

Şimdiye kadar sıra dışı bir şey yok ama şimdi biraz günlük kaydı eklemek istiyoruz - bir günlük Dekoratörüyle kolay .

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

Hepsi tamamlandı ve mevcut kodumuzda değişiklik yapılmadan . İstisna işleme, veri önbelleğe alma, veri doğrulama veya her neyse ve tasarım ve oluşturma sürecimiz boyunca ekleyebileceğimiz çok sayıda başka kesişen endişeler var, mevcut kodlarımızdan herhangi birini değiştirmeden basit özellikler eklememizi sağlayan en değerli şeyimiz. bizim IRepositorysoyutlamamızdır .

Şimdi, StackOverflow'da bu soruyu birçok kez gördüm - "Entity Framework'ü çok kiracılı bir ortamda nasıl çalıştırırsınız?".

https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant

Bir Repositorysoyutlamanız varsa, cevap "bir dekoratör eklemek kolaydır"

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

IMO, her zaman bir avuç yerden daha fazla yerde referans verilecek herhangi bir 3. taraf bileşeninin üzerine basit bir soyutlama koymalısınız. Bu açıdan bakıldığında, bir ORM, kodumuzun çoğunda referans verildiği için mükemmel adaydır.

Normalde birisi "neden Repositorybu veya üçüncü taraf kitaplığı üzerine bir soyutlama yapmalıyım?" Dediğinde akla gelen cevap "neden olmasın?"

PS Dekoratörleri, SimpleInjector gibi bir IoC Container kullanarak uygulamak son derece kolaydır .

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}

İlginç bir şekilde, bu cevap 2020'de hala geçerli. O zamandan beri pek bir şey değişmedi. RP'nin yerini alacak pek fazla yöntem yok
Volkan Güven

11

Her şeyden önce, bazı cevapların önerdiği gibi, EF'in kendisi bir depo modelidir, sadece onu depo olarak adlandırmak için daha fazla soyutlama oluşturmaya gerek yoktur.

Birim Testleri için Mockable Depo, gerçekten buna ihtiyacımız var mı?

İş mantığımızı doğrudan SQL test DB'sine karşı test etmek için EF'in birim testlerinde DB'yi test etmesine izin verdik. Herhangi bir depo modeline sahip olmanın hiçbir faydası görmüyorum. Test veritabanına karşı birim testleri yapmak gerçekten yanlış olan nedir? Toplu işlemler mümkün olmadığı için ham SQL yazıyoruz. Bellekteki SQLite, gerçek veritabanına karşı birim testleri yapmak için mükemmel bir adaydır.

Gereksiz Soyutlama

Gelecekte EF'yi NHbibernate vb. Veya başka bir şeyle kolayca değiştirebilmeniz için depo oluşturmak ister misiniz? Harika bir plan gibi görünüyor, ancak gerçekten uygun maliyetli mi?

Linq birim testlerini mi öldürüyor?

Nasıl öldürebileceğine dair herhangi bir örnek görmek isterim.

Bağımlılık Enjeksiyonu, IoC

Vay canına, bunlar harika kelimeler, elbette teoride harika görünüyorlar, ancak bazen harika tasarım ile harika çözüm arasında bir tercih yapmak zorunda kalıyorsunuz. Bunların hepsini kullandık ve sonunda hepsini çöpe atıp farklı bir yaklaşım seçtik. Boyuta Karşı Hıza (Kodun boyutu ve Geliştirme hızı) gerçek hayatta çok önemlidir. Kullanıcılar esnekliğe ihtiyaç duyar, kodunuzun DI veya IoC açısından tasarım açısından harika olup olmadığını umursamazlar.

Visual Studio oluşturmadığınız sürece

Visual Studio veya Eclipse gibi birçok kişi tarafından geliştirilecek karmaşık bir program oluşturuyorsanız ve oldukça özelleştirilebilir olması gerekiyorsa, tüm bu harika tasarımlara ihtiyaç vardır. Tüm büyük geliştirme modelleri, bu IDE'lerin yıllarca geliştirilmesinden sonra ortaya çıktı ve tüm bu harika tasarım modellerinin çok önemli olduğu yerde geliştiler. Ancak, basit bir web tabanlı maaş bordrosu veya basit bir iş uygulaması yapıyorsanız, yalnızca 100'lerce kullanıcıya dağıtılacağı bir milyon kullanıcı için oluşturmak için zaman harcamak yerine, gelişiminizde zamanla gelişmeniz daha iyidir.

Filtrelenmiş Görünüm Olarak Depo - ISecureRepository

Diğer taraftan, havuz, mevcut kullanıcıya / role göre gerekli doldurucuyu uygulayarak verilere erişimi koruyan, EF'nin filtrelenmiş bir görünümü olmalıdır.

Ancak bunu yapmak, depoyu daha da karmaşık hale getirir, çünkü bakımı büyük bir kod tabanıyla sonuçlanır. İnsanlar, farklı kullanıcı türleri veya varlık türlerinin kombinasyonu için farklı depolar oluşturur. Sadece bu değil, aynı zamanda birçok DTO'yla da karşımıza çıkıyor.

Aşağıdaki yanıt, tüm sınıflar ve yöntemler kümesi oluşturmadan Filtrelenmiş Depo'nun örnek bir uygulamasıdır. Soruya doğrudan cevap vermeyebilir ancak bir tanesini türetmede faydalı olabilir.

Sorumluluk reddi: Entity REST SDK'nın yazarıyım.

http://entityrestsdk.codeplex.com

Yukarıdakileri akılda tutarak, CRUD işlemleri için filtreleri tutan SecurityContext'e dayalı filtrelenmiş görünüm deposu oluşturan bir SDK geliştirdik. Ve sadece iki tür kural herhangi bir karmaşık işlemi basitleştirir. Birincisi varlığa erişim, diğeri ise mülkiyet için Okuma / Yazma kuralı.

Bunun avantajı, farklı kullanıcı türleri için iş mantığını veya depoları yeniden yazmamanız, onlara erişimi engellemeniz veya vermenizdir.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

Bu LINQ Kuralları, her işlem için SaveChanges yönteminde Veritabanına göre değerlendirilir ve bu Kurallar Veritabanının önünde Güvenlik Duvarı görevi görür.


3
DB'ye karşı birim testi, testleriniz için ekstra dış gereksiniminiz olduğu anlamına gelir. Bu DB çalışmazsa veya veriler temizlenirse veya bu DB'ye herhangi bir şey olursa, testleriniz başarısız olur. Bu istenmez. IQueryable'ı açığa çıkaran depoların kurulumu yaklaşık 2 dakika sürer. Burada zaman kaybı yok. DI neden uzun sürdü? Tüm bunlar dakika sürer. Tüm bunların, hizmet katmanımdaki karmaşık sorgularımı test etmek için harika çalıştığını söyleyeceğim. Bağlanmak için bir veritabanına ihtiyaç duymamak çok güzeldi. Nuget'ten alaycı bir çerçeve almak yaklaşık bir dakika sürdü. Bu şeyler hiç zaman almıyor.
user441521

@ user441521 IQueryable ile depoların kurulumu 2 dakika? hangi dünyada yaşıyor olursanız olun, canlı sitemizdeki her asp.net talebi milisaniyeler içinde karşılanmaktadır. Alay etme ve numara yapma vb. Koda daha fazla karmaşıklık katar, toplam zaman kaybıdır. Birim, iş mantığı birimi olarak tanımlanmadığında birim testleri işe yaramaz.
Akash Kava

7

Hangi yöntemin doğru olduğu konusunda çok fazla tartışma var, bu yüzden her ikisinin de kabul edilebilir olduğuna bakıyorum, bu yüzden en çok hangisini sevdiğimi kullanıyorum (Hangisi depo değil, UoW).

EF'de UoW, DbContext aracılığıyla uygulanır ve DbSet'ler depolardır.

Veri katmanıyla nasıl çalışılacağına gelince, doğrudan DbContext nesnesi üzerinde çalışıyorum, karmaşık sorgular için sorgu için yeniden kullanılabilecek uzatma yöntemleri yapacağım.

Ayende'nin CUD işlemlerini soyutlamanın ne kadar kötü olduğuna dair bazı paylaşımları olduğuna inanıyorum.

DI için bir IoC konteyneri kullanabilmek için her zaman bir arayüz oluşturuyorum ve bağlamım ondan miras kalıyor.


Peki uzatma yöntemleri ne kadar kapsamlı? Uzantımdaki başka bir varlığın durumunu almam gerektiğini varsayalım. Şu anda benim en büyük endişem bu. Bazı uzatma yöntemleri örneklerini göstermenin sakıncası var mı?
Dejan.S

ayende.com/blog/153473/… ve ayende.com/blog/153569/… . (Bunlar, s # arp lite adlı bir mimarinin (Çerçeve?) İncelemeleridir. Çoğunlukla iyi, ancak depolara ve CUD soyutlamalarına katılmıyor).
Josh

NHibernate esaslıdır. EF kullanarak herhangi bir örneğiniz yok mu? Ve yine başka bir varlığı çağırmam gerektiğinde, bu, statik genişletme yönteminde en iyi nasıl yapılır?
Dejan.S

3
Etki alanı nesnenizin bir özelliğinin veritabanınızda depolanmayan verilerle ıslatılması gerekene kadar bu her şey yolunda ve iyidir; veya şişirilmiş ORM'nizden daha performanslı bir teknolojiye geçmeniz gerekir. OOPS! Bir ORM, sadece bir havuzun yerine geçmez, birinin uygulama ayrıntısıdır.
cdaq

2

EF üzerinde en çok geçerli olan bir Depo Modeli değildir. Bu bir Cephe modelidir (EF yöntemlerine yapılan çağrıları daha basit, kullanımı daha kolay sürümlere ayırır).

EF, Depo Modelini (ve aynı zamanda Çalışma Birimi modelini) uygulayandır. Diğer bir deyişle, EF, kullanıcının SQLServer ile uğraştıklarına dair hiçbir fikri kalmaması için veri erişim katmanını soyutlayan tek yerdir.

Ve bu noktada, EF üzerindeki çoğu "depo", yalnızca aynı imzalara sahip olma noktasına kadar, oldukça basit bir şekilde, EF'deki tek yöntemlerle eşleştirdikleri için iyi Facades bile değildir.

Öyleyse, bu sözde "Depo" modelini EF üzerinden uygulamanın iki nedeni, daha kolay teste izin vermek ve ona "hazır" çağrıların bir alt kümesini oluşturmaktır. Kendi başlarına fena değil, ama açıkça bir Depo değil.


1

Linq günümüzde bir 'Depo'dur.

ISession + Linq zaten depodur ve ne GetXByYyöntemlere ne de QueryData(Query q)genellemeye ihtiyacınız var . DAL kullanımına biraz paranoyak olarak, hala depo arayüzünü tercih ediyorum. (Sürdürülebilirlik açısından bakıldığında, yine de belirli veri erişim arayüzleri üzerinde biraz cepheye sahip olmamız gerekiyor).

İşte kullandığımız depo - bizi nhibernate'in doğrudan kullanımından ayırıyor, ancak linq arabirimi sağlıyor (sonunda yeniden düzenlemeye tabi olan istisnai durumlarda ISession erişimi olarak).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}

Hizmet katmanı için ne yaparsınız?
Dejan.S

Denetleyiciler salt okunur veriler için depoyu sorguluyor, neden fazladan katman ekleyelim? Diğer olasılık, gittikçe daha fazla hizmet seviyesi deposu olma eğiliminde olan "ContentService" kullanmaktır: GetXByY, vb. Değişiklik işlemleri için - uygulama hizmetleri sadece kullanım durumlarına göre soyutlamadır - BL kullanırlar ve özgürce repo
yaparlar

İş mantığı için servis katmanı yapmaya alışkınım. ContentService ile sizi takip ettiğimden pek emin değilim, lütfen detaylandırın. Yardımcı sınıfları "hizmet katmanı" olarak yapmak kötü bir uygulama olur mu?
Dejan.S

"Hizmet katmanı" derken "uygulama hizmetleri" ni kastettim. Depoyu ve etki alanı katmanının diğer herhangi bir genel bölümünü kullanabilirler. "Hizmet katmanı" kötü bir uygulama değildir, ancak yalnızca List <X> sonucunu sağlamak için XService sınıfını yapmaktan kaçınırım. Yorum alanı, hizmetleri ayrıntılı olarak açıklamak için çok kısa görünüyor, üzgünüm.
mikalai

Farz edelim ki, bir sepet hesaplaması diyelim ve bir hesaplama yapmak için uygulama ayarları parametrelerini ve belirli müşteri parametrelerini almanız gerekiyor ve bu, uygulamanın birçok yerinde yeniden kullanılıyor. Bu durumu nasıl idare ediyorsunuz? yardımcı sınıf veya uygulama hizmeti?
Dejan.S

1

Depo bana kalıcılık katmanı uzak soyutlayarak hakkında çoğunlukla için şu anda (veya bununla tek seçer söylersek).

Bunu sorgu nesneleriyle birleştirilmiş olarak kullanıyorum, böylece uygulamalarımda herhangi bir özel teknolojiyle bir bağlantım yok. Ayrıca test etmeyi çok kolaylaştırır.

Yani, sahip olma eğilimindeyim

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

Muhtemelen temsilci olarak geri aramalarla zaman uyumsuz yöntemler ekleyin. Deponun genel olarak uygulanması kolaydır , bu nedenle uygulamadan uygulamaya bir uygulama satırına dokunamıyorum. Eh, bu en azından NH kullanırken doğrudur, bunu EF ile de yaptım ama EF'ten nefret etmeme neden oldu. 4. Konuşma, bir işlemin başlangıcıdır. Depo örneğini birkaç sınıf paylaşıyorsa çok güzel. Ayrıca NH için benim uygulamamdaki bir repo ilk talepte açılan bir seansa eşittir.

Ardından Sorgu Nesneleri

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

NH'de yalnızca ISession'ı geçmek için kullanıyorum yapılandırma için. EF'de az veya çok anlam ifade etmiyor.

Örnek bir sorgu olabilir .. (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

Bir EF sorgusu yapmak için, bağlamın oturumda değil Özet tabanında olması gerekir. Ama tabii ki ifc aynı olacaktır.

Bu şekilde, sorguların kendisi özetlenir ve kolayca test edilebilir. Hepsinden iyisi, kodum yalnızca arayüzlere dayanıyor. Her şey çok temiz. Etki alanı (iş) nesneleri sadece, örneğin etki alanı nesnesindeki zorlukla test edilebilen ve veri erişim (sorgu) kodunu karıştıran etkin kayıt modelini kullanırken olduğu gibi sorumlulukların karıştırılmamasıdır ve bunu yaparken endişeleri karıştırır ( kendisi ??). Veri aktarımı için POCO'lar oluşturmakta hala herkes özgürdür.

Sonuç olarak, bu yaklaşımla çok fazla kod yeniden kullanımı ve basitlik sağlanıyor, hayal edebileceğim hiçbir şey yok. Herhangi bir fikir?

Ayende'ye harika paylaşımları ve sürekli bağlılığı için çok teşekkürler. Buradaki fikirleri (sorgu nesnesi), benim değil.


1
Kalıcı varlıklar (POCO'larınız) işletme / etki alanı varlıkları DEĞİLDİR. Ve deponun amacı, iş (herhangi bir) katmanını kalıcılıktan ayırmaktır.
MikeSW

Bağlantıyı göremiyorum. POCO kısmına biraz katılıyorum, ama umursama. Sizi 'gerçek' POCO'lara sahip olmaktan alıkoyacak ve bu yaklaşımı kullanmaya devam edecek hiçbir şey yok.
h.alex

1
Varlıkların aptal POCO'lar olması gerekmez. Aslında, iş mantığını Varlıklara modellemek, DDD kalabalığının her zaman yaptığı şeydir. Bu geliştirme tarzı NH veya EF ile çok iyi uyum sağlar.
chris

1

Benim için, nispeten az faktör içeren basit bir karar. Faktörler:

  1. Depolar, alan sınıfları içindir.
  2. Bazı uygulamalarımda, alan sınıfları kalıcılık (DAL) sınıflarımla aynı, bazılarında ise değil.
  3. Aynı olduklarında, EF bana zaten Depolar sağlıyor.
  4. EF, geç yükleme ve IQueryable sağlar. Bunları sevdim.
  5. Özetleme / 'yüzleştirme' / depoyu EF üzerinden yeniden uygulama genellikle tembel ve IQueryable kaybı anlamına gelir

Bu nedenle, uygulamam # 2'yi, ayrı alan ve veri modellerini doğrulayamıyorsa, genellikle # 5 ile uğraşmam.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.