الگوی طراحی در asp.net core
شناسه پست: 2381
بازدید: 554

الگوی Repository چیست و چرا باید از آن استفاده کنیم؟

در یک اپلیکیشن، با الگوی Repository ما یک لایه abstraction بین لایه data access و لایه business logic ایجاد میکنیم. با استفاده از این الگو، ما رویکرد بیشتری از loosely coupled را برای دسترسی به داده هایمان از پایگاه داده ترویج میدهیم. همچنین این الگو باعث میشود که کد تمیزتری داشته باشیم و نگهداری و استفاده مجدد از آن راحت تر باشد. منطق Data access با مسئولیت پایداری مدل business اپلیکیشن، در یک کلاس جداگانه و یا در مجموعه ای از کلاسها به نام repository قرار دارد.

موضوع ما در این مقاله، پیاده سازی الگوی repository است. علاوه بر این، این مقاله، یک ارتباط قوی با EF Core دارد.

این مقاله، قسمتی از مجموعه آموزشی زیر میباشد:

اگر می خواهید تمام آموزشهای لازم و پایه مربوط به این دوره آموزشی را ببینید ، لطفاً روی این لینک کلیک کنید: صفحه مقدمه برای این آموزش.

برای مطالعه قسمت قبلی، این لینک را بررسی کنید: ایجاد پروژه NET Core WebApi. – لاگ سفارشی در NET Core.

این مقاله به چند قسمت زیر تقسیم میشود:

ایجاد Model ها

کارمان را با ایجاد یک پروژه Class Library جدید به نام Entities شروع میکنیم و داخل آن، یک پوشه جدید به نام Models ایجاد میکنیم که شامل تمام model classe ها میباشد. model classe ها ارائه دهنده جداول داخل پایگاه داده میباشند و برای ما، داده ها را از پایگاه داده به NET Core. مپ میکنند. بعد از این، باید این پروژه را به پروژه اصلی، رفرنس دهیم.

در پوشه Models، ما دو کلاس ایجاد میکنیم و به این صورت آنها را تغییر میدهیم:

همانطور که میبینید، دو مدل وجود دارد که با ویژگی Table(“tableName”) ست شده اند. این ویژگی نام جدول مربوطه را در پایگاه داده پیکربندی می کند. تمام فیلدهای اجباری، ویژگی  [Required] را بر روی خود دارند و اگر بخواهیم رشته ها را محدود کنیم میتوانیم از ویژگی [StringLength] استفاده کنیم. در کلاس Owner، خصوصیت Accounts را داریم که دلالت بر این دارد که یک Owner میتواند چندین Account داشته باشد. علاوه بر این، ما خصوصیات OwnerId و Owner را با ویژگی [ForeignKey] ست کردیم به این معنی که یک Account فقط دارای یک Owner میباشد.

کلاس Context و Database Connection

حال اجازه بدید تا کلاس context را ایجاد کنیم که یک کامپوننت middleware برای ارتباط با پایگاه داده میباشد. این کلاس، خصوصیت DbSet دارد که شامل داده های جدول از پایگاه داده میباشد.

در root پروژه Entities، یک کلاس به نام RepositoryContext ایجاد میکنیم و کدهای زیر را در آن قرار میدهیم:

توجه داشته باشید که باید پکیج Microsoft.EntityFrameworkCore را نصب نمایید.

برای برقراری ارتباط بین NET Core. و پایگاه داده MySQL، باید یک کتابخانه third-party به نام Pomelo.EntityFrameworkCore.MySql را نصب کنیم. در پروژه اصلی، ما میتوانیم آن را با NuGet package manager یا Package manager console نصب کنیم.

بعد از نصب، فایل appsettings.json را باز میکنیم و تنظیمات connection پایگاه داده را داخل آن اضافه میکنیم:

در کلاس ServiceExtensions، ما کدی را برای پیکربندی MySQL context مینویسیم.

اول، directive ها را با using اضافه میکنیم و سپس متد ConfigureMySqlContext را اضافه میکنیم:

با کمک پارامتر IConfiguration config، میتوانیم به فابل appsettings.json و تمام داده هایی که از آن نیاز داریم دسترسی داشته باشیم.

اگر از پروژه NET 5. و از ورژن 5 کتابخانه Pomelo استفاده میکنید باید از کدی متفاوت در متد ConfigureMySqlContext استفاده کنید. متد UseMySql اینجا یک پارامتر دیگر نیز دارد:

سپس در کلاس Startup، درون متد ConfigureServices، سرویس context را به IOC درست بالای ()services.AddControllers اضافه میکنیم:

منطق الگوی Repository

بعد از ایجاد اتصال به پایگاه داده، باید یک generic repository ایجاد کنیم تا برای تمام متدهای CRUD بتوانیم از آن استفاده کنیم. به عبارتی، تمام متدها میتوانند تحت هر کلاس repository در پروژه فراخوانی شوند.

علاوه بر این، ایجاد generic repository و کلاسهای repository که از این generic repository استفاده میکنند مرحله نهایی نمیباشد. بلکه بعد از آن، قرار است یک مرحله جلوتر برویم و یک wrapper برای کلاسهای repository ایجاد کنیم و آن را به عنوان یک سرویس، inject کنیم. در نتیجه، در هر کنترلری که نیاز داریم میتوانیم این wrapper را فقط یکبار نمونه سازی کنیم و سپس هر کلاس repository ای را که بخواهیم توسط آن فراخوانی کنیم. زمانیکه ما از این wrapper در پروژه استفاده میکنیم به مزایای آن پی خواهید برد.

اول، یک اینترفیس برای repository داخل پروژه Contracts میسازیم:

بعد از ایجاد اینترفیس، یک پروژه جدید از نوع Class Library (.NET Core) به نام Repository ایجاد میکنیم (reference را از Contracts و Entities به این پروژه اضافه میکنیم) و داخل پروژه Repository، یک کلاس abstract به نام RepositoryBase که اینترفیس IRepositoryBase را پیاده سازی میکند را ایجاد میکنیم.

این پروژه را به پروژه اصلی نیز رفرنس دهید.

کد زیر را به کلاس RepositoryBase اضافه میکنیم:

این کلاس abstract، همانند اینترفیس IRepositoryBase از نوع generic T برای کار با type های مختلف استفاده میکند. این نوع T امکان reusability بیشتری را به کلاس RepositoryBase میدهد. این به این معنی است که ما مجبور نیستیم که مدل مدنظرمان (class) را درست همین حالا برای RepositoryBase برای کار با آن تعیین کنیم، بلکه میخواهیم این کار را بعدا انجام دهیم.

کلاسهای Repository کاربر

حالا که ما کلاس RepositoryBase را داریم، classe های کاربری که از این کلاس abstract مشتق میشوند (ارث بری میکنند) را ایجاد میکنیم. هر class کاربر، اینترفیس مخصوص به خودش را برای متدهای اضافی ویژه مدل خودش خواهد داشت. علاوه بر این، با ارث بری کردن از کلاس RepositoryBase، آنها به تمام متدها از RepositoryBase دسترسی خواهند داشت. به این ترتیب ، ما logic را که برای همه کلاسهای repository کاربر مشترک است و از طرفی هم برای هر کلاس کاربر خاص است را جدا می کنیم.

اینترفیس ها را در پروژه Contracts برای کلاسهای Owner و Account ایجاد میکنیم.

فراموش نکنید که رفرنس را از پروژه Entities اضافه کنید. به محض اینکه این کار را انجام میدهیم، میتوانیم رفرنس Entities را از پروژه اصلی حذف کنیم. زیرا آن در حال حاضر از طریق پروژه Repository که از قبل شامل رفرنس پروژه Contracts میباشد به همراه رفرنس Entities داخل آن، ارائه شده است.

حال کلاسهای repository کاربر را در پروژه Repository ایجاد میکنیم:

بعد از این مراحل، ما ایجاد repository و کلاسهای repository کاربر را تمام میکنیم. اما هنوز کارهای دیگری نیز هست که باید انجام دهیم.

ایجاد یک Repository Wrapper

بیایید تصور کنیم اگر داخل یک کنترلر نیاز به جمع آوری همه Owner ها و جمع آوری فقط برخی از Account های خاص داشته باشیم (برای مثال فقط اکانتهای Domestic)، باید کلاسهای OwnerRepository و AccountRepository را نمونه سازی کنیم و متدهای FindAll و FindByCondition را فراخوانی کنیم.

این شاید زمانیکه فقط دو کلاس داشته باشیم مسئله ای نباشد، اما اگر منطقی از 5 کلاس مختلف و یا بیشتر را نیاز داشته باشیم آنوقت تکلیف چه میشود. با توجه به این موضوع، یک wrapper برای کلاسهای repository کاربر ایجاد میکنیم. سپس آن را داخل IOC قرار میدهیم و در آخر آن را داخل سازنده کنترلر تزریق میکنیم. حالا با این نمونه wrapper ها، این امکان وجود دارد که هر کلاس repository که نیاز داشته باشیم را فراخوانی کنیم.

حال یک اینترفیس جدید در پروژه Contract ایجاد میکنیم:

بعد از آن، یک کلاس جدید به پروژه Repository اضافه میکنیم:

همانطور که میبینید، ما خصوصیتهایی که repository های مربوطه را در اختیار ما قرار میدهند را ایجاد میکنیم و همچنین متد ()Save را داریم که بعد از تمام تغییراتی که بر روی یک آبجکت مشخص صورت میگیرد استفاده میکنیم. این یک شیوه مناسب است زیرا به فرض مثال، حالا میتوانیم دو owner اضافه کنیم، دو account را تغییر دهیم و یک owner را حذف کنیم و تمام اینها را در یک متد انجام دهیم و سپس در آخر فقط یکبار متد Save را فراخوانی کنیم. حال تمام تغییرات بر روی آبجکت اعمال خواهد شد و یا اینکه اگر چیزی به مشکل بخورد، تمام تغییرات برگردانده میشوند:

در کلاس ServiceExtensions، ما این کد را اضافه میکنیم:

و در کلاس Startup داخل متد ConfigureServices بالای خط ()services.AddControllers، این کد را اضافه میکنیم:

بسیار خب.

تست

حال تمام کاری که باید انجام دهیم این است که به همان شیوه ای که logger سفارشی که در قسمت 3 این سری آموزش تست کردیم را تست کنیم.

سرویس RepositoryWrapper را به داخل کنترلر WeatherForecast تزریق کنید و هر متدی از کلاس RepositoryBase را فراخوانی کنید:

داخل متد ()Get، یک breakpoint بگذارید و داده های برگشتی از پایگاه داده را ببینید.

در قسمت بعدی، به شما نشان میدهیم که اگر نمیخواهید که متدهای RepositoryBase اینجا در کنترلر در معرض نمایش قرار بگیرند را چطور محدود کنید.

نتیجه گیری

الگوی Repository، سطح انتزاع در کدتان را افزایش میدهد. این ممکن است که باعث شود که درک برای توسعه دهندگانی که با این الگو آشنا نیستند دشوارتر شود. اما زمانیکه شما با این الگو آشنایی دارید، این الگو مقدار کدهای زائد را کاهش میدهد و منطق را برای نگهداری راحت تر میسازد.

در این پست، شما یاد گرفتید که:

  • الگوی repository چیست
  • چطور مدل و ویژگی های مدل را اضافه کنیم
  • چطور کلاس context و database connection را بسازیم
  • راه صحیح ایجاد منطق repository چیست
  • و راه ایجاد یک wrapper برای کلاسهای repository تان چیست

بابت مطالعه این مقاله از شما سپاسگذاریم. امیدوارم که اطلاعات مفیدی را در این مقاله دریافت کرده باشید.

به زودی شما را در مقاله بعدی میبینیم، جاییکه ما از منطق repository برای ایجاد درخواستهای HTTP استفاده میکنیم.

نویسنده

امید عباسی
من امید عباسی هستم. سالهاست که در زمینه برنامه نویسی با تکنولوژی دات نت فعالیت میکنم و عاشق این هستم که تجربیات و دانش خودم را در این زمینه با دیگران به اشتراک بزارم. خیلی دوست دارم که نظر و انتقاد خودتون رو در مورد این نوشته برای من بنویسید تا بتونم در آینده، مطالب بهتر و ارزشمندتری را برای شما فراهم کنم. در صورت داشتن هرگونه سوال هم در قسمت دیدگاه ها میتونید با بنده در ارتباط باشید