این الگو در کتاب "Patterns of Enterprise Application Architecture" نوشته مارتین فاولر توضیح داده شده است.
یک وبسایت فروشگاه را در نظر بگیرید. در فرآیند حذف دستهبندیها، ابتدا دستهبندی را حذف میکنیم و سپس بعد از اطمینان از حذف صحیح دستهبندی، محصولات آن را حذف میکنیم. حالا، اگر هنگام حذف محصولات مشکلی پیش بیاید و محصولات به درستی حذف نشوند، دستهبندی حذف شده و ما مجموعهای از محصولات بدون دستهبندی خواهیم داشت!!!
اینجاست که الگوی Unit of Work به ما کمک میکند.
یک UnitOfWork، همانطور که از نامش پیداست، برای انجام کاری وجود دارد. این کار میتواند به سادگی دریافت و نمایش اطلاعات باشد، یا به پیچیدگی پردازش یک سفارش جدید. زمانی که از EntityFramework استفاده میکنید و یک DbContext ایجاد میکنید، در واقع در حال ایجاد یک UnitOfWork هستید.
الگوی Unit of Work یک الگوی طراحی است که میتوانید از آن برای ارائه Repository های مختلف در برنامه نیز استفاده کنید. این الگو ویژگیهای بسیار مشابهی با DbContext دارد، با این تفاوت که Unit of Work به Entity Framework Core وابسته نیست، برخلاف DbContext.
تا اینجا، چندین Repository ساختهایم. میتوانیم بهراحتی این Repository ها را به سازنده کلاسهای سرویس تزریق کرده و به دادهها دسترسی پیدا کنیم. این کار زمانی که تنها ۲ یا ۳ Repositoryدرگیر باشد، بسیار آسان است. اما وقتی تعداد Repository ها بیش از ۳ تا شود، افزودن تزریقهای جدید هر بار عملی نخواهد بود. برای قرار دادن همه Repository ها در یک شیء، از الگوی Unit Of Work استفاده میکنیم.
مزیت این روش این است که در مثال بالا، زمانی که ابتدا دستهبندیها را حذف میکنیم و سپس محصولات را، این تغییرات فقط در حافظه اعمال میشوند و با فراخوانی متد Commit
، یک تراکنش در پایگاه داده ایجاد میشود و تمام این تغییرات بهصورت یکجا در پایگاه داده اعمال میشوند. حالا اگر در وسط کار مشکلی پیش بیاید و بهعنوان مثال یکی از محصولات نتواند حذف شود، تمام تغییرات به حالت اولیه بازگردانده میشوند و انگار که هیچ تغییری در پایگاه داده ایجاد نشده است (و این روش یک خطا برمیگرداند و به کاربر اطلاع میدهد که حذف به درستی انجام نشده است.) و در نتیجه نه دستهبندیها و نه محصولات حذف نخواهند شد.
الگوی Unit of Work مسئول ارائه Repository های موجود و اعمال تغییرات در منبع داده (DataSource) است تا یک تراکنش کامل انجام شود و از از دست رفتن دادهها جلوگیری کند.
یکی دیگر از مزایای مهم این الگو این است که در صورت استفاده از چندین شیء Repository، هر یک از آنها میتوانند نمونههای متفاوتی از DbContext داشته باشند. این موضوع در موارد پیچیده ممکن است منجر به نشت دادهها شود.
توجه: IDisposable یک رابط است که شامل یک متد به نام Dispose() میباشد که برای آزادسازی منابع غیرمدیریتی مانند فایلها، جریانات، پایگاههای داده، اتصالات و غیره استفاده میشود.
الگوی طراحی Unit of Work عملیات حفظ دادهها را برای چندین شیء در کسبوکار ما بهعنوان یک تراکنش اتمیک (Atomic) اجرا میکند که اطمینان حاصل میکند که تمام تراکنش یا تأیید (commit) میشود یا برگشت (rollback) مییابد. الگوی طراحی Unit of Work چندین Repository را در خود جای میدهد و یک کانتکست پایگاه داده مشترک را بین آنها به اشتراک میگذارد.
محصورسازی یا Encapsulation به معنای حفاظت از یکپارچگی دادهها است. به این معنا که زمانی که یک کلاس بهخوبی محصور شده باشد، دادههای داخلی آن نمیتوانند در یک وضعیت یا مقدار نامعتبر و ناهماهنگ قرار بگیرند. این به معنای حفاظت از یکپارچگی و دقت دادهها است که به آن Encapsulation گفته میشود.
همانطور که ذکر شد، پنهانسازی اطلاعات و تجمیع دادهها یا عملیات انجامشده روی دادهها بهصورت مشترک به Encapsulation کمک میکند. پنهانسازی اطلاعات دادههای داخلی را از دید استفاده کنندگان آن کلاس مخفی میکند و قرار دادن دادهها و عملیات در کنار هم، یک نقطه ورود واحد برای تمام عملیاتی که میتوان بر روی آن کلاس انجام داد ایجاد میکند.
به این ترتیب، میتوانید قبل از تغییر وضعیت یا دادههای یک کلاس، یک بررسی انجام دهید و یکپارچگی دادههایی را که قرار است در کلاس شما قرار بگیرد، بررسی کنید.
این موضوع همچنین در بحث محصورسازی invariants (ثابتها) مطرح میشود. invariant به معنای یک سری شرایط است که باید در تمام اوقات حفظ شوند. بهعنوان یک برنامهنویس، شما باید اطمینان حاصل کنید که هیچیک از invariantها هرگز معیوب نشوند و وظیفه شماست که تدابیر لازم برای این کار را در نظر بگیرید.
این دو مفهوم بهطور نزدیکی با هم مرتبط هستند، اما تفاوتهای زیادی نیز دارند. محصورسازی (Encapsulation) به ثبات دادهها اشاره دارد، در حالی که انتزاع (Abstraction) به بزرگتر کردن جنبههای ضروری و حذف جنبههای غیرضروری میپردازد.
کمکی که انتزاع به ما میکند این است که دیگر نگران این نیستیم که آیا عملی که انجام میدهیم معتبر است یا خیر و این وظیفه را به Encapsulation واگذار کرده و بر آنچه که کد انجام میدهد، تمرکز میکنیم، نه بر اینکه چگونه این کار را انجام میدهد.
اینجا جایی است که اختلافنظرهایی در مورد ایجاد Repository وجود دارد.
همانطور که قبلاً اشاره شد، زمانی که یک انتزاع سطح بالا، مجموعهای از پیچیدگیهای سطح پایین را انتزاع میکند، از انتزاع استفاده میکنیم. وقتی خود DbContext یک Unit of Work است، دلیلی برای تعریف یک Unit of Work دیگر نداریم. با این پیادهسازی، در واقع با انتزاعهای سطحی مواجه هستیم که به آنها انتزاعهای سطحی (Shallow abstractions) میگوییم. در سناریوهایی که پیچیدگی در DbContext بیش از حد میشود، Unit of Work از حالت سطحی خارج شده و پیادهسازی آن توجیهپذیر میشود.
زمانی که از EF Core استفاده میکنید و یک DbContext ایجاد میکنید، در واقع در حال ایجاد یک Unit of Work هستید.
کلاس DbContext بر پایه الگوی Unit of Work استوار است و شامل تمام موجودیتهای DbSet میباشد. این کلاس عملیات پایگاه داده را بر روی این موجودیتها مدیریت کرده و سپس تمام این بهروزرسانیها را بهعنوان یک تراکنش در پایگاه داده ذخیره میکند.
کلاس DbContext ترکیبی از الگوهای Unit of Work و Repository است.