अच्छी तरह से डिजाइन क्वेरी कमांड और / या विनिर्देशों


90

मैं विशिष्ट रिपॉजिटरी पैटर्न (विशेष प्रश्नों के लिए तरीकों की बढ़ती सूची, आदि) द्वारा प्रस्तुत समस्याओं के अच्छे समाधान के लिए काफी समय से खोज रहा हूं। देखें: http://ayende.com/blog/3955/repository- is-of-new-सिंगलटन )।

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

(नोट: मैं "कमांड" शब्द का उपयोग कमांड पैटर्न में करता हूं, जिसे क्वेरी ऑब्जेक्ट के रूप में भी जाना जाता है। मैं कमांड के बारे में कमांड / क्वेरी पृथक्करण के बारे में बात नहीं कर रहा हूं जहां प्रश्नों और आदेशों के बीच अंतर किया गया है (अपडेट, हटाएं) डालने))

इसलिए मैं ऐसे विकल्पों की तलाश कर रहा हूं जो पूरी क्वेरी को एनकैप्सुलेट करता हो, लेकिन फिर भी इतना लचीला हो कि आप केवल कमांड कक्षाओं के विस्फोट के लिए स्पेगेटी रिपोजिटरी को स्वैप नहीं कर रहे हैं।

मैंने उदाहरण के लिए Linqspecs का उपयोग किया है, और जब मैं चयन मानदंड को सार्थक नाम प्रदान करने में सक्षम होने में कुछ मूल्य पाता हूं, तो यह केवल पर्याप्त है। शायद मैं एक मिश्रित समाधान की मांग कर रहा हूं जो कई दृष्टिकोणों को जोड़ती है।

मैं उन समाधानों की तलाश कर रहा हूं जो दूसरों ने इस समस्या को हल करने के लिए विकसित किए हैं, या एक अलग समस्या को संबोधित कर सकते हैं लेकिन फिर भी इन आवश्यकताओं को संतुष्ट करते हैं। लिंक किए गए लेख में, आयंडे सीधे nHibernate संदर्भ का उपयोग करने का सुझाव देते हैं, लेकिन मुझे लगता है कि मोटे तौर पर आपकी व्यावसायिक परत को जटिल बनाता है क्योंकि इसमें अब क्वेरी जानकारी भी शामिल है।

मैं इस पर एक इनाम की पेशकश करूंगा, जैसे ही प्रतीक्षा अवधि समाप्त हो जाएगी। इसलिए कृपया अपने समाधानों को उचित समझें, और मैं अच्छे समाधानों का चयन करूं और मैं सर्वश्रेष्ठ समाधान का चयन करूं और उपविजेताओं को ऊपर उठाऊं।

ध्यान दें: मैं कुछ ऐसी चीज़ ढूँढ रहा हूँ जो ORM आधारित हो। स्पष्ट रूप से EF या nHibernate होना जरूरी नहीं है, लेकिन वे सबसे आम हैं और सबसे अच्छा फिट होगा। अगर यह आसानी से अन्य ORM के लिए अनुकूलित किया जा सकता है जो कि एक बोनस होगा। Linq संगत भी अच्छा होगा।

अद्यतन: मैं वास्तव में आश्चर्यचकित हूँ कि यहाँ कई अच्छे सुझाव नहीं हैं। ऐसा लगता है कि लोग या तो पूरी तरह से CQRS हैं, या वे पूरी तरह से रिपॉजिटरी कैंप में हैं। मेरे अधिकांश एप्लिकेशन वारंट CQRS के लिए पर्याप्त जटिल नहीं हैं (कुछ CQRS अधिवक्ताओं के साथ कुछ आसानी से कहते हैं कि आपको इसके लिए उपयोग नहीं करना चाहिए)।

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

