Her nesne için özel bir havuz yerine genel bir havuz oluşturmanın avantajı?


132

Bir ASP.NET MVC uygulaması geliştiriyoruz ve şu anda depo / hizmet sınıflarını oluşturuyoruz. Tüm depoların uyguladığı genel bir IRepository arabirimi oluşturmanın, her Deponun kendi benzersiz arabirimine ve yöntem setine sahip olmasına karşın herhangi bir önemli avantajı olup olmadığını merak ediyorum.

Örneğin: genel bir IRepository arayüzü şöyle görünebilir ( bu cevaptan alınmıştır ):

public interface IRepository : IDisposable
{
    T[] GetAll<T>();
    T[] GetAll<T>(Expression<Func<T, bool>> filter);
    T GetSingle<T>(Expression<Func<T, bool>> filter);
    T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
    void Delete<T>(T entity);
    void Add<T>(T entity);
    int SaveChanges();
    DbTransaction BeginTransaction();
}

Her Depo bu arayüzü uygulayacaktır, örneğin:

  • CustomerRepository: IRepository
  • ProductRepository: IRepository
  • vb.

Önceki projelerde izlediğimiz alternatif şu olacaktır:

public interface IInvoiceRepository : IDisposable
{
    EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
    EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
    InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
    InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
    InvoiceEntity CreateInvoice();
    InvoiceLineEntity CreateInvoiceLine();
    void SaveChanges(InvoiceEntity); //handles inserts or updates
    void DeleteInvoice(InvoiceEntity);
    void DeleteInvoiceLine(InvoiceLineEntity);
}

İkinci durumda, ifadeler (LINQ veya başka türlü) tamamen Depo uygulamasında yer alır, hizmeti uygulayan kişi yalnızca hangi depo işlevinin çağrılacağını bilmesi gerekir.

Sanırım tüm ifade sözdizimini servis sınıfında yazıp depoya geçmenin avantajını görmüyorum. Bu, kolayca karıştırılabilen LINQ kodunun birçok durumda çoğaltıldığı anlamına gelmez mi?

Örneğin, eski faturalama sistemimizde

InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)

birkaç farklı hizmetten (Müşteri, Fatura, Hesap vb.) Bu, aşağıdakileri birden çok yerde yazmaktan çok daha temiz görünüyor:

rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);

Spesifik yaklaşımı kullanmanın tek dezavantajı, Get * fonksiyonlarının birçok permütasyonuyla sonuçlanabilmemizdir, ancak bu, ifade mantığını Servis sınıflarına itmek için yine de tercih edilebilir görünüyor.

Neyi kaçırıyorum?


Tam ORM'lerle jenerik depoları kullanmak işe yaramaz görünüyor. Bunu burada ayrıntılı olarak tartıştım .
Amit Joshi

Yanıtlar:


169

Bu, Depo modelinin kendisi kadar eski bir sorundur. LINQ'lerin son tanıtımıIQueryableBir sorgunun tek tip bir temsili bu konu hakkında pek çok tartışmaya neden oldu.

Genel bir depo çerçevesi oluşturmak için çok çalıştıktan sonra ben de belirli depoları tercih ediyorum. Hangi akıllı mekanizmayı denediğim önemli değil, hep aynı problemle karşılaştım: bir depo, modellenen alanın bir parçasıdır ve bu alan genel değildir. Her varlık silinemez, her varlık eklenemez, her varlığın bir havuzu yoktur. Sorgular çılgınca değişir; depo API'si varlığın kendisi kadar benzersiz hale gelir.

Sık kullandığım bir model, belirli depo arayüzlerine sahip olmak, ancak uygulamalar için temel bir sınıfa sahip olmaktır. Örneğin, LINQ to SQL kullanarak şunları yapabilirsiniz:

public abstract class Repository<TEntity>
{
    private DataContext _dataContext;

    protected Repository(DataContext dataContext)
    {
        _dataContext = dataContext;
    }

    protected IQueryable<TEntity> Query
    {
        get { return _dataContext.GetTable<TEntity>(); }
    }

    protected void InsertOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().InsertOnCommit(entity);
    }

    protected void DeleteOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().DeleteOnCommit(entity);
    }
}

