مطالب
آموزش PouchDB : قسمت اول (معرفی)

آموزش PouchDB : معرفی

هدف این مقاله بر این است که شما را با دیتابیس PouchDB  آشنا سازد .

در مطلب اول هدف فقط آشنایی و نحوه نصب  PouchDB قرار خوهد داشت و در مطالب بعدی نحوه آشنایی با نحوه کدنویسی و استفاده به صورت آفلاین یا آنلاین بررسی خواهد شد .

فهرست مطالب :

  • بخش اول : معرفی PouchDB
  • شروع به کار با PouchDB
  • نحوه استفاده از API ها
  • سوالات متداول در مورد PouchDB
  • خطاهای احتمالی
  • پروژه‌ها و پلاگین های PouchDB


PouchDB یک دیتابیس NoSQL می‌باشد که به وسیله Javascript نوشته شده و هدف آن این است که برنامه نویس‌ها بتوانند برنامه‌هایی را توسعه و ارائه کنند که بتواند هم به صورت آفلاین و هم آنلاین سرویس دهی داشته باشند.
برنامه اطلاعات خودش را به صورت آفلاین ذخیره می‌کند و کاربر می‌تواند زمانیکه به اینترنت متصل نیست، از آنها استفاده کند. اما به محض اتصال به اینترنت، دیتابیس خودش را با دیتابیس آنلاین همگام (Sync) می‌کند. اینجاست که قدرت اصلی PouchDB مشخص می‌شود.
بزرگترین برتری  PouchDB همین است. دیتابیسی است که به صورت توکار قابلیت‌های همگام سازی را دارا می‌باشد و به صورت اتوماتیک این کار را انجام می‌دهد.
PouchDB یک پروژه‌ی اوپن سورس است که توسط  این افراد به روز می‌شود. البته باید گفت که PouchDB  از CouchDB الهام گرفته شده است. اگر شما هم قصد همکاری در این پروژه را دارید بهتر است که  راهنمای همکاری  را مطالعه کنید .



پشتیبانی مرورگرها

PouchDB پیش زمینه‌های مختلفی دارد که به آن این امکان را می‌دهد تا روی همه مرورگر‌ها و صد البته روی NodeJs کار کند. از IndexedDB بر روی Firefox/Chrome/Opera/IE و WebSql بر روی Safari و همچنین LevelDB بر روی NodeJs استفاده می‌کند.
در حال حاظر PouchDB  بر روی مرورگرهای زیر تست شده است:
  • فایرفاکس 12 و بالاتر
  • گوگل کروم 19 و بالاتر
  • اپرا 12 و بالاتر
  • سافاری 5 و بالاتر
  • اینترنت اکسپلورر 10 و بالاتر
  • NodeJs 0.10 و بالاتر
  • و به صورت شگفت انگیزی در Apache Cordova
برای اطلاعات بیشتر در مورد مرورگرهایی که IndexdDB و WebSql را پشتیبانی می‌کنند به لینک‌های زیر مراجعه کنید:
نکته : در صورتی که برنامه شما نیاز دارد تا از اینترنت اکسپلورر نسخه پایینتر از 10 استفاده کند می‌توانید از دیتابیس‌های آنلاین استفاده کنید، که البته دیگر قابلیت استفاده آفلاین را نخواهد داشت.



وضعیت فعلی PouchDB

PouchDB برای مرورگر، فعلا در وضعیت بتا به سر می‌برد و به صورت فعالی در حال گذراندن تست هایی می‌باشد تا باگ‌های آن برطرف شود و به صورت پایدار (Stable ) ارائه گردد. البته فقط ممکن است که شما باگی را در قسمت Api‌ها پیدا کنید که البته Api‌ها هم در حال حاضر پایدار هستند و گزارشی مبنی بر باگ در آن‌ها موجود نیست. اگر هم باگی پیدا بشود شما می‌توانید PouchDB  را بدون ریسک از دست رفتن اطلاعات آپگرید کنید.
PouchDB برای NodeJs فعلا در وضعیت آلفا است و آپگرید کردن ممکن است به اطلاعات شما آسیب بزند. البته با آپدیت به صورت دستی خطری شما را تهدید نخواهد کرد .



نحوه‌ی نصب PouchDB

PouchDB به صورت یک کتابخانه‌ی کوچک و جمع و جور طراحی شده است تا بتواند همه نیاز‌ها را برطرف و روی همه نوع Device اعم از موبایل، تبلت، مرورگر و کلا هر چیزی که جاوا اسکریپت را ساپورت می‌کند کار خود را به خوبی انجام بدهد.
برای استفاده از PouchDB میبایست این فایل را با حجم فوق العاده 97 کیلوبایت دانلود کنید  و آن را به یک صفحه وب اضافه کنید :
<script src="pouchdb-2.1.0.min.js"></script>

آخرین نسخه و بهترین نسخه : pouchdb-2.1.0.min.js
برای اطلاع از آخرین آپدیتها و نسخه‌ها به این صفحه در گیت هاب مراجعه کنید .
برای کسانی هم که از NodeJS استفاده می‌کنند نحوه نصب به این صورت است :
$ npm install pouchdb

مطالب
شروع به کار با EF Core 1.0 - قسمت 9 - بررسی رابطه‌ی One-to-One
بررسی رابطه‌ی One-to-Zero-or-One

زمانیکه نیاز است موجودیت A با هیچ و یا حداکثر یک وهله از موجودیت B در ارتباط باشد، به یک چنین رابطه‌ای One-to-Zero-or-One می‌گویند. برای اینکه یک چنین ارتباطی را تشکیل دهیم، نیاز است کلید اصلی یک جدول، در جدول مرتبط به آن، هم به عنوان کلید اصلی و هم به عنوان کلید خارجی معرفی شود؛ همانند شکل زیر که در آن CartableId، همزمان به صورت PK و FK تعریف شده‌است که به آن one-to-one association with shared primary key نیز می‌گویند:



الف) مدلسازی رابطه‌ی One-to-Zero-or-One توسط Fluent API

در اینجا دو موجودیت را ملاحظه می‌کنید که توسط دو navigation property به هم متصل شده‌اند:
    public class UserProfile
    {
        public int UserProfileId { get; set; }
        public string UserName { get; set; }

        public virtual Cartable Cartable { get; set; }
    }

    public class Cartable
    {
        public int CartableId { get; set; }

        public virtual UserProfile UserProfile { get; set; }
    }
برای اینکه بتوان CartableId را هم به عنوان PK و هم FK معرفی کرد، نیاز است از Fluent API به نحو ذیل استفاده کنیم:
    public class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Cartable>(entity =>
            {
                entity.Property(e => e.CartableId).ValueGeneratedNever();

                entity.HasOne(d => d.UserProfile)
                    .WithOne(p => p.Cartable)
                    .HasForeignKey<Cartable>(d => d.CartableId);
            });
        }

        public virtual DbSet<Cartable> Cartables { get; set; }
        public virtual DbSet<UserProfile> UserProfiles { get; set; }
    }
در اینجا رابطه‌ی یک به یک، توسط متدهای HasOne و WithOne معرفی شده‌است. به علاوه FK بودن CartableId به صورت صریح توسط متد HasForeignKey نیز مشخص گردیده‌است.
همچنین بر روی CartableId، فراخوانی متد ValueGeneratedNever را مشاهده می‌کنید. این متد را در قسمت «روش‌های مختلف تولید خودکار مقادیر خواص» مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها» پیشتر بررسی کردیم. هدف آن این است که به بانک اطلاعاتی اعلام کند، این فیلد یک کلید اصلی از نوع خود افزایش یابنده نیست و مقدار آن توسط برنامه به صورت صریح تنظیم می‌شود (چون کلید خارجی نیز هست و به کلید اصلی جدول سمت دیگر رابطه اشاره می‌کند).

ب) مدلسازی رابطه‌ی One-to-Zero-or-One توسط Data Annotations
برای تنظیم این رابطه توسط ویژگی‌ها نیاز است DatabaseGenerated به None تنظیم شود تا کلید اصلی CartableId به صورت خودکار توسط بانک اطلاعاتی تولید نشود. همچنین این کلید اصلی نیز باید به صورت کلید خارجی نیز معرفی شود. به علاوه توسط InversePropertyها، باید هر دو سطر به هم مرتبط، ذکر شوند:
    public class Cartable
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CartableId { get; set; }

        [ForeignKey("CartableId")]
        [InverseProperty("Cartable")]
        public virtual UserProfile UserProfile { get; set; }
    }

    public class UserProfile
    {
        public int UserProfileId { get; set; }
        public string UserName { get; set; }

        [InverseProperty("UserProfile")]
        public virtual Cartable Cartable { get; set; }
    }


بررسی رابطه‌ی One-to-One

تشکیل رابطه‌ی One-to-One که در آن برخلاف رابطه‌ی One-to-Zero-or-One، وجود هر دو سر رابطه اجباری هستند، در SQL Server میسر نیست (زیرا زمانیکه یک چنین رابطه‌ای تشکیل شود، نمی‌توان رکوردی را Insert کرد! چون زمانیکه هنوز یک سر رابطه ثبت نشده‌است، چگونه می‌توان Id آن‌را در سر دیگری به اجبار ثبت کرد؟!). SQL Server این رابطه را نیز به صورت همان One-to-Zero-or-One تشکیل می‌دهد. تنظیمات آن نیز با قبل تفاوتی ندارد. در این حالت اجباری بودن و یا نبودن یک سر رابطه همانند نکات قسمت «تعیین اجباری بودن یا نبودن ستون‌ها در EF Core» در مطلب «شروع به کار با EF Core 1.0 - قسمت 6 - تعیین نوع‌های داده و ویژگی‌های آن‌ها» است و این تنظیمات در اینجا صرفا از دیدگاه EF Core مفهوم دارند.
جهت تکمیل بحث، روش تشکیل رابطه‌ی One-to-One بدون استفاده از روش به اشتراک گذاری کلید اصلی (one-to-one association with shared primary key) به صورت زیر است:


همانطور که مشاهده می‌کنید، در اینجا هر بلاگ حداکثر یک تصویر را می‌تواند داشته باشد. علت آن نیز به ذکر MyBlogForeignKey بر می‌گردد که یک کلید خارجی نال نپذیر است.
    public class MyBlog
    {
        public int MyBlogId { get; set; }
        public string Url { get; set; }

        public MyBlogImage MyBlogImage { get; set; }
    }

    public class MyBlogImage
    {
        public int MyBlogImageId { get; set; }
        public byte[] Image { get; set; }
        public string Caption { get; set; }

        public int MyBlogForeignKey { get; set; }
        public MyBlog MyBlog { get; set; }
    }
با این تنظیمات:
modelBuilder.Entity<MyBlog>()
   .HasOne(p => p.MyBlogImage)
   .WithOne(i => i.MyBlog)
   .HasForeignKey<MyBlogImage>(b => b.MyBlogForeignKey);
در اینجا جدول MyBlogImage هنوز Id خود افزاینده‌ی خود را دارد، اما ثبت رکورد آن بدون وجود کلید خارجی MyBlog میسر نیست.
مطالب دوره‌ها
به روز رسانی خواص راهبری و مجموعه‌های Entity Framework توسط AutoMapper
فرض کنید مدل‌های بانک اطلاعاتی ما چنین ساختاری را دارند:
public abstract class BaseEntity
{
    public int Id { set; get; }
}

public class User : BaseEntity
{
        public string Name { set; get; }
 
        public virtual ICollection<Advertisement> Advertisements { get; set; }
}

public class Advertisement : BaseEntity
{
    public string Title { get; set; }
    public string Description { get; set; }
 
    [ForeignKey("UserId")]
    public virtual User User { get; set; }
    public int UserId { get; set; }
}
و همچنین مدل‌های رابط کاربری یا ViewModel‌های برنامه نیز به صورت ذیل تعریف شده‌اند:
public class AdvertisementViewModel
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int UserId { get; set; }
}
 
public class UserViewModel
{
    public int Id { set; get; }
    public string Name { set; get; }
    public List<AdvertisementViewModel> Advertisements { get; set; }
}


به روز رسانی خواص راهبری Entity framework توسط AutoMapper

در کلاس‌های فوق، یک کاربر، تعدادی تبلیغات را می‌تواند ثبت کند. در این حالت اگر بخواهیم خاصیت User کلاس Advertisement را توسط AutoMapper به روز کنیم، با رعایت دو نکته، اینکار به سادگی انجام خواهد شد:
الف) همانطور که در کلاس Advertisement جهت تعریف کلید خارجی مشخص است، UserId نیز علاوه بر User ذکر شده‌است. این مورد کار نگاشت UserId اطلاعات دریافتی از کاربر را ساده کرده و در این حالت نیازی به یافتن اصل User این UserId از بانک اطلاعاتی نخواهد بود.
ب) چون در اطلاعات دریافتی از کاربر تنها Id او را داریم و نه کل شیء مرتبط را، بنابراین باید به AutoMapper اعلام کنیم تا از این خاصیت صرفنظر کند که اینکار توسط متد Ignore به نحو ذیل قابل انجام است:
this.CreateMap<AdvertisementViewModel, Advertisement>()
      .ForMember(advertisement => advertisement.Description, opt => opt.Ignore())
      .ForMember(advertisement => advertisement.User, opt => opt.Ignore());


به روز رسانی مجموعه‌های Entity Framework توسط AutoMapper

فرض کنید چنین اطلاعاتی از کاربر و رابط کاربری برنامه دریافت شده است:
var uiUser1 = new UserViewModel
{
    Id = 1,
    Name = "user 1",
    Advertisements = new List<AdvertisementViewModel>
    {
        new AdvertisementViewModel
        {
            Id = 1,
            Title = "Adv 1",
            UserId = 1
        },
        new AdvertisementViewModel
        {
            Id = 2,
            Title = "Adv 2",
            UserId = 1
        }
    }
};
اکنون می‌خواهیم معادل این رکورد را از بانک اطلاعاتی یافته و سپس اطلاعات آن‌را بر اساس اطلاعات UI به روز کنیم. شاید در نگاه اول چنین روشی پیشنهاد شود:
 var dbUser1 = ctx.Users.Include(user => user.Advertisements).First(x => x.Id == uiUser1.Id);
Mapper.Map(source: uiUser1, destination: dbUser1);
ابتدا کاربری را که Id آن مساوی uiUser1.Id است، یافته و سپس به AutoMapper اعلام می‌کنیم تا تمام اطلاعات آن‌را به صورت یکجا به روز کند. این نگاشت را نیز برای آن تعریف خواهیم کرد:
 this.CreateMap<UserViewModel, User>()
در یک چنین حالتی، ابتدا شیء user 1 از بانک اطلاعاتی دریافت شده (و با توجه به وجود Include، تمام تبلیغات او نیز دریافت می‌شوند)، سپس ... دو رکورد دریافتی از کاربر، کاملا جایگزین اطلاعات موجود می‌شوند. این جایگزینی سبب تخریب پروکسی‌های EF می‌گردند. برای مثال اگر پیشتر تبلیغی با Id=1 در بانک اطلاعاتی وجود داشته، اکنون با نمونه‌ی جدیدی جایگزین می‌شود که سیستم Tracking و ردیابی EF اطلاعاتی در مورد آن ندارد. به همین جهت اگر در این حالت ctx.SaveChanges فراخوانی شود، عملیات ثبت و یا به روز رسانی با شکست مواجه خواهد شد.

علت را در این دو تصویر بهتر می‌توان مشاهده کرد:



تصویر اول که مستقیما از بانک اطلاعاتی حاصل شده‌است، دارای پروکسی‌های EF است. اما در تصویر دوم، جایگزین شدن این پروکسی‌ها را مشاهده می‌کنید که سبب خواهد شد این اشیاء دیگر تحت نظارت EF نباشند.


راه حل:

در این مورد خاص باید به AutoMapper اعلام کنیم تا کاری با لیست تبلیغات کاربر دریافت شده‌ی از بانک اطلاعاتی نداشته باشد و آن‌را راسا جایگزین نکند:
this.CreateMap<UserViewModel, User>().ForMember(user => user.Advertisements, opt => opt.Ignore());
در اینجا متد Ignore را بر روی لیست تبلیغات کاربر بانک اطلاعاتی فراخوانی کرده‌ایم، تا اطلاعات آن پس از اولین نگاشت انجام شده‌ی توسط AutoMapper دست نخورده باقی بماند.
سپس کار ثبت یا به روز رسانی را به صورت نیمه خودکار مدیریت می‌کنیم:
using (var ctx = new MyContext())
{
    var dbUser1 = ctx.Users.Include(user => user.Advertisements).First(x => x.Id == uiUser1.Id);
    Mapper.Map(source: uiUser1, destination: dbUser1);
 
    foreach (var uiUserAdvertisement in uiUser1.Advertisements)
    {
        var dbUserAdvertisement = dbUser1.Advertisements.FirstOrDefault(ad => ad.Id == uiUserAdvertisement.Id);
        if (dbUserAdvertisement == null)
        {
            // Add new record
            var advertisement = Mapper.Map<AdvertisementViewModel, Advertisement>(uiUserAdvertisement);
            dbUser1.Advertisements.Add(advertisement);
        }
        else
        {
            // Update the existing record
            Mapper.Map(uiUserAdvertisement, dbUserAdvertisement);
        }
    }
 
    ctx.SaveChanges();
}
- در اینجا ابتدا db user معادل اطلاعات ui user از بانک اطلاعاتی، به همراه لیست تبلیغات او دریافت می‌شود و اطلاعات ابتدایی او نگاشت خواهند شد.
- سپس بر روی اطلاعات تبلیغات دریافتی از کاربر، یک حلقه را تشکیل خواهیم داد. در اینجا هربار بررسی می‌کنیم که آیا معادل این تبلیغ هم اکنون به شیء db user متصل است یا خیر؟ اگر متصل نبود، یعنی یک رکورد جدید است و باید Add شود. اگر متصل بود صرفا باید به روز رسانی صورت گیرد.
- برای حالت ایجاد شیء جدید بانک اطلاعاتی، بر اساس uiUserAdvertisement دریافتی، می‌توان از متد Mapper.Map استفاده کرد؛ خروجی این متد، یک شیء جدید تبلیغ است.
- برای حالت به روز رسانی اطلاعات db user موجود، بر اساس اطلاعات ارسالی کاربر نیز می‌توان از متد Mapper.Map کمک گرفت.


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


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
AM_Sample04.zip
مسیرراه‌ها
NHibernate
      نظرات مطالب
      سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه
      مطلب «شروع به کار با EF Core 1.0 - قسمت 2 - به روز رسانی ساختار بانک اطلاعاتی» را مطالعه کنید. صرف تعریف یک خاصیت که کافی نیست. «Invalid column name» یعنی این ستون در بانک اطلاعاتی وجود خارجی ندارد. بنابراین مرحله‌ی بعد، اجرای دستی مهاجرت‌ها است که در اینجا همان اجرای فایل _01-add_migrations.cmd است.
      مطالب
      آشنایی با NHibernate - قسمت هشتم

      معرفی الگوی Repository

      روش متداول کار با فناوری‌های مختلف دسترسی به داده‌ها عموما بدین شکل است:
      الف) یافتن رشته اتصالی رمزنگاری شده به دیتابیس از یک فایل کانفیگ (در یک برنامه اصولی البته!)
      ب) باز کردن یک اتصال به دیتابیس
      ج) ایجاد اشیاء Command برای انجام عملیات مورد نظر
      د) اجرا و فراخوانی اشیاء مراحل قبل
      ه) بستن اتصال به دیتابیس و آزاد سازی اشیاء

      اگر در برنامه‌های یک تازه کار به هر محلی از برنامه او دقت کنید این 5 مرحله را می‌توانید مشاهده کنید. همه جا! قسمت ثبت، قسمت جستجو، قسمت نمایش و ...
      مشکلات این روش:
      1- حجم کارهای تکراری انجام شده بالا است. اگر قسمتی از فناوری دسترسی به داده‌ها را به اشتباه درک کرده باشد، پس از مطالعه بیشتر و مشخص شدن نحوه‌ی رفع مشکل، قسمت عمده‌ای از برنامه را باید اصلاح کند (زیرا کدهای تکراری همه جای آن پراکنده‌اند).
      2- برنامه نویس هر بار باید این مراحل را به درستی انجام دهد. اگر در یک برنامه بزرگ تنها قسمت آخر در یکی از مراحل کاری فراموش شود دیر یا زود برنامه تحت فشار کاری بالا از کار خواهد افتاد (و متاسفانه این مساله بسیار شایع است).
      3- برنامه منحصرا برای یک نوع دیتابیس خاص تهیه خواهد شد و تغییر این رویه جهت استفاده از دیتابیسی دیگر (مثلا کوچ برنامه از اکسس به اس کیوال سرور)، نیازمند بازنویسی کل برنامه می‌باشد.
      و ...

      همین برنامه نویس پس از مدتی کار به این نتیجه می‌رسد که باید برای این‌کارهای متداول، یک لایه و کلاس دسترسی به داده‌ها را تشکیل دهد. اکنون هر قسمتی از برنامه برای کار با دیتابیس باید با این کلاس مرکزی که انجام کارهای متداول با دیتابیس را خلاصه می‌کند، کار کند. به این صورت کد نویسی یک نواختی با حذف کدهای تکراری از سطح برنامه و همچنین بدون فراموش شدن قسمت مهمی از مراحل کاری، حاصل می‌گردد. در اینجا اگر روزی قرار شد از یک دیتابیس دیگر استفاده شود فقط کافی است یک کلاس برنامه تغییر کند و نیازی به بازنویسی کل برنامه نخواهد بود.

      این روزها تشکیل این لایه دسترسی به داده‌ها (data access layer یا DAL) نیز مزموم است! و دلایل آن در مباحث چرا به یک ORM نیازمندیم برشمرده شده است. جهت کار با ORM ها نیز نیازمند یک لایه دیگر می‌باشیم تا یک سری اعمال متداول با آن‌هارا کپسوله کرده و از حجم کارهای تکراری خود بکاهیم. برای این منظور قبل از اینکه دست به اختراع بزنیم، بهتر است به الگوهای طراحی برنامه نویسی شیء گرا رجوع کرد و از رهنمودهای آن استفاده نمود.

      الگوی Repository یکی از الگوهای برنامه‌ نویسی با مقیاس سازمانی است. با کمک این الگو لایه‌ای بر روی لایه نگاشت اشیاء برنامه به دیتابیس تشکیل شده و عملا برنامه را مستقل از نوع ORM مورد استفاه می‌کند. به این صورت هم از تشکیل یک سری کدهای تکراری در سطح برنامه جلوگیری شده و هم از وابستگی بین مدل برنامه و لایه دسترسی به داده‌ها (که در اینجا همان NHibernate می‌باشد) جلوگیری می‌شود. الگوی Repository (مخزن)، کار ثبت،‌ حذف، جستجو و به روز رسانی داده‌ها را با ترجمه آن‌ها به روش‌های بومی مورد استفاده توسط ORM‌ مورد نظر، کپسوله می‌کند. به این شکل شما می‌توانید یک الگوی مخزن عمومی را برای کارهای خود تهیه کرده و به سادگی از یک ORM به ORM دیگر کوچ کنید؛ زیرا کدهای برنامه شما به هیچ ORM خاصی گره نخورده و این عملیات بومی کار با ORM توسط لایه‌ای که توسط الگوی مخزن تشکیل شده، صورت گرفته است.

      طراحی کلاس مخزن باید شرایط زیر را برآورده سازد:
      الف) باید یک طراحی عمومی داشته باشد و بتواند در پروژه‌های متعددی مورد استفاده مجدد قرار گیرد.
      ب) باید با سیستمی از نوع اول طراحی و کد نویسی و بعد کار با دیتابیس، سازگاری داشته باشد.
      ج) باید امکان انجام آزمایشات واحد را سهولت بخشد.
      د) باید وابستگی کلاس‌های دومین برنامه را به زیر ساخت ORM مورد استفاده قطع کند (اگر سال بعد به این نتیجه رسیدید که ORM ایی به نام XYZ برای کار شما بهتر است، فقط پیاده سازی این کلاس باید تغییر کند و نه کل برنامه).
      ه) باید استفاده از کوئری‌هایی از نوع strongly typed را ترویج کند (مثل کوئری‌هایی از نوع LINQ).


      بررسی مدل برنامه

      مدل این قسمت (برنامه NHSample4 از نوع کنسول با همان ارجاعات متداول ذکر شده در قسمت‌های قبل)، از نوع many-to-many می‌باشد. در اینجا یک واحد درسی توسط چندین دانشجو می‌تواند اخذ شود یا یک دانشجو می‌تواند چندین واحد درسی را اخذ نماید که برای نمونه کلاس دیاگرام و کلاس‌های متشکل آن به شکل زیر خواهند بود:



      using System.Collections.Generic;

      namespace NHSample4.Domain
      {
      public class Course
      {
      public virtual int Id { get; set; }
      public virtual string Teacher { get; set; }
      public virtual IList<Student> Students { get; set; }

      public Course()
      {
      Students = new List<Student>();
      }
      }
      }


      using System.Collections.Generic;

      namespace NHSample4.Domain
      {
      public class Student
      {
      public virtual int Id { get; set; }
      public virtual string Name { get; set; }
      public virtual IList<Course> Courses { get; set; }

      public Student()
      {
      Courses = new List<Course>();
      }
      }
      }

      کلاس کانفیگ برنامه جهت ایجاد نگاشت‌ها و سپس ساخت دیتابیس متناظر

      using FluentNHibernate.Automapping;
      using FluentNHibernate.Cfg;
      using FluentNHibernate.Cfg.Db;
      using NHibernate.Tool.hbm2ddl;

      namespace NHSessionManager
      {
      public class Config
      {
      public static FluentConfiguration GetConfig()
      {
      return
      Fluently.Configure()
      .Database(
      MsSqlConfiguration
      .MsSql2008
      .ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
      )
      .Mappings(
      m => m.AutoMappings.Add(
      new AutoPersistenceModel()
      .Where(x => x.Namespace.EndsWith("Domain"))
      .AddEntityAssembly(typeof(NHSample4.Domain.Course).Assembly))
      .ExportTo(System.Environment.CurrentDirectory)
      );
      }

      public static void CreateDb()
      {
      bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
      bool export = true;//آیا بر روی دیتابیس هم اجرا شود
      bool dropTables = false;//آیا جداول موجود دراپ شوند
      new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
      }
      }
      }
      چند نکته در مورد این کلاس:
      الف) با توجه به اینکه برنامه از نوع ویندوزی است، برای مدیریت صحیح کانکشن استرینگ، فایل App.Config را به برنامه افروده و محتویات آن‌را به شکل زیر تنظیم می‌کنیم (تا کلید DbConnectionString توسط متد GetConfig مورد استفاده قرارگیرد ):

      <?xml version="1.0" encoding="utf-8" ?>
      <configuration>
      <connectionStrings>
      <!--NHSessionManager-->
      <add name="DbConnectionString"
      connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
      </connectionStrings>
      </configuration>

      ب) در NHibernate سنتی (!) کار ساخت نگاشت‌ها توسط یک سری فایل xml صورت می‌گیرد که با معرفی فریم ورک Fluent NHibernate و استفاده از قابلیت‌های Auto Mapping آن، این‌کار با سهولت و دقت هر چه تمام‌تر قابل انجام است که توضیحات نحوه‌ی انجام ‌آن‌را در قسمت‌های قبل مطالعه فرمودید. اگر نیاز بود تا این فایل‌های XML نیز جهت بررسی شخصی ایجاد شوند، تنها کافی است از متد ExportTo آن همانگونه که در متد GetConfig استفاده شده، کمک گرفته شود. به این صورت پس از ایجاد خودکار نگاشت‌ها، فایل‌های XML متناظر نیز در مسیری که به عنوان آرگومان متد ExportTo مشخص گردیده است، تولید خواهند شد (دو فایل NHSample4.Domain.Course.hbm.xml و NHSample4.Domain.Student.hbm.xml را در پوشه‌ای که محل اجرای برنامه است خواهید یافت).

      با فراخوانی متد CreateDb این کلاس، پس از ساخت خودکار نگاشت‌ها، database schema متناظر، در دیتابیسی که توسط کانکشن استرینگ برنامه مشخص شده، ایجاد خواهد شد که دیتابیس دیاگرام آن‌را در شکل ذیل مشاهده می‌نمائید (جداول دانشجویان و واحدها هر کدام به صورت موجودیتی مستقل ایجاد شده که ارجاعات آن‌ها در جدولی سوم نگهداری می‌شود).



      پیاده سازی الگوی مخزن

      اینترفیس عمومی الگوی مخزن به شکل زیر می‌تواند باشد:

      using System;
      using System.Linq;
      using System.Linq.Expressions;

      namespace NHSample4.NHRepository
      {
      //Repository Interface
      public interface IRepository<T>
      {
      T Get(object key);

      T Save(T entity);
      T Update(T entity);
      void Delete(T entity);

      IQueryable<T> Find();
      IQueryable<T> Find(Expression<Func<T, bool>> predicate);
      }
      }

      سپس پیاده سازی آن با توجه به کلاس SingletonCore ایی که در قسمت قبل تهیه کردیم (جهت مدیریت صحیح سشن فکتوری)، به صورت زیر خواهد بود.
      این کلاس کار آغاز و پایان تراکنش‌ها را نیز مدیریت کرده و جهت سهولت کار اینترفیس IDisposable را نیز پیاده سازی می‌کند :

      using System;
      using System.Linq;
      using NHSessionManager;
      using NHibernate;
      using NHibernate.Linq;

      namespace NHSample4.NHRepository
      {
      public class Repository<T> : IRepository<T>, IDisposable
      {
      private ISession _session;
      private bool _disposed = false;

      public Repository()
      {
      _session = SingletonCore.SessionFactory.OpenSession();
      BeginTransaction();
      }

      ~Repository()
      {
      Dispose(false);
      }

      public T Get(object key)
      {
      if (!isSessionSafe) return default(T);

      return _session.Get<T>(key);
      }

      public T Save(T entity)
      {
      if (!isSessionSafe) return default(T);

      _session.Save(entity);
      return entity;
      }

      public T Update(T entity)
      {
      if (!isSessionSafe) return default(T);

      _session.Update(entity);
      return entity;
      }

      public void Delete(T entity)
      {
      if (!isSessionSafe) return;

      _session.Delete(entity);
      }

      public IQueryable<T> Find()
      {
      if (!isSessionSafe) return null;

      return _session.Linq<T>();
      }

      public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
      {
      if (!isSessionSafe) return null;

      return Find().Where(predicate);
      }

      void Commit()
      {
      if (!isSessionSafe) return;

      if (_session.Transaction != null &&
      _session.Transaction.IsActive &&
      !_session.Transaction.WasCommitted &&
      !_session.Transaction.WasRolledBack)
      {
      _session.Transaction.Commit();
      }
      else
      {
      _session.Flush();
      }
      }

      void Rollback()
      {
      if (!isSessionSafe) return;

      if (_session.Transaction != null && _session.Transaction.IsActive)
      {
      _session.Transaction.Rollback();
      }
      }

      private bool isSessionSafe
      {
      get
      {
      return _session != null && _session.IsOpen;
      }
      }

      void BeginTransaction()
      {
      if (!isSessionSafe) return;

      _session.BeginTransaction();
      }


      public void Dispose()
      {
      Dispose(true);
      // tell the GC that the Finalize process no longer needs to be run for this object.
      GC.SuppressFinalize(this);
      }

      protected virtual void Dispose(bool disposeManagedResources)
      {
      if (_disposed) return;
      if (!disposeManagedResources) return;
      if (!isSessionSafe) return;

      try
      {
      Commit();
      }
      catch (Exception ex)
      {
      Console.WriteLine(ex.ToString());
      Rollback();
      }
      finally
      {
      if (isSessionSafe)
      {
      _session.Close();
      _session.Dispose();
      }
      }

      _disposed = true;
      }
      }
      }
      اکنون جهت استفاده از این کلاس مخزن به شکل زیر می‌توان عمل کرد:

      using System;
      using System.Collections.Generic;
      using NHSample4.Domain;
      using NHSample4.NHRepository;

      namespace NHSample4
      {
      class Program
      {
      static void Main(string[] args)
      {
      //ایجاد دیتابیس در صورت نیاز
      //NHSessionManager.Config.CreateDb();


      //ابتدا یک دانشجو را اضافه می‌کنیم
      Student student = null;
      using (var studentRepo = new Repository<Student>())
      {
      student = studentRepo.Save(new Student() { Name = "Vahid" });
      }

      //سپس یک واحد را اضافه می‌کنیم
      using (var courseRepo = new Repository<Course>())
      {
      var course = courseRepo.Save(new Course() { Teacher = "Shams" });
      }

      //اکنون یک واحد را به دانشجو انتساب می‌دهیم
      using (var courseRepo = new Repository<Course>())
      {
      courseRepo.Save(new Course() { Students = new List<Student>() { student } });
      }

      //سپس شماره دروس استادی خاص را نمایش می‌دهیم
      using (var courseRepo = new Repository<Course>())
      {
      var query = courseRepo.Find(t => t.Teacher == "Shams");

      foreach (var course in query)
      Console.WriteLine(course.Id);
      }

      Console.WriteLine("Press a key...");
      Console.ReadKey();
      }
      }
      }

      همانطور که ملاحظه می‌کنید در این سطح دیگر برنامه هیچ درکی از ORM مورد استفاده ندارد و پیاده سازی نحوه‌ی تعامل با NHibernate در پس کلاس مخزن مخفی شده است. کار آغاز و پایان تراکنش‌ها به صورت خودکار مدیریت گردیده و همچنین آزاد سازی منابع را نیز توسط اینترفیس IDisposable مدیریت می‌کند. به این صورت امکان فراموش شدن یک سری از اعمال متداول به حداقل رسیده، میزان کدهای تکراری برنامه کم شده و همچنین هر زمانیکه نیاز بود، صرفا با تغییر پیاده سازی کلاس مخزن می‌توان به ORM دیگری کوچ کرد؛ بدون اینکه نیازی به بازنویسی کل برنامه وجود داشته باشد.

      دریافت سورس برنامه قسمت هشتم

      ادامه دارد ...


      اشتراک‌ها
      چرا از مانگو‌دی‌بی به پستگرس مهاجرت کردیم؟
      • پستگرس از داده‌های جی‌سان که مورد نیاز ما بود هم پشتیبانی می‌کند و حتی آنها را موثرتر ذخیره می‌کند و می‌توان برای ذخیره داده‌ها در قالب جی‌سان به راحتی توابع اعتبارسنج (بررسی کننده اعتبار و صحت داده‌ها) نوشت.
      • حجم دیتابیس ما حدود نود درصد کاهش پیدا کرد! 
      چرا از مانگو‌دی‌بی به پستگرس مهاجرت کردیم؟
      نظرات مطالب
      مروری بر کدهای کلاس SqlHelper
      سلام
      از مطالب مفید و پربارتون ممنون .
      در مورد استفاده از EF یه مشکلی که  برای من وجود داره و نتونستم براش یه راه  حل درست و درمون پیدا کنم  نحوه‌ی استفاده از EF با سایر بانک‌های اطلاعاتی (مثلا اراکله) که خود شما هم تو  نقدی که بر کتاب آقای راد نوشته بودید ذکر کردیدش. من هرچی تو نت گشتم تنها راه حلی رو که برای اینکار گفته بودند استفاده از پروایدر های شرکت های ثالث بوده.آیا برای اینکار یه راه حل استاندارد وجود داره ؟
      نظرسنجی‌ها
      چگونه خطاهای برنامه‌ی خود را لاگ می‌کنید؟
      خطاها را لاگ نمی‌کنم. کاربران آن‌ها را گزارش می‌کنند.
      ثبت آن‌ها در syslog یا events خود سیستم عامل
      ثبت آن‌ها در بانک اطلاعاتی
      ثبت آن‌ها در فایل‌ها
      ثبت آن‌ها در سیستم‌های پیام رسانی مانند ایمیل
      ثبت آن‌ها در سرویس‌های ثالث مخصوص اینکار
      ثبت آن‌ها در سرویس‌ ثبت وقایع داخلی خودمان
      مطالب
      آشنایی با CLR: قسمت یازدهم
      انتشار نوع‌ها  (Types) به یک ماژول
      در این قسمت به نحوه‌ی تبدیل سورس به یک فایل قابل انتشار می‌پردازیم. کد زیر را به عنوان مثال در نظر بگیرید:
      public sealed class Program {
        public static void Main() {
          System.Console.WriteLine("Hi");
        }
      }
      این کد یک ارجاع به نام کنسول دارد که این ارجاع، داخل فایلی به نام mscorlib.dll قرار دارد. پس برنامه‌ی ما نوعی را دارد، که آن نوع توسط شرکت دیگری پیاده سازی شده است. برای ساخت برنامه‌ی کد بالا، کدها را داخل فایلی با نام program.cs قرار داده و با دستور زیر در خط فرمان آن را کامپایل می‌کنیم:
      csc.exe /out:Program.exe /t:exe /r:MSCorLib.dll Program.cs
      کد بالا با سوئیچ اول می‌گوید که فایلی را با نام program.exe درست کن و با سوئیچ دوم می‌گوید که این برنامه از نوع کنسول هست.
      موقعیکه کامپایلر فایل سورس را مورد بررسی قرار می‌دهد، متوجه متد writerline می‌گردد؛ ولی از آنجاکه این نوع توسط شما ایجاد نشده است و یک نوع خارجی است، شما باید یک مجموعه از ارجاعات را به کمپایلر داده تا آن نوع را در آن‌ها بیابد. ارائه این ارجاعات به کامپایلر توسط سوئیچ r/ که در خط بالا استفاده شده است، صورت می‌گیرد.


      mscorlib یک فایل سورس است که شامل همه‌ی نوع‌ها، از قبیل int,string,byte و خیلی از نوع‌های دیگر می‌شود. از آنجائیکه استفاده‌ی از این نوع‌ها به طور مکرر توسط برنامه نویس‌ها صورت می‌گیرد، کمپایلر به طور خودکار این کتابخانه را به لیست ارجاعات اضافه می‌کند. به بیان دیگر خط بالا به شکل زیر هم قابل اجراست:
      csc.exe /out:Program.exe /t:exe Program.cs

      به علاوه از آنجایی که بقیه‌ی سوئیچ‌ها هم مقدار پیش فرضی را دارند، خط زیر هم معادل خطوط بالاست:
      csc.exe Program.cs

      اگر به هر دلیلی دوست ندارید که سمت mscorlib ارجاعی صورت بگیرید، می‌توانید از دستور زیر استفاده کنید:
      /nostdlib

      مایکروسافت موقعی از این سوئیچ بالا استفاده کرده است که خواسته است خود mscorlib را بسازد. با اضافه کردن این سوئیچ، کد مثال که حاوی شیء یا نوع کنسول است به خطا برخواهد خورد چون تعریف آن در mscorlib صورت گرفته و شما با سوئیچ بالا دسترسی به آن را ممنوع اعلام کرد‌ه‌اید و از آنجاکه این نوع تعریف نشده، برنامه ازکامپایل بازخواهد ماند.
      csc.exe /out:Program.exe /t:exe /nostdlib Program.cs

      بیایید نگاه دقیقتری به فایل program.exe ساخته شده بیندازیم؛ دقیقا این فایل چه نوع فایلی است؟ برای بسیاری از مبتدیان، این یک فایل اجرایی است که در هر دو ماشین 32 و 64 بیتی قابل اجراست. ویندوز از سه نوع برنامه پشتیبانی می‌کند: CUI یا برنامه‌های تحت کنسول، برنامه‌هایی با رابط گرافیکی GUI و برنامه‌های مخصوص windows store که سوئیچ‌های آن به شرح زیر است:
      //CUI
      /t:exe
      
      //GUI
      /t:winexe
      
      //Winsows store App
      /t:appcontainerexe

      قبل از اینکه بحث را در مورد سوئیچ‌ها به پایان برسانیم، اجازه دهید در مورد فایل‌های پاسخگو یا response file صحبت کنیم. یک فایل پاسخگو، فایلی است که شامل مجموعه‌ای از سوئیچ‌های خط فرمان می‌شود. موقعی‌که csc.exe اجرا می‌شود، به فایل پاسخگویی که شما به آن معرفی کرده‌اید مراجعه کرده و فرمان را با سوئیچ‌های داخل آن اجرا می‌کند. معرفی یک فایل پاسخگو به کامپایلر توسط علامت @ و سپس نام فایل صورت می‌گیرد و در این فایل هر خط، شامل یک سوئیچ است. مثلا فایل پاسخگوی response.rsp شامل سوئیچ‌های زیر است:
      /out:MyProject.exe
      /target:winexe

      و برای در نظر گرفتن این سوئیچ‌ها فایل پاسخگو را به کامپایلر معرفی می‌کنیم:
      csc.exe @MyProject.rsp CodeFile1.cs CodeFile2.cs

      این فایل خیلی کار شما را راحت می‌کند و نمی‌گذارد در هر بار کامپایل، مرتب سوئیچ‌های آن را وارد کنید و کیفیت کار را بالا می‌برد. همچنین می‌توانید چندین فایل پاسخگو داشته باشید و هر کدام شامل سوئیچ‌های مختلفی تا اگر خواستید تنظیمات کامپایل را تغییر دهید، به راحتی تنها نام فایل پاسخگو را تغییر دهید. همچنین کامپایلر سی شارپ از چندین فایل پاسخگو هم پشتیبانی می‌کند و می‌توانید هر تعداد فایل پاسخگویی را به آن معرفی کنید. در صورتیکه فایل را با نام csc.rsp نامگذاری کرده باشید، نیازی به معرفی آن نیست چرا که کامپایلر در صورت وجود، آن را به طور خودکار خواهد خواند و به این فایل global response file یا فایل پاسخگوی عمومی گویند.
      در صورتیکه چندین فایل پاسخگو که به آن فایل‌های محلی local می‌گویند، معرفی کنید که دستورات آن‌ها(سوئیچ) با دستورات داخل csc.rsp مقدار متفاوتی داشته باشند، فایل‌های محلی الویت بالاتری نسبت به فایل global داشته و تنظمیات آن‌ها روی فایل global رونوشت می‌گردند.

      موقعی‌که شما دات نت فریمورک را نصب می‌کنید، فایل csc.rsp را با تنظیمات پیش فرض در مسیر زیر نصب می‌کند:
      %SystemRoot%\
      Microsoft.NET\Framework(64)\vX.X.X

      حروف x نمایانگر نسخه‌ی دات نت فریمورکی هست که شما نصب کرده‌اید. آخرین ورژن از این فایل در زمان نگارش کتاب، شامل سوئیچ‌های زیر بوده است.
      # This file contains command­line options that the C#
      # command line compiler (CSC) will process as part
      # of every compilation, unless the "/noconfig" option
      # is specified.
      # Reference the common Framework libraries
      /r:Accessibility.dll
      /r:Microsoft.CSharp.dll
      /r:System.Configuration.dll
      /r:System.Configuration.Install.dll
      /r:System.Core.dll
      /r:System.Data.dll
      /r:System.Data.DataSetExtensions.dll
      /r:System.Data.Linq.dll
      /r:System.Data.OracleClient.dll
      /r:System.Deployment.dll
      /r:System.Design.dll
      /r:System.DirectoryServices.dll
      /r:System.dll
      /r:System.Drawing.Design.dll
      /r:System.Drawing.dll
      /r:System.EnterpriseServices.dll
      /r:System.Management.dll
      /r:System.Messaging.dll
      /r:System.Runtime.Remoting.dll
      /r:System.Runtime.Serialization.dll
      /r:System.Runtime.Serialization.Formatters.Soap.dll
      /r:System.Security.dll
      /r:System.ServiceModel.dll
      /r:System.ServiceModel.Web.dll
      /r:System.ServiceProcess.dll
      /r:System.Transactions.dll
      /r:System.Web.dll
      /r:System.Web.Extensions.Design.dll
      /r:System.Web.Extensions.dll
      /r:System.Web.Mobile.dll
      /r:System.Web.RegularExpressions.dll
      /r:System.Web.Services.dll
      /r:System.Windows.Forms.Dll
      /r:System.Workflow.Activities.dll
      /r:System.Workflow.ComponentModel.dll
      /r:System.Workflow.Runtime.dll
      /r:System.Xml.dll
      /r:System.Xml.Linq.dll

      این فایل حاوی بسیاری از ارجاعات اسمبلی‌هایی است که بیشتر توسط توسعه دهندگان مورد استفاده قرار می‌گیرد و در صورتیکه برنامه‌ی شما به این اسمبلی‌ها محدود می‌گردد، لازم نیست که این اسمبلی‌ها را به کامپایلر معرفی کنید.
      البته ارجاع کردن به این اسمبلی‌ها تا حد کمی باعث کند شدن صورت کامپایل می‌شوند؛ ولی تاثیری بر فایل نهایی و نحوه‌ی اجرای آن نمی‌گذارند.

      نکته: در صورتی که قصد ارجاعی را دارید، می‌توانید آدرس مستقیم اسمبلی را هم ذکر کنید. ولی اگر تنها به نام اسمبلی اکتفا کنید، مسیرهای زیر جهت یافتن اسمبلی بررسی خواهند شد:
      • دایرکتوری برنامه
      • دایرکتوری که شامل فایل csc.exe می‌شود. که خود فایل mscorlib از همانجا خوانده می‌شود و مسیر آن شبیه مسیر زیر است:
      %SystemRoot%\Microsoft.NET\Framework\v4.0.#####

      • هر دایرکتوری که توسط سوئیچ lib/ مشخص شده باشد.
      • هر دایرکتوری که توسط متغیر محلی lib مشخص شده باشد.
      استفاده از سوئیچ noconfig/ هم باعث می‌شود که فایل‌های پاسخگوی از هر نوعی، چه عمومی و چه محلی، مورد استفاده قرار نگیرند. همچنین شما مجاز هستید که فایل csc.rsp را هم تغییر دهید؛ ولی این نکته را فراموش نکنید، در صورتی که برنامه‌ی شما به سیستمی دیگر منتقل شود، تنظیمات این فایل در آنجا متفاوت خواهد بود و بهتر هست یک فایل محلی را که همراه خودش هست استفاده کنید.
      در قسمت بعدی نگاه دیگری بر متادیتا خواهیم داشت.