नोट: यह उत्तर एंटिटी फ्रेमवर्क के बारे में बात करता है DbContext
, लेकिन यह किसी भी प्रकार के कार्य कार्यान्वयन के लिए लागू होता है, जैसे कि LINQ से SQL DataContext
और NHibernate का ISession
।
आइएन को गूँजने से शुरू करें: DbContext
पूरे आवेदन के लिए एक एकल होना एक खराब विचार है। एकमात्र स्थिति जहां यह समझ में आता है, जब आपके पास एकल-थ्रेडेड एप्लिकेशन और एक डेटाबेस है जो केवल उस एकल एप्लिकेशन इंस्टेंस द्वारा उपयोग किया जाता है। यह DbContext
थ्रेड-सुरक्षित नहीं है और चूंकि DbContext
कैश डेटा के बाद , यह जल्द ही बासी हो जाता है। यह आपको हर तरह की परेशानी में डालेगा जब कई उपयोगकर्ता / अनुप्रयोग उस डेटाबेस पर एक साथ काम करते हैं (जो कि बहुत ही सामान्य है)। लेकिन मुझे उम्मीद है कि आप पहले से ही जानते हैं कि और यह जानना चाहते हैं कि क्यों न DbContext
किसी की जरूरत पर एक नया उदाहरण (यानी एक क्षणिक जीवन शैली के साथ) इंजेक्ट करें । (इस बारे में अधिक जानकारी के लिए कि DbContext
प्रति थ्रेड के संदर्भ में एक भी क्यों -बुरा है, इस उत्तर को पढ़ें )।
मुझे यह कहने से शुरू करें कि एक DbContext
क्षणिक के रूप में पंजीकरण करना काम कर सकता है, लेकिन आम तौर पर आप एक निश्चित दायरे में इस तरह की एक इकाई का एक ही उदाहरण रखना चाहते हैं। एक वेब एप्लिकेशन में, वेब अनुरोध की सीमाओं पर इस तरह के दायरे को परिभाषित करना व्यावहारिक हो सकता है; इस प्रकार प्रति वेब अनुरोध जीवन शैली। यह आपको एक ही संदर्भ में वस्तुओं का एक पूरा सेट संचालित करने देता है। दूसरे शब्दों में, वे एक ही व्यापार लेनदेन के भीतर काम करते हैं।
यदि आपके पास समान संदर्भ के अंदर संचालन का एक सेट होने का कोई लक्ष्य नहीं है, तो उस स्थिति में क्षणिक जीवनशैली ठीक है, लेकिन देखने के लिए कुछ चीजें हैं:
- चूँकि हर वस्तु का अपना उदाहरण मिलता है, हर वर्ग जो सिस्टम की स्थिति को बदलता है, उसे कॉल करने की आवश्यकता होती है
_context.SaveChanges()
(अन्यथा परिवर्तन खो जाएगा)। यह आपके कोड को जटिल कर सकता है, और कोड के लिए एक दूसरी जिम्मेदारी (संदर्भ को नियंत्रित करने की जिम्मेदारी) को जोड़ता है, और एकल जिम्मेदारी सिद्धांत का उल्लंघन है ।
- आपको यह सुनिश्चित करने की आवश्यकता है कि इकाइयाँ [द्वारा लोड और सहेजे गए
DbContext
] ऐसी श्रेणी का दायरा कभी नहीं छोड़तीं, क्योंकि उनका उपयोग किसी अन्य वर्ग के संदर्भ उदाहरण में नहीं किया जा सकता है। यह आपके कोड को बहुत जटिल कर सकता है, क्योंकि जब आपको उन संस्थाओं की आवश्यकता होती है, तो आपको उन्हें फिर से आईडी द्वारा लोड करने की आवश्यकता होती है, जिससे प्रदर्शन की समस्याएं भी हो सकती हैं।
DbContext
लागू होने के बाद से IDisposable
, आप शायद सभी बनाए गए उदाहरणों को निपटाना चाहते हैं। यदि आप ऐसा करना चाहते हैं, तो आपके पास मूल रूप से दो विकल्प हैं। आपको कॉल करने के ठीक बाद उन्हें उसी तरीके से निपटाने की आवश्यकता है context.SaveChanges()
, लेकिन उस स्थिति में व्यावसायिक तर्क किसी ऐसी वस्तु का स्वामित्व लेता है जिसे वह बाहर से पास करता है। दूसरा विकल्प Http रिक्वेस्ट की सीमा पर सभी बनाए गए इंस्टेंस को डिस्पोज़ करना है, लेकिन उस स्थिति में भी आपको कंटेनर को यह बताने के लिए किसी प्रकार की स्कूपिंग की आवश्यकता होती है जब उन इंस्टेंसेस को डिस्पोज़ करने की आवश्यकता होती है।
एक अन्य विकल्प यह है कि बिल्कुल भी इंजेक्शन न लगाएं DbContext
। इसके बजाय, आप एक इंजेक्शन लगाते हैं जो DbContextFactory
एक नया उदाहरण बनाने में सक्षम होता है (मैं अतीत में इस दृष्टिकोण का उपयोग करता था)। इस तरह से व्यापार तर्क स्पष्ट रूप से संदर्भ को नियंत्रित करता है। अगर ऐसा दिख सकता है:
public void SomeOperation()
{
using (var context = this.contextFactory.CreateNew())
{
var entities = this.otherDependency.Operate(
context, "some value");
context.Entities.InsertOnSubmit(entities);
context.SaveChanges();
}
}
इसका प्लस पक्ष यह है कि आप DbContext
स्पष्ट रूप से जीवन का प्रबंधन करते हैं और इसे स्थापित करना आसान है। यह आपको एक निश्चित दायरे में एक ही संदर्भ का उपयोग करने की भी अनुमति देता है, जिसमें स्पष्ट लाभ हैं, जैसे कि एकल व्यापार लेनदेन में कोड चलाना और संस्थाओं से गुजरने में सक्षम होना, क्योंकि वे उसी से उत्पन्न होते हैं DbContext
।
नकारात्मक पक्ष यह है कि आपको DbContext
विधि से विधि (जिसे विधि इंजेक्शन कहा जाता है) के आसपास से गुजरना होगा । ध्यान दें कि यह समाधान 'स्कोप्ड' दृष्टिकोण के समान है, लेकिन अब यह गुंजाइश एप्लिकेशन कोड में ही नियंत्रित होती है (और संभवतः कई बार दोहराई जाती है)। यह एप्लिकेशन है जो काम की इकाई बनाने और निपटाने के लिए जिम्मेदार है। चूंकि DbContext
निर्भरता ग्राफ के निर्माण के बाद बनाया गया है, कन्स्ट्रक्टर इंजेक्शन तस्वीर से बाहर है और आपको विधि इंजेक्शन से अलग करने की आवश्यकता है जब आपको एक वर्ग से दूसरे तक संदर्भ पर पास करने की आवश्यकता होती है।
विधि इंजेक्शन वह बुरा नहीं है, लेकिन जब व्यावसायिक तर्क अधिक जटिल हो जाता है, और अधिक कक्षाएं शामिल हो जाती हैं, तो आपको इसे विधि से विधि और कक्षा से कक्षा तक पारित करना होगा, जो कोड को बहुत जटिल कर सकता है (मैंने देखा है) यह अतीत में है)। एक साधारण आवेदन के लिए, यह दृष्टिकोण हालांकि ठीक होगा।
डाउनसाइड्स के कारण, यह कारखाना दृष्टिकोण बड़ी प्रणालियों के लिए है, एक और दृष्टिकोण उपयोगी हो सकता है और वह वह है जहां आप कंटेनर या बुनियादी ढांचे कोड / संरचना रूट को कार्य की इकाई का प्रबंधन करते हैं। यह वह शैली है जिसके बारे में आपका प्रश्न है।
कंटेनर और / या इंफ्रास्ट्रक्चर को संभालने से, आपके एप्लिकेशन कोड को बनाने के लिए प्रदूषित नहीं होता है, (वैकल्पिक रूप से) और एक यूओडब्ल्यू उदाहरण का निपटान करें, जो व्यापार तर्क को सरल और स्वच्छ रखता है (सिर्फ एक जिम्मेदारी)। इस दृष्टिकोण के साथ कुछ कठिनाइयाँ हैं। उदाहरण के लिए, क्या आप उदाहरण के लिए कमिट और डिस्पोज़ करते थे?
वेब अनुरोध के अंत में कार्य की एक इकाई का निपटान किया जा सकता है। हालांकि, कई लोग गलत तरीके से मानते हैं कि यह कार्य की इकाई को प्रतिबद्ध करने का स्थान भी है। हालाँकि, उस बिंदु पर अनुप्रयोग में, आप केवल यह सुनिश्चित करने के लिए निर्धारित नहीं कर सकते हैं कि कार्य की इकाई वास्तव में प्रतिबद्ध होनी चाहिए। उदाहरण के लिए यदि व्यवसाय परत कोड एक अपवाद को फेंक देता है जो कॉलस्टैक से अधिक पकड़ा गया है, तो आप निश्चित रूप से कमिट नहीं करना चाहते हैं।
वास्तविक समाधान फिर से किसी प्रकार के दायरे को स्पष्ट रूप से प्रबंधित करने के लिए है, लेकिन इस बार इसे रचना रूट के अंदर करें। कमांड / हैंडलर पैटर्न के पीछे सभी व्यावसायिक तर्क को सार करते हुए , आप एक डेकोरेटर लिखने में सक्षम होंगे जो प्रत्येक कमांड हैंडलर के चारों ओर लपेटा जा सकता है जो ऐसा करने की अनुमति देता है। उदाहरण:
class TransactionalCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
readonly DbContext context;
readonly ICommandHandler<TCommand> decorated;
public TransactionCommandHandlerDecorator(
DbContext context,
ICommandHandler<TCommand> decorated)
{
this.context = context;
this.decorated = decorated;
}
public void Handle(TCommand command)
{
this.decorated.Handle(command);
context.SaveChanges();
}
}
यह सुनिश्चित करता है कि आपको केवल एक बार इस इंफ्रास्ट्रक्चर कोड को लिखना होगा। कोई भी ठोस डीआई कंटेनर आपको इस तरह के डेकोरेटर को सभी ICommandHandler<T>
कार्यान्वयन के चारों ओर एक सुसंगत तरीके से लपेटने के लिए कॉन्फ़िगर करने की अनुमति देता है।