आदर्श रूप से, मैं जिस चीज की तलाश कर रहा हूं, वह क्वेरी ऑब्जेक्ट्स, स्पेसिफिकेशन पैटर्न और रिपॉजिटरी के बीच किसी प्रकार का क्रॉस है। जैसा कि मैंने ऊपर कहा, विनिर्देश पैटर्न केवल उस खंड के पहलू से संबंधित होता है, जहाँ क्वेरी के अन्य पहलू नहीं होते हैं, जैसे कि जोड़, उप-चयन, आदि .. रिपोजिटरी पूरी क्वेरी से निपटते हैं, लेकिन थोड़ी देर बाद हाथ से निकल जाते हैं । क्वेरी ऑब्जेक्ट भी पूरी क्वेरी के साथ काम करते हैं, लेकिन मैं केवल क्वेरी ऑब्जेक्ट्स के विस्फोट के साथ रिपॉजिटरी को बदलना नहीं चाहता हूं।


5
विलक्षण प्रश्न। मैं भी यह देखना चाहता हूं कि मेरे सुझाव से अधिक अनुभव वाले लोग क्या करेंगे। मैं इस समय एक कोड बेस पर काम कर रहा हूं जहां सामान्य रिपॉजिटरी में कमांड ऑब्जेक्ट्स या क्वेरी ऑब्जेक्ट्स के लिए ओवरलोड भी होते हैं, जिनकी संरचना आयेंडे अपने ब्लॉग में वर्णित के समान है। पुनश्च: यह प्रोग्रामर पर कुछ ध्यान आकर्षित कर सकता है।
साइमन व्हाइटहेड

यदि आप LINQ पर निर्भरता का बुरा नहीं मानते हैं तो सिर्फ एक रिपॉजिटरी का उपयोग क्यों करें जो IQueryable को उजागर करता है? एक सामान्य दृष्टिकोण एक सामान्य रिपॉजिटरी है, और फिर जब आपको ऊपर पुन: प्रयोज्य तर्क की आवश्यकता होती है तो आप अपने अतिरिक्त तरीकों से एक व्युत्पन्न रिपॉजिटरी प्रकार बनाते हैं।
देवजीतलाल

@devdigital - Linq पर निर्भरता डेटा कार्यान्वयन पर निर्भरता के समान नहीं है। मैं वस्तुओं को Linq का उपयोग करना चाहूंगा, इसलिए मैं अन्य व्यावसायिक परत कार्यों को सॉर्ट या निष्पादित कर सकता हूं। लेकिन इसका मतलब यह नहीं है कि मैं डेटा मॉडल कार्यान्वयन पर निर्भरता चाहता हूं। क्या मैं वास्तव में यहाँ के बारे में बात कर रहा हूँ परत / स्तरीय इंटरफ़ेस है। एक उदाहरण के रूप में, मैं एक क्वेरी को बदलने में सक्षम होना चाहता हूं और इसे 200 स्थानों पर नहीं बदलना है, जो कि होता है यदि आप IQueryable को सीधे व्यवसाय मॉडल में धकेल देते हैं।
एरिक फनकेनबसच

1
@devdigital - जो मूल रूप से समस्याओं को आपके व्यवसाय की परत में ले जाता है। आप बस समस्या को हल कर रहे हैं।
एरिक फुन्केनबस

1
@MystereMan इन 2 लेख पर एक नज़र डालें: blog.gauffin.org/2012/10/griffin-decoupled-the-queries और cuttingedge.it/blogs/steven/pivot/entry.php?id=92
david.s

जवाबों:


94

डिस्क्लेमर: चूंकि अभी तक कोई महान उत्तर नहीं हैं, इसलिए मैंने एक महान ब्लॉग पोस्ट से एक हिस्सा पोस्ट करने का फैसला किया, जिसे मैंने कुछ समय पहले पढ़ा था, लगभग मौखिक रूप से कॉपी किया। आप यहाँ पूर्ण ब्लॉग पोस्ट पा सकते हैं । तो यहाँ है:


हम निम्नलिखित दो इंटरफेस को परिभाषित कर सकते हैं:

public interface IQuery<TResult>
{
}

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

IQuery<TResult>निर्दिष्ट करता है संदेश है कि डेटा का उपयोग कर देता है के साथ एक विशिष्ट क्वेरी को परिभाषित करता है TResultसामान्य प्रकार। पहले से परिभाषित इंटरफ़ेस के साथ हम इस तरह एक क्वेरी संदेश को परिभाषित कर सकते हैं:

public class FindUsersBySearchTextQuery : IQuery<User[]>
{
    public string SearchText { get; set; }
    public bool IncludeInactiveUsers { get; set; }
}

यह वर्ग दो मापदंडों के साथ एक क्वेरी ऑपरेशन को परिभाषित करता है, जिसके परिणामस्वरूप Userवस्तुओं की एक सरणी होगी । इस संदेश को संभालने वाला वर्ग निम्नानुसार परिभाषित किया जा सकता है:

public class FindUsersBySearchTextQueryHandler
    : IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
    private readonly NorthwindUnitOfWork db;

    public FindUsersBySearchTextQueryHandler(NorthwindUnitOfWork db)
    {
        this.db = db;
    }

    public User[] Handle(FindUsersBySearchTextQuery query)
    {
        return db.Users.Where(x => x.Name.Contains(query.SearchText)).ToArray();
    }
}

अब हम उपभोक्ताओं को सामान्य IQueryHandlerइंटरफ़ेस पर निर्भर कर सकते हैं :

public class UserController : Controller
{
    IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler;

    public UserController(
        IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler)
    {
        this.findUsersBySearchTextHandler = findUsersBySearchTextHandler;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        User[] users = this.findUsersBySearchTextHandler.Handle(query);    
        return View(users);
    }
}

तुरंत यह मॉडल हमें बहुत लचीलापन प्रदान करता है, क्योंकि हम अब यह तय कर सकते हैं कि किसमें इंजेक्ट किया जाए UserController। हम UserController(और उस इंटरफ़ेस के अन्य सभी उपभोक्ताओं) में बदलाव किए बिना, पूरी तरह से अलग कार्यान्वयन को लागू कर सकते हैं, या वास्तविक कार्यान्वयन को लपेटता है ।

IQuery<TResult>इंटरफ़ेस हमें संकलन-टाइम समर्थन जब निर्दिष्ट करने या इंजेक्शन लगाने देता है IQueryHandlersहमारे कोड में। जब हम बदले में (लागू करके ) FindUsersBySearchTextQueryलौटने के लिए बदल जाते हैं , तो संकलन करने में विफल हो जाएगा, क्योंकि सामान्य प्रकार की बाधा पर मैप करने में सक्षम नहीं होगा ।UserInfo[]IQuery<UserInfo[]>UserControllerIQueryHandler<TQuery, TResult>FindUsersBySearchTextQueryUser[]

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

हम IQueryHandlersएब्सट्रैक्शन की एक अतिरिक्त परत के साथ बहुत अधिक इंजेक्शन लगाने की समस्या को ठीक कर सकते हैं । हम एक मध्यस्थ बनाते हैं जो उपभोक्ताओं और क्वेरी हैंडलर्स के बीच बैठता है:

public interface IQueryProcessor
{
    TResult Process<TResult>(IQuery<TResult> query);
}

IQueryProcessorएक सामान्य विधि के साथ एक गैर सामान्य इंटरफेस है। जैसा कि आप इंटरफ़ेस परिभाषा में देख सकते हैं, इंटरफ़ेस IQueryProcessorपर निर्भर करता IQuery<TResult>है। यह हमें अपने उपभोक्ताओं पर समय का समर्थन करने की अनुमति देता है जो पर निर्भर करता है IQueryProcessor। आइए UserControllerनए का उपयोग करने के लिए फिर से लिखें IQueryProcessor:

public class UserController : Controller
{
    private IQueryProcessor queryProcessor;

    public UserController(IQueryProcessor queryProcessor)
    {
        this.queryProcessor = queryProcessor;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        // Note how we omit the generic type argument,
        // but still have type safety.
        User[] users = this.queryProcessor.Process(query);

        return this.View(users);
    }
}

UserControllerअब एक पर निर्भर करता है IQueryProcessorकि हमारे सभी जिज्ञासाओं के संभाल कर सकते हैं। UserControllerकी SearchUsersप्रणाली को बुलाती है IQueryProcessor.Processविधि एक initialized क्वेरी ऑब्जेक्ट में गुजर। इंटरफ़ेस को FindUsersBySearchTextQueryलागू करने के बाद से IQuery<User[]>, हम इसे जेनेरिक Execute<TResult>(IQuery<TResult> query)विधि से पारित कर सकते हैं । सी # प्रकार के निष्कर्ष के लिए धन्यवाद, कंपाइलर जेनेरिक प्रकार को निर्धारित करने में सक्षम है और यह हमें स्पष्ट रूप से टाइप करने के लिए बचाता है। Processविधि का वापसी प्रकार भी ज्ञात है।

अब यह IQueryProcessorअधिकार प्राप्त करने के कार्यान्वयन की जिम्मेदारी है IQueryHandler। इसके लिए कुछ डायनामिक टाइपिंग की आवश्यकता होती है, और वैकल्पिक रूप से एक डिपेंडेंसी इंजेक्शन फ्रेमवर्क का उपयोग होता है, और यह सब कोड की कुछ पंक्तियों के साथ किया जा सकता है:

sealed class QueryProcessor : IQueryProcessor
{
    private readonly Container container;

    public QueryProcessor(Container container)
    {
        this.container = container;
    }

    [DebuggerStepThrough]
    public TResult Process<TResult>(IQuery<TResult> query)
    {
        var handlerType = typeof(IQueryHandler<,>)
            .MakeGenericType(query.GetType(), typeof(TResult));

        dynamic handler = container.GetInstance(handlerType);

        return handler.Handle((dynamic)query);
    }
}

QueryProcessorकक्षा एक विशिष्ट निर्माण करती IQueryHandler<TQuery, TResult>आपूर्ति क्वेरी उदाहरण के प्रकार के आधार प्रकार। इस प्रकार का उपयोग आपूर्ति किए गए कंटेनर वर्ग को उस प्रकार का उदाहरण प्राप्त करने के लिए कहने के लिए किया जाता है। दुर्भाग्य से हमें Handleप्रतिबिंब का उपयोग करने की विधि को कॉल करने की आवश्यकता है (इस मामले में C # 4.0 डायममिक कीवर्ड का उपयोग करके), क्योंकि इस समय हैंडलर उदाहरण को डालना असंभव है, क्योंकि सामान्य TQueryतर्क संकलन समय पर उपलब्ध नहीं है। हालांकि, जब तक Handleविधि का नाम बदला नहीं जाता है या अन्य तर्क नहीं मिलते हैं , तब तक यह कॉल कभी भी विफल नहीं होगी और यदि आप चाहते हैं, तो इस वर्ग के लिए एक इकाई परीक्षण लिखना बहुत आसान है। प्रतिबिंब का उपयोग करने से थोड़ी गिरावट आएगी, लेकिन वास्तव में चिंता की कोई बात नहीं है।


आपकी किसी चिंता का जवाब देने के लिए:

इसलिए मैं ऐसे विकल्पों की तलाश कर रहा हूं जो पूरी क्वेरी को एनकैप्सुलेट करता हो, लेकिन फिर भी इतना लचीला हो कि आप केवल कमांड कक्षाओं के विस्फोट के लिए स्पेगेटी रिपोजिटरी को स्वैप नहीं कर रहे हैं।

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


2
लगता है कि आपको पुरस्कार मिलेगा। मुझे अवधारणाएं पसंद हैं, मैं बस किसी के लिए वास्तव में कुछ अलग पेश करने की उम्मीद कर रहा था। बधाई।
एरिक फनकेनबस

1
@FuriCuri, क्या किसी एकल वर्ग को वास्तव में 5 प्रश्नों की आवश्यकता है? शायद आप यह देख सकते हैं कि बहुत सारी जिम्मेदारियों के साथ एक वर्ग होने के नाते। वैकल्पिक रूप से, यदि प्रश्नों को एकत्र किया जा रहा है, तो शायद उन्हें वास्तव में एक ही प्रश्न होना चाहिए। ये सिर्फ सुझाव हैं, निश्चित रूप से।
सैम

1
@stakx आप बिल्कुल सही हैं कि मेरे प्रारंभिक उदाहरण TResultमें IQueryइंटरफ़ेस का सामान्य पैरामीटर उपयोगी नहीं है। लेकिन, मेरा अद्यतन जवाब में, TResultपैरामीटर द्वारा प्रयोग किया जाता है Processकी विधि IQueryProcessorको हल करने IQueryHandlerरनटाइम पर।
david.s

1
मेरे पास एक बहुत समान कार्यान्वयन वाला एक ब्लॉग है जो मुझे बताता है कि मैं सही रास्ते पर हूं, यह लिंक jupaol.blogspot.mx/2012/11/… है और मैं इसका इस्तेमाल कुछ समय से PROD अनुप्रयोगों में कर रहा हूं, लेकिन मुझे इस दृष्टिकोण के साथ एक समस्या थी। प्रश्नों का पीछा करना और पुन: उपयोग करना आइए कहते हैं कि मेरे पास कई छोटे प्रश्न हैं जिन्हें और अधिक जटिल प्रश्न बनाने के लिए संयोजित करने की आवश्यकता है , मैंने कोड को केवल डुप्लिकेट करना समाप्त कर दिया है लेकिन मैं एक बेहतर और स्वच्छ दृष्टिकोण की तलाश कर रहा हूं। कोई विचार?
बृहस्पतिवार

4
@ मैं पूरी तरह से विस्तार IQueryableऔर वापस नहीं लेने के लिए सुनिश्चित करने के लिए विस्तार के तरीकों में अपने प्रश्नों को इनकैप्सुलेट करना समाप्त कर दिया , फिर QueryHandlerमैंने अभी-अभी प्रश्नों को कॉल / चेन किया है। इसने मुझे मेरे प्रश्नों की जांच करने और उन्हें श्रृंखलाबद्ध करने की सुविधा दी। मेरे पास मेरे ऊपर एक एप्लीकेटन सेवा है QueryHandler, और मेरे नियंत्रक को हैंडलर के बजाय सीधे सेवा के साथ बात करने के लिए प्रभारी है
जुपॉल

4

उससे निपटने का मेरा तरीका वास्तव में सरल और ओआरएम अज्ञेयवादी है। एक रिपॉजिटरी के लिए मेरा विचार यह है: रिपॉजिटरी का काम संदर्भ के लिए आवश्यक मॉडल के साथ ऐप प्रदान करना है, इसलिए ऐप केवल रेपो से यह पूछता है कि उसे क्या चाहिए लेकिन यह नहीं बताता कि इसे कैसे प्राप्त किया जाए।

मैं एक मानदंड (हाँ, DDD शैली) के साथ रिपॉजिटरी विधि की आपूर्ति करता हूं, जिसका उपयोग रेपो द्वारा क्वेरी बनाने के लिए किया जाएगा (या जो भी आवश्यक है - यह एक webservice अनुरोध हो सकता है)। जॉइन और ग्रुप इम्हो इस बात का विवरण है कि कैसे, क्या और क्या मापदंड होना चाहिए, जहां एक क्लॉज बनाने के लिए केवल आधार होना चाहिए।

मॉडल = एप्लिकेशन द्वारा अंतिम वस्तु या डेटा संरचना की जरूरत।

public class MyCriteria
{
   public Guid Id {get;set;}
   public string Name {get;set;}
    //etc
 }

 public interface Repository
  {
       MyModel GetModel(Expression<Func<MyCriteria,bool>> criteria);
   }

संभवतः आप ORM मानदंड (निहर्नेट) का उपयोग सीधे कर सकते हैं यदि आप इसे चाहते हैं। भंडार कार्यान्वयन को पता होना चाहिए कि अंतर्निहित भंडारण या डीएओ के साथ मानदंड का उपयोग कैसे करें।

मैं आपके डोमेन और मॉडल की आवश्यकताओं को नहीं जानता, लेकिन यह सबसे अच्छा तरीका होगा कि ऐप ही क्वेरी का निर्माण करे। मॉडल इतना बदल जाता है कि आप कुछ स्थिर परिभाषित नहीं कर सकते हैं?

इस समाधान में स्पष्ट रूप से कुछ अतिरिक्त कोड की आवश्यकता होती है, लेकिन यह ओआरएम के बाकी हिस्से या जो भी आप भंडारण का उपयोग करने के लिए उपयोग कर रहे हैं, उसे युगल नहीं करता है। रिपॉजिटरी एक मुखौटा और IMO के रूप में कार्य करने के लिए अपना काम करता है यह साफ है और 'मापदंड अनुवाद' कोड पुन: प्रयोज्य है


यह रिपॉजिटरी वृद्धि की समस्याओं को संबोधित नहीं करता है, और विभिन्न प्रकार के डेटा को वापस करने के लिए तरीकों की एक विस्तृत सूची है। मैं समझता हूं कि आप इसके साथ एक समस्या नहीं देख सकते हैं (बहुत से लोग नहीं करते हैं), लेकिन अन्य लोग इसे अलग तरह से देखते हैं (मैं सुझाव देता हूं कि मैं जिस लेख से जुड़ा हूं, उसी तरह की राय वाले बहुत से लोग हैं)।
एरिक फुन्केनबस

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

1
यह कुछ हद तक समस्या को कम कर सकता है, लेकिन एक बड़ा पर्याप्त अनुप्रयोग दिया गया है जो अभी भी राक्षस रिपॉजिटरी बनाएगा। मैं सीधे मुख्य तर्क में nHibernate का उपयोग करने के आयेंडे के समाधान से सहमत नहीं हूं, लेकिन मैं नियंत्रण से बाहर निकलने के विकास की बेरुखी के बारे में उनसे सहमत हूं। मैं डेटाबेस को सीधे क्वेरी नहीं करना चाहता, लेकिन मैं समस्या को रिपॉजिटरी से क्वेरी ऑब्जेक्ट्स के एक विस्फोट में स्थानांतरित नहीं करना चाहता।
एरिक फुन्केनबस

2

मैंने यह किया है, इसका समर्थन किया है और इसे पूर्ववत किया है।

प्रमुख समस्या यह है: कोई फर्क नहीं पड़ता कि आप इसे कैसे करते हैं, अतिरिक्त अमूर्त आपको स्वतंत्रता प्राप्त नहीं करता है। यह परिभाषा से लीक होगा। संक्षेप में, आप अपने कोड को प्यारा बनाने के लिए पूरी परत का आविष्कार कर रहे हैं ... लेकिन यह रखरखाव को कम नहीं करता है, पठनीयता में सुधार करता है या आपको किसी भी प्रकार का मॉडल अज्ञेयवाद हासिल करता है।

मज़े की बात यह है कि आपने ओलिवियर की प्रतिक्रिया के जवाब में अपने खुद के सवाल का जवाब दिया: "यह अनिवार्य रूप से लिनक की कार्यक्षमता को दोहरा रहा है, जो आपको लिनक से मिलने वाले सभी लाभों के बिना है"।

अपने आप से पूछें: यह कैसे नहीं हो सकता है?


ठीक है, मैंने निश्चित रूप से Linq को आपकी व्यावसायिक परत में एकीकृत करने की समस्याओं का अनुभव किया है। यह बहुत शक्तिशाली है, लेकिन जब हम डेटा मॉडल में बदलाव करते हैं तो यह एक बुरा सपना होता है। रिपॉजिटरी के साथ चीजों में सुधार किया जाता है, क्योंकि मैं व्यवसाय की परत को प्रभावित किए बिना एक स्थानीय स्थान में बदलाव कर सकता हूं (अन्य के अलावा जब आपको परिवर्तन का समर्थन करने के लिए व्यावसायिक परत को बदलना होगा)। लेकिन, रिपॉजिटरी ये फूली हुई परतें बन जाती हैं जो एसआरपी का बड़े पैमाने पर उल्लंघन करती हैं। मैं आपकी बात समझता हूं, लेकिन यह वास्तव में किसी भी समस्या का समाधान नहीं करता है।
एरिक फुन्केनबस

यदि आपकी डेटा परत LINQ का उपयोग करती है, और डेटा मॉडल में परिवर्तन से आपकी व्यावसायिक परत में परिवर्तन की आवश्यकता होती है ... तो आप ठीक से स्तरित नहीं हो रहे हैं।
स्टु

मुझे लगा कि आप कह रहे हैं कि आपने अब उस परत को नहीं जोड़ा है। जब आप कहते हैं कि अतिरिक्त अमूर्त आपको कुछ हासिल नहीं करता है, तो इसका मतलब है कि आप आयेंडे के साथ एनहाइबनेट सत्र (या ईएफ संदर्भ) को सीधे आपके व्यवसाय की परत में पारित करने के बारे में सहमत हैं।
एरिक फनकेनबसच

1

आप एक धाराप्रवाह इंटरफ़ेस का उपयोग कर सकते हैं। मूल विचार यह है कि कुछ क्रिया करने के बाद एक वर्ग की विधियाँ इस वर्ग को वर्तमान आवृत्ति लौटाती हैं। यह आपको चेन मेथड कॉल करने की अनुमति देता है।

एक उपयुक्त वर्ग पदानुक्रम बनाकर, आप सुलभ विधियों का एक तार्किक प्रवाह बना सकते हैं।

public class FinalQuery
{
    protected string _table;
    protected string[] _selectFields;
    protected string _where;
    protected string[] _groupBy;
    protected string _having;
    protected string[] _orderByDescending;
    protected string[] _orderBy;

    protected FinalQuery()
    {
    }

    public override string ToString()
    {
        var sb = new StringBuilder("SELECT ");
        AppendFields(sb, _selectFields);
        sb.AppendLine();

        sb.Append("FROM ");
        sb.Append("[").Append(_table).AppendLine("]");

        if (_where != null) {
            sb.Append("WHERE").AppendLine(_where);
        }

        if (_groupBy != null) {
            sb.Append("GROUP BY ");
            AppendFields(sb, _groupBy);
            sb.AppendLine();
        }

        if (_having != null) {
            sb.Append("HAVING").AppendLine(_having);
        }

        if (_orderBy != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderBy);
            sb.AppendLine();
        } else if (_orderByDescending != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderByDescending);
            sb.Append(" DESC").AppendLine();
        }

        return sb.ToString();
    }

    private static void AppendFields(StringBuilder sb, string[] fields)
    {
        foreach (string field in fields) {
            sb.Append(field).Append(", ");
        }
        sb.Length -= 2;
    }
}

public class GroupedQuery : FinalQuery
{
    protected GroupedQuery()
    {
    }

    public GroupedQuery Having(string condition)
    {
        if (_groupBy == null) {
            throw new InvalidOperationException("HAVING clause without GROUP BY clause");
        }
        if (_having == null) {
            _having = " (" + condition + ")";
        } else {
            _having += " AND (" + condition + ")";
        }
        return this;
    }

    public FinalQuery OrderBy(params string[] fields)
    {
        _orderBy = fields;
        return this;
    }

    public FinalQuery OrderByDescending(params string[] fields)
    {
        _orderByDescending = fields;
        return this;
    }
}

public class Query : GroupedQuery
{
    public Query(string table, params string[] selectFields)
    {
        _table = table;
        _selectFields = selectFields;
    }

    public Query Where(string condition)
    {
        if (_where == null) {
            _where = " (" + condition + ")";
        } else {
            _where += " AND (" + condition + ")";
        }
        return this;
    }

    public GroupedQuery GroupBy(params string[] fields)
    {
        _groupBy = fields;
        return this;
    }
}

आप इसे इस तरह कहेंगे

string query = new Query("myTable", "name", "SUM(amount) AS total")
    .Where("name LIKE 'A%'")
    .GroupBy("name")
    .Having("COUNT(*) > 2")
    .OrderBy("name")
    .ToString();

आप केवल एक नया उदाहरण बना सकते हैं Query। अन्य वर्गों में एक संरक्षित कंस्ट्रक्टर है। पदानुक्रम का बिंदु "अक्षम" करने के तरीके हैं। उदाहरण के लिए, वह GroupByविधि लौटाती है GroupedQueryजो आधार वर्ग है Queryऔर जिसमें Whereविधि नहीं है (जहाँ विधि घोषित की गई है Query)। इसलिए इसके Whereबाद कॉल करना संभव नहीं है GroupBy

यह हालांकि सही नहीं है। इस श्रेणी के पदानुक्रम से आप क्रमिक रूप से सदस्यों को छिपा सकते हैं, लेकिन नए नहीं दिखा सकते। इसलिए Havingएक अपवाद फेंकता है जब इसे पहले कहा जाता है GroupBy

ध्यान दें कि Whereकई बार कॉल करना संभव है । यह ANDमौजूदा स्थितियों के साथ नई स्थितियों को जोड़ता है । इससे एकल स्थितियों से प्रोग्राम को फ़िल्टर करना आसान हो जाता है। उसी के साथ संभव है Having

फ़ील्ड सूचियों को स्वीकार करने के तरीकों में एक पैरामीटर है params string[] fields। यह आपको एकल फ़ील्ड नाम या एक स्ट्रिंग सरणी पास करने की अनुमति देता है।


धाराप्रवाह इंटरफेस बहुत लचीले होते हैं और आपको मापदंडों के विभिन्न संयोजनों के साथ बहुत सारे अधिभार बनाने की आवश्यकता नहीं होती है। मेरा उदाहरण स्ट्रिंग्स के साथ काम करता है, हालांकि दृष्टिकोण को अन्य प्रकारों तक बढ़ाया जा सकता है। आप विशेष मामलों या कस्टम प्रकार को स्वीकार करने वाले तरीकों के लिए पूर्वनिर्धारित तरीकों की घोषणा भी कर सकते हैं। आप ExecuteReaderया जैसी विधियाँ भी जोड़ सकते हैं ExceuteScalar<T>। यह आपको इस तरह से प्रश्नों को परिभाषित करने की अनुमति देगा

var reader = new Query<Employee>(new MonthlyReportFields{ IncludeSalary = true })
    .Where(new CurrentMonthCondition())
    .Where(new DivisionCondition{ DivisionType = DivisionType.Production})
    .OrderBy(new StandardMonthlyReportSorting())
    .ExecuteReader();

यहां तक ​​कि इस तरह से निर्मित SQL कमांड में कमांड पैरामीटर हो सकते हैं और इस प्रकार SQL इंजेक्शन की समस्याओं से बच सकते हैं और साथ ही डेटाबेस सर्वर द्वारा कमांड को कैश करने की अनुमति देते हैं। यह O / R-mapper के लिए प्रतिस्थापन नहीं है, लेकिन उन स्थितियों में मदद कर सकता है जहां आप साधारण स्ट्रिंग समवर्ती का उपयोग करके कमांड बनाएंगे अन्यथा।


3
हम्म .. दिलचस्प है, लेकिन आपका समाधान SQL इंजेक्शन संभावनाओं के साथ समस्या है, और वास्तव में पूर्व संकलित निष्पादन के लिए तैयार बयान नहीं बनाता है (इस प्रकार अधिक धीरे-धीरे प्रदर्शन)। यह शायद उन समस्याओं को ठीक करने के लिए अनुकूलित किया जा सकता है, लेकिन फिर हम गैर-प्रकार के सुरक्षित डेटासेट परिणामों के साथ फंस गए हैं और क्या नहीं। मैं ORM आधारित समाधान पसंद करूंगा, और शायद मुझे यह स्पष्ट रूप से निर्दिष्ट करना चाहिए। यह अनिवार्य रूप से Linq की कार्यक्षमता को दोहरा रहा है जो आपको Linq से मिलने वाले सभी लाभों के बिना है।
एरिक फनकेनबस

मैं इन समस्याओं से अवगत हूं। यह सिर्फ एक त्वरित और गंदा समाधान है, जिसमें दिखाया गया है कि एक धाराप्रवाह इंटरफ़ेस का निर्माण कैसे किया जा सकता है। एक वास्तविक दुनिया के समाधान में आप अपनी जरूरतों के अनुकूल एक धाराप्रवाह इंटरफ़ेस में अपने मौजूदा दृष्टिकोण को "सेंकना" करेंगे।
ओलिवियर जैकोट-डेसकोम्बर्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.