MongoDb در سی شارپ (بخش هشتم)
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: پنج دقیقه

در الگوهایی که به عنوان واسط بین اپلیکیشن و دیتابیس تعریف میکنیم نام دو الگوی Repository و Unit of work به چشم میخورد. در این سایت بارها این مباحث به صورت گفتمان و مقالات تکرار شده‌اند و میدانیم که این الگوها کمک شایانی برای بالا بردن کارآیی برنامه، عدم تکرار کد، قابلیت استفاده مجدد و راحتی کار برای آزمون‌های واحد و چهارچوب‌های تقلید میکنند.

Unit of Work یا الگوی کار در واقع یک الگو، جهت جمع آوری عملیات کار با دیتابیس است که همه عملیات را تحت یک تراکنش به سمت دیتابیس ارسال میکند تا مبحث Atomic بودن عملیات، به مرحله اجرا گذاشته شود. در صورتیکه یکی از عملیات با نقص یا خطایی روبرو شود، کل عملیات Roll back یا برگشت میخورد. از آنجا که دیتابیس‌های معدودی چون Ravendb این مراحل را تا حدی پیاده سازی میکنند نباید از مونگو هم چنین انتظاری نداشته باشید. مونگو برخورد تراکنشی یا اتمیک ندارد؛ پس پیاده سازی الگوی واحد کاری تاثیری بر روی روند کاری آن ندارد. هر چند تعدادی مثال بدین شکل پیاده شده‌اند، ولی در عمل حقیقی نیستند و تنها یک حرکت مشابه داشته‌اند.

ولی الگوی repository  برای پرهیز از تکرار کد، قابلیت به روزرسانی کد و همچنین عملیاتی چون آزمون‌های واحد و چهارچوب تقلید به کار میرود. وابستگی بین اشیاء را کاهش داده و باعث ایجاد یک کد با دوام‌تر میگردد.

ابتدا قبل از هر چیزی نیاز است تا اتصالات یا ساخت کانکشن به سرور و همچنین دریافت دیتابیس مورد نظر را در قالب یک کلاس تعریف نماییم. نام آن را MongoDbContext میگذارم:
   public class MongoDbContext : IMongoDbContext
    {
        public const string DatabaseName = "MongoDbTest";

        private static readonly IMongoClient _client;
        private static readonly IMongoDatabase Database;

        static MongoDbContext()
        {
            _client = new MongoClient();
            Database = _client.GetDatabase(DatabaseName);
        }

        public IMongoCollection<TEntity> GetCollection<TEntity>()
        {
            return Database.GetCollection<TEntity>(typeof(TEntity).Name.ToLower() + "s");
        }
    }
در حالت بالا شما میتوانید در سازنده کلاس اتصال را برقرار کرده و دیتابیس را دریافت نمایید و از متد GetCollection در سطوح بالاتر، نوع کالکشن درخواستی خود را اعلام کنید. اگر به خط اول کلاس دقت نمایید میبینید که ما از اینترفیسی به نام IMongoDbContext که شامل خطوط زیر میباشد استفاده کردیم و دلیل استفاده این است که اگر قرار باشد از کلاس کانتکست، در کلاس‌های repository استفاده شود، ایجاد وابستگی میکند. چرا که معلوم نیست این کانتکست دقیقا چیست و از کجا آمده است و در آزمون واحد و همچنین تقلید دست ما را می‌بندد و الگوی repository را مردود اعلام میکند. پس از این حیث یک اینترفیس با محتوای زیر تولید کرده‌ایم که از این پس از آن در کدها استفاده میکنیم و پر کردن این اینترفیس‌ها را از طریق تزریق وابستگی‌ها در حالت Constructor Injection که ساده‌ترین نوع آن است انجام میدهیم:
public interface IMongoDbContext
    {    
        IMongoCollection<TEntity> GetCollection<TEntity>();
    }
در صورتیکه دوست دارید محتوای‌های دیگری را چون کانکشن استرینگ و .. نیز در اینجا بگنجانید، به عهده خود شماست. تنها نیاز به تغییراتی کوچک است.
در مرحله بعد یک IMongoDbRepositry ساخته و محتوای آن را به شکل زیر پر میکنیم:
public interface IMongoDbRepository
{
Task<List<TEntity>> GetMany<TEntity>(FilterDefinition<TEntity> filter) where TEntity : class, new();
}
در اینجا کلاس‌ها را از نوع جنریک تعریف میکنیم تا کاربر بتواند هر نوع کلاسی را که نیاز دارد، به سمت این مخزن ارسال کند. در پیاده سازی هم به شکل زیر آن را تعریف میکنیم:
public class MongoRepository : IMongoDbRepository
    {

        private IMongoDbContext _mongoDbContext ;
        public MongoRepository(IMongoDbContext mongoDbContext)
        {
            _mongoDbContext = mongoDbContext;
        }
 public async Task<List<TEntity>> GetMany<TEntity>(FilterDefinition<TEntity> filter) where TEntity : class, new()
        {            
                var collection = GetCollection<TEntity>();
                var entities = await collection.Find(filter).ToListAsync();                
                return entities;
        }

 private IMongoCollection<TEntity> GetCollection<TEntity>()
        {
            return _mongoDbContext.GetCollection<TEntity>();
        } 
 }
همانطور که می‌بینید در ابتدا در سازنده از طریق یک کتابخانه‌ی تزریق وابستگی‌ها (که در اینجا من از Structure map استفاده کرده‌ام) شیء ImongoDbContext را مقدار دهی میکنیم. الان اگر در اینجا بجای تعریف اینترفیس، از همان کلاس مستقیما استفاده میکردیم، بین دو لایه repository و context یک وابستگی ایجاد میشد. ولی در اینجا کانتکست میتواند هر چیزی باشد. بعد از آن به تعریف متد مورد نظر پرداخته‌ایم. البته با توجه به اینکه این تنها یک مثال است، بنده تنها یکی از این متدها را به عنوان نمونه نشان داده‌ام و میتوانید فایل‌های کامل آن را در انتهای مقاله دریافت نمایید. همانطور که مشاهده میکنیم، متدها به صورت غیرهمزمان نوشته شده‌اند که باعث مقیاس پذیری برنامه میشوند و در اینجا از متدهای همزمان استفاده نکرده‌ایم؛ چرا که افرادی که از دیتابیس‌های غیر رابطه‌ای استفاده میکنند، نیاز به مقیاس پذیری بالایی دارند. به همین دلیل نیاز چندانی به استفاده از متدهای همزمان دیده نمیشود. ولی خودتان در صورت تمایل میتوانید آن‌ها را به اینترفیس اضافه کنید. در ضمن در کد بالا متد خصوصی را جهت دریافت کالکشن نوشته‌ایم تا دریافت کالکشن را در کدها، تا حدی خلاصه‌تر و شیواتر کنیم.

الگوی بالا در یک کنترلر به شرح زیر استفاده شده است:
 public class HomeController : Controller
    {
        private IMongoDbRepository _mongoDbRepository;

        public HomeController(IMongoDbRepository mongoDbRepository)
        {
            this._mongoDbRepository = mongoDbRepository;
        }
        // GET: Home
        public async Task<ActionResult> Index()
        {
            var filter = Builders<Resturant>.Filter.Gte("Capacity", 400);
            var c =await _mongoDbRepository.GetMany<Resturant>(filter);       
            return View(c);
        }
    }
در کد بالا رستورانهایی را که 400 نفر یا بیشتر ظرفیت پذیرایی دارند، واکشی کرده و در ویوو نشان میدهد. در اینجا الگوی repo، توسط تزریق وابستگی‌ها ساخته شده و کانتکست آن‌ها به همین شکل ساخته خواهد شد و در کل کنترلر، قابلیت استفاده را دارند.

  MongoRepository.zip