DataContextSeçtiğiniz iş birimiyle değiştirin . Örnek bir uygulama olabilir:

public interface IUserRepository
{
    User GetById(int id);

    IQueryable<User> GetLockedOutUsers();

    void Insert(User user);
}

public class UserRepository : Repository<User>, IUserRepository
{
    public UserRepository(DataContext dataContext) : base(dataContext)
    {}

    public User GetById(int id)
    {
        return Query.Where(user => user.Id == id).SingleOrDefault();
    }

    public IQueryable<User> GetLockedOutUsers()
    {
        return Query.Where(user => user.IsLockedOut);
    }

    public void Insert(User user)
    {
        InsertOnCommit(user);
    }
}

Deponun genel API'sinin kullanıcıların silinmesine izin vermediğine dikkat edin. Ayrıca, açığa çıkarmak IQueryabletamamen başka bir solucan kutusu - bu konuda göbek deliği kadar çok fikir var.


9
Cidden, harika cevap. Teşekkürler!
bip bip sesi

5
Peki bununla IoC / DI'yi nasıl kullanırsınız? (IoC'de acemiyim) Tam olarak kalıbınızla ilgili sorum: stackoverflow.com/questions/4312388/…
dan

36
"bir depo, modellenen alanın bir parçasıdır ve bu alan genel değildir. Her varlık silinemez, her varlık eklenemez, her varlığın bir havuzu yoktur" mükemmel!
adamwtiko

Bunun eski bir cevap olduğunu biliyorum, ancak kasıtlı olarak Depo sınıfından bir Güncelleme yöntemini bırakıp bırakmayacağınızı merak ediyorum. Bunu yapmanın temiz bir yolunu bulmakta zorlanıyorum.
rtf

1
Eski ama bir güzellik. Bu gönderi inanılmaz derecede akıllıca ve okunmalı ve yeniden okunmalı, ancak hepsi iyi olan geliştiriciler. Teşekkürler @BryanWatts. Benim uygulamam tipik olarak zarif tabanlı, ancak öncül aynı. Özelliklere dahil olan etki alanını temsil etmek için belirli havuzlara sahip temel depo.
pimbrouwers

27

Aslında Bryan'ın gönderisine biraz katılmıyorum. Bence haklı, sonuçta her şey çok benzersiz ve bu böyle devam ediyor. Ama aynı zamanda, bunların çoğu siz tasarlarken ortaya çıkıyor ve modelimi geliştirirken genel bir havuz oluşturup onu kullandığımı fark ediyorum, bir uygulamayı çok hızlı bir şekilde kurabilirim, sonra daha fazla özgüllük için yeniden düzenleme yapabilirim buna ihtiyacım var.

Bu nedenle, bu gibi durumlarda, sıklıkla tam CRUD yığınına sahip genel bir IR deposu oluşturdum ve bu API ile hızlı bir şekilde oynamama ve insanların UI ile oynamasına ve paralel olarak entegrasyon ve kullanıcı kabul testi yapmasına izin vermeme izin veriyor. Ardından, repo vb. İle ilgili özel sorgulara ihtiyacım olduğunu anladığımda, bu bağımlılığı gerekirse belirli olanla değiştirmeye ve oradan devam etmeye başlıyorum. Altta yatan bir impl. oluşturmak ve kullanmak kolaydır (ve muhtemelen bir bellek içi veritabanına veya statik nesnelere veya alay edilen nesnelere veya her neyse ona bağlanır).

Bununla birlikte, son zamanlarda yapmaya başladığım şey davranışı bozmaktır. Dolayısıyla, IDataFetcher, IDataUpdater, IDataInserter ve IDataDeleter (örneğin) için arayüzler yaparsanız, arayüz aracılığıyla gereksinimlerinizi tanımlamak için karıştırıp eşleştirebilir ve ardından bunların bir kısmını veya tamamını halleten uygulamalara sahip olabilirsiniz. Ben uygulamayı geliştirirken kullanmak için hala her şeyi yap uygulamasını enjekte ediyorum.

paul


4
Cevabınız için teşekkürler @ Paul. Aslında bu yaklaşımı da denedim. Denediğim ilk yöntemi genel olarak nasıl ifade edeceğimi çözemedim GetById(). Ben kullanmalı mıyım IRepository<T, TId>, GetById(object id)ya da varsayım ve faydalanmak GetById(int id)? Bileşik anahtarlar nasıl çalışır? Kimliğe göre genel bir seçimin değerli bir soyutlama olup olmadığını merak ettim. Aksi takdirde, jenerik depolar akward olarak ifade etmeye zorlanabilir mi? Arayüzü değil, uygulamayı soyutlamanın ardındaki mantık budur .
Bryan Watts

10
Ayrıca, genel bir sorgu mekanizması bir ORM'nin sorumluluğundadır. Depolarınız , genel sorgu mekanizmasını kullanarak projenizin varlıkları için belirli sorguları uyguluyor olmalıdır . Deponuzun tüketicileri, raporlamada olduğu gibi sorun etki alanınızın bir parçası olmadığı sürece kendi sorgularını yazmaya zorlanmamalıdır.
Bryan Watts

2
@Bryan - GetById () ile ilgili. FindById <T, TId> (TId id) kullanıyorum; Böylece repository.FindById <Invoice, int> (435);
Joshua Hayes

Açıkçası genel arayüzlere alan düzeyinde sorgulama yöntemleri koymam. Sizin de belirttiğiniz gibi, tüm modeller tek bir anahtarla sorgulanmamalıdır ve bazı durumlarda uygulamanız hiçbir zaman bir kimlikle bir şey almayacaktır (örneğin, DB tarafından oluşturulan birincil anahtarları kullanıyorsanız ve yalnızca doğal bir anahtar, örneğin oturum açma adı). Sorgu yöntemleri, yeniden düzenlemenin bir parçası olarak oluşturduğum belirli arabirimler üzerinde gelişir.
Paul

13

Genel depodan (veya tam davranışı belirtmek için genel depoların listesini) geçersiz kılınabilen yöntem imzaları ile türetilen belirli depoları tercih ederim.


Lütfen küçük bir snippet-ish örneği verebilir misiniz?
Johann Gerell

@Johann Gerell hayır, çünkü artık depo kullanmıyorum.
Arnis Lapsa

Depolardan uzak durduğunuza göre şimdi ne kullanıyorsunuz?
Chris

@Chris, ağırlıklı olarak zengin alan modeline sahip olmaya odaklanıyorum. önemli olan uygulamanın girdi kısmıdır. tüm durum değişiklikleri dikkatle izlenirse, yeterince verimli olduğu sürece verileri nasıl okuduğunuz o kadar da önemli değildir. bu yüzden NHibernate ISession'ı doğrudan kullanıyorum. depo katmanı soyutlaması olmadan, istekli yükleme, çoklu sorgular vb. gibi şeyleri belirtmek çok daha kolaydır ve gerçekten ihtiyacınız varsa, ISession ile alay etmek de zor değildir.
Arnis Lapsa

3
@JesseWebb Naah ... Zengin alan modeli ile sorgu mantığı önemli ölçüde basitleştirildi. Örneğin, bir şey satın almış kullanıcıları aramak istersem, kullanıcılar yerine sadece kullanıcıları ararım.Kullanıcılar yerine burada (u => u.HasPurchasedAnything) Katıl (x => Siparişler, bir şey, bir şey, linq bilmiyorum). order => order.Status == 1) .Join (x => x.products) .Where (x .... etc etc .... blah blah blah
Arnis Lapsa

5

Belirli bir havuz tarafından sarılmış genel bir depoya sahip olun. Bu şekilde, genel arabirimi kontrol edebilirsiniz, ancak yine de genel bir depoya sahip olmaktan gelen kodu yeniden kullanma avantajına sahip olabilirsiniz.


2

public class UserRepository: Repository, IUserRepository

Arayüzün açığa çıkmasını önlemek için IUserRepository enjekte etmeniz gerekmez. İnsanların söylediği gibi, tam CRUD yığınına vb. İhtiyacınız olmayabilir.


2
Bu cevaptan çok bir yoruma benziyor.
Amit Joshi
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.