प्रत्येक वस्तु के लिए एक सामान्य रिपॉजिटरी बनाम विशिष्ट रिपॉजिटरी बनाने का लाभ?


132

हम एक ASP.NET MVC एप्लिकेशन विकसित कर रहे हैं, और अब रिपॉजिटरी / सर्विस क्लासेस का निर्माण कर रहे हैं। मैं सोच रहा था कि क्या कोई सामान्य IRepository इंटरफ़ेस बनाने के लिए कोई बड़ा लाभ है जो सभी रिपॉजिटरी को लागू करता है, बनाम प्रत्येक रिपॉजिटरी का अपना अनूठा इंटरफ़ेस और विधियों का सेट है।

उदाहरण के लिए: एक सामान्य IRepository इंटरफ़ेस की तरह लग सकता है (से लिया गया) इस उत्तर ):

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

प्रत्येक रिपॉजिटरी इस इंटरफ़ेस को लागू करेगी, उदाहरण के लिए:

  • CustomerRepository: IRepository
  • ProductRepository: IRepository
  • आदि।

पूर्व परियोजनाओं में हमने जो वैकल्पिक अनुसरण किया है वह होगा:

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

दूसरे मामले में, अभिव्यक्तियाँ (LINQ या अन्यथा) पूरी तरह से रिपॉजिटरी कार्यान्वयन में शामिल होंगी, जो कोई भी सेवा को लागू कर रहा है, उसे यह जानना होगा कि किस रिपॉजिटरी फ़ंक्शन को कॉल करना है।

मुझे लगता है मुझे सेवा वर्ग में सभी अभिव्यक्ति वाक्य रचना लिखने और रिपॉजिटरी में पास होने का फायदा नहीं दिखता। क्या इसका मतलब यह नहीं होगा कि कई मामलों में आसान-से-लाइनअप लाइनक्यू कोड को दोहराया जा रहा है?

उदाहरण के लिए, हमारे पुराने चालान सिस्टम में, हम कॉल करते हैं

InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)

कुछ अलग सेवाओं (ग्राहक, चालान, खाता, आदि) से। यह कई स्थानों पर निम्नलिखित लिखने की तुलना में बहुत साफ लगता है:

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

विशिष्ट दृष्टिकोण का उपयोग करने के लिए एकमात्र नुकसान यह है कि हम गेट * कार्यों के कई क्रमपरिवर्तन के साथ समाप्त हो सकते हैं, लेकिन यह अभी भी सेवा वर्गों में अभिव्यक्ति तर्क को आगे बढ़ाने के लिए बेहतर लगता है।

मैं क्या खो रहा हूँ?


पूर्ण ORM के साथ सामान्य रिपोजिटरी का उपयोग करना बेकार लगता है। मैंने यहां इसके विवरण पर चर्चा की है
अमित जोशी

जवाबों:


169

यह रिपोजिटरी पैटर्न के रूप में पुराना एक मुद्दा है। LINQ का हालिया परिचय IQueryable, एक क्वेरी का एक समान प्रतिनिधित्व, इस बहुत विषय के बारे में बहुत चर्चा का कारण बना है।

जेनेरिक रिपॉजिटरी फ्रेमवर्क बनाने के लिए बहुत मेहनत करने के बाद मैं खुद विशिष्ट रिपोजिटरीज़ पसंद करता हूं। कोई फर्क नहीं पड़ता कि मैंने किस चतुर तंत्र की कोशिश की, मैं हमेशा एक ही समस्या पर समाप्त हुआ: रिपॉजिटरी डोमेन के मॉडल का एक हिस्सा है, और यह डोमेन सामान्य नहीं है। प्रत्येक इकाई को नहीं हटाया जा सकता है, प्रत्येक इकाई को नहीं जोड़ा जा सकता है, प्रत्येक इकाई का भंडार नहीं है। प्रश्न बेतहाशा भिन्न होते हैं; रिपॉजिटरी एपीआई अपने आप में इकाई के रूप में अद्वितीय हो जाता है।

एक पैटर्न जो मैं अक्सर उपयोग करता हूं वह विशिष्ट रिपॉजिटरी इंटरफेस है, लेकिन कार्यान्वयन के लिए एक आधार वर्ग है। उदाहरण के लिए, SQL में LINQ का उपयोग करके, आप कर सकते हैं:

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

DataContextअपनी पसंद के यूनिट-ऑफ-वर्क से बदलें । एक उदाहरण कार्यान्वयन हो सकता है:

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

ध्यान दें कि रिपॉजिटरी का सार्वजनिक एपीआई उपयोगकर्ताओं को हटाने की अनुमति नहीं देता है। इसके अलावा, एक्सपोज़िंग IQueryableकीड़े की एक पूरी अन्य कैन है - इस विषय पर पेट बटन के रूप में कई राय हैं।


9
गंभीरता से, महान जवाब। धन्यवाद!
बीप बीप

5
तो आप इसके साथ IoC / DI का उपयोग कैसे करेंगे? (मैं IoC में एक नौसिखिया हूँ) मेरा सवाल पूर्ण में अपने पैटर्न के संबंध में: stackoverflow.com/questions/4312388/…
dan

36
"रिपॉजिटरी डोमेन के मॉडल का एक हिस्सा है, और यह डोमेन सामान्य नहीं है। प्रत्येक निकाय को हटाया नहीं जा सकता है, प्रत्येक इकाई को नहीं जोड़ा जा सकता है, न कि प्रत्येक निकाय के पास रिपॉजिटरी है" सही!
adamwtiko

मुझे पता है कि यह एक पुराना उत्तर है, लेकिन मैं जानबूझकर के साथ रिपॉजिटरी वर्ग से एक अद्यतन विधि को छोड़ने पर उत्सुक हूं। मुझे ऐसा करने का एक साफ तरीका खोजने में परेशानी हो रही है।
rtf

1
Oldie लेकिन एक गुडी। यह पोस्ट अविश्वसनीय रूप से बुद्धिमान है और इसे अच्छी तरह से डेवलपर्स के लिए पढ़ना और फिर से पढ़ना चाहिए। धन्यवाद @BryanWatts मेरा कार्यान्वयन आम तौर पर आधारित है, लेकिन आधार एक ही है। बेस रिपॉजिटरी, विशिष्ट रिपॉजिटरी के साथ उस डोमेन का प्रतिनिधित्व करने के लिए जो सुविधाओं में ऑप्ट-इन करता है।
pimbrouwers

27

मैं वास्तव में ब्रायन के पोस्ट से थोड़ा असहमत हूं। मुझे लगता है कि वह सही है, कि अंततः सब कुछ बहुत अनूठा है और इसी तरह। लेकिन एक ही समय में, जो कि आप डिजाइन करते हैं, उनमें से अधिकांश निकलता है, और मुझे पता चलता है कि एक सामान्य रिपॉजिटरी को प्राप्त करना और अपने मॉडल को विकसित करते समय इसका उपयोग करना, मैं एक ऐप को बहुत तेज़ी से प्राप्त कर सकता हूं, फिर अधिक विशिष्टता के लिए रिफ्लेक्टर जैसा कि मैं पाता हूं। ऐसा करने की जरूरत है।

तो, इस तरह के मामलों में, मैंने अक्सर एक सामान्य IRepository बनाया है जिसमें पूर्ण CRUD स्टैक है, और जो मुझे एपीआई के साथ खेलने और लोगों को w / UI खेलने और समानांतर में एकीकरण और उपयोगकर्ता स्वीकृति परीक्षण करने देता है। फिर, जैसा कि मुझे लगता है कि मुझे रेपो आदि पर विशिष्ट प्रश्नों की आवश्यकता है, मैं उस निर्भरता w / को प्रतिस्थापित करना शुरू कर देता हूं यदि आवश्यक हो और वहां से जा रहा हो। एक अंतर्निहित निहित। बनाना और उपयोग करना आसान है (और संभवतः इन-मेमोरी डीबी या स्टैटिक ऑब्जेक्ट या मॉक ऑब्जेक्ट या जो भी हो) को हुक करें।

उसने कहा, मैंने हाल ही में जो करना शुरू किया है वह व्यवहार को तोड़ रहा है। इसलिए, यदि आप IDataFetcher, IDataUpdater, IDataInserter, और IDataDeleter (उदाहरण के लिए) के लिए इंटरफेस करते हैं, तो आप इंटरफ़ेस के माध्यम से अपनी आवश्यकताओं को परिभाषित करने के लिए मिक्स-एंड-मैच कर सकते हैं और फिर कार्यान्वयन कर सकते हैं या उनमें से कुछ का ख्याल रख सकते हैं, और मैं कर सकता हूं जब भी मैं ऐप बना रहा हूं, तब भी उपयोग करने के लिए यह सभी-कार्यान्वयन को इंजेक्ट करता है।

पॉल


4
उत्तर @Paul के लिए धन्यवाद। मैंने वास्तव में उस दृष्टिकोण की भी कोशिश की। मैं यह पता लगाने की कोशिश नहीं कर सकता कि मैंने जो पहली कोशिश की थी, उसे उदारतापूर्वक कैसे व्यक्त किया जाए GetById()। मैं का उपयोग करना चाहिए IRepository<T, TId>, GetById(object id)या मान्यताओं और इस्तेमाल करते हैं GetById(int id)? समग्र कुंजियाँ कैसे काम करेंगी? मुझे आश्चर्य है कि अगर आईडी द्वारा एक सामान्य चयन एक सार्थक अमूर्त था। यदि नहीं, तो सामान्य रिपॉजिटरी को और क्या व्यक्त करने के लिए मजबूर किया जाएगा? यह कार्यान्वयन की अमूर्तता के पीछे तर्क की रेखा थी , न कि इंटरफ़ेस की
ब्रायन वॉट्स

10
इसके अलावा, एक सामान्य क्वेरी तंत्र ORM की जिम्मेदारी है। आपके रिपॉजिटरी में सामान्य क्वेरी तंत्र का उपयोग करके आपकी परियोजना की संस्थाओं के लिए विशिष्ट प्रश्नों को लागू किया जाना चाहिए । आपके रिपॉजिटरी के उपभोक्ताओं को अपने स्वयं के प्रश्नों को लिखने के लिए मजबूर नहीं किया जाना चाहिए जब तक कि आपके समस्या डोमेन का हिस्सा न हो, जैसे कि रिपोर्टिंग के साथ।
ब्रायन वॉट्स

2
@ ब्रायन - अपने गेटबायड () के बारे में। मैं FindById <T, TId> (TId id) का उपयोग करता हूं; इस प्रकार रिपॉजिटरी की तरह कुछ हुआ। फ़ाइंडबीइडी <चालान, इंट> (435);
जोशुआ हेस

मैं आमतौर पर जेनेरिक इंटरफेस पर फ़ील्ड-स्तरीय क्वेरी विधियों को स्पष्ट रूप से नहीं डालता हूं। जैसा कि आपने बताया, सभी मॉडलों को एक ही कुंजी द्वारा क्वेर नहीं किया जाना चाहिए, और कुछ मामलों में आपका ऐप कभी भी आईडी द्वारा कुछ प्राप्त नहीं करेगा (उदाहरण के लिए, यदि आप डीबी-जनरेट किए गए प्राथमिक कुंजी का उपयोग कर रहे हैं और आप केवल प्राप्त करते हैं एक प्राकृतिक कुंजी, उदाहरण के लिए लॉगिन नाम)। क्वेरी पद्धतियां उन विशिष्ट इंटरफेस पर विकसित होती हैं जिन्हें मैं रिफैक्टरिंग के हिस्से के रूप में बनाता हूं।
पॉल

13

मैं विशिष्ट रिपॉजिटरी पसंद करता हूं जो कि सामान्य रिपोजिटरी (या सटीक व्यवहार को निर्दिष्ट करने के लिए जेनेरिक रिपॉजिटरी की सूची) से निकला हुआ है।


क्या आप एक छोटा सा स्निपेट-ईश उदाहरण प्रदान कर सकते हैं?
जोहान गेर्ले

@ जोहान गेरेल नहीं, क्योंकि मैं अब रिपॉजिटरी का उपयोग नहीं करता हूं।
अर्निस लैप्सा

अब आप क्या उपयोग करते हैं कि आप रिपॉजिटरी से दूर रहें?
क्रिस

@ क्रिस मैं समृद्ध डोमेन मॉडल पर बहुत ध्यान केंद्रित करता हूं। आवेदन का इनपुट हिस्सा क्या महत्वपूर्ण है। यदि सभी राज्य परिवर्तनों की सावधानीपूर्वक निगरानी की जाती है, तो इससे कोई फर्क नहीं पड़ता कि आप डेटा को कितने समय तक पढ़ते हैं जब तक यह पर्याप्त रूप से कुशल न हो। इसलिए मैं सीधे NHibernate ISession का उपयोग करता हूं। रिपॉजिटरी लेयर एब्स्ट्रैक्शन के बिना, उत्सुक लोडिंग, मल्टी क्वेरी आदि जैसे सामान को निर्दिष्ट करना बहुत आसान है, और यदि आपको वास्तव में ज़रूरत है, तो आईएसएस को भी मॉक करना मुश्किल नहीं है।
अर्निस लैप्सा

3
@JesseWebb Naah ... अमीर डोमेन मॉडल क्वेरी तर्क के साथ काफी सरल हो जाता है। जैसे अगर मैं उन उपयोगकर्ताओं को खोजना चाहता हूं जिन्होंने कुछ खरीदा है, तो मैं केवल उपयोगकर्ताओं के लिए खोज करता हूं। (u => u.HasPurchasedAnything) उपयोगकर्ताओं के बजाय। (x => आदेश, कुछ कुछ, मैं linq नहीं जानता।) आदेश => क्रम। शब्द = = 1)। जॉइन (x => x.products)। कहाँ (x .... आदि) आदि .... blah blah blah
'17:

5

एक सामान्य रिपॉजिटरी है जिसे एक विशिष्ट रिपॉजिटरी द्वारा लपेटा गया है। इस तरह से आप सार्वजनिक इंटरफ़ेस को नियंत्रित कर सकते हैं लेकिन फिर भी कोड-पुन: उपयोग का लाभ है जो एक सामान्य रिपॉजिटरी से आता है।


2

सार्वजनिक वर्ग UserRepository: रिपोजिटरी, IUserRepository

इंटरफ़ेस को उजागर करने से बचने के लिए क्या आपको IUserRepository को इंजेक्ट नहीं करना चाहिए। जैसा कि लोगों ने कहा है, आपको पूर्ण CRUD स्टैक आदि की आवश्यकता नहीं हो सकती है।


2
यह उत्तर की तुलना में टिप्पणी की तरह दिखता है।
अमित जोशी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.