مطالب
EF Code First #7

مدیریت روابط بین جداول در EF Code first به کمک Fluent API

EF Code first بجای اتلاف وقت شما با نوشتن فایل‌های XML تهیه نگاشت‌ها یا تنظیم آن‌ها با کد، رویه Convention over configuration را پیشنهاد می‌دهد. همین رویه، جهت مدیریت روابط بین جداول نیز برقرار است. روابط one-to-one، one-to-many، many-to-many و موارد دیگر را بدون یک سطر تنظیم اضافی، صرفا بر اساس یک سری قراردادهای توکار می‌تواند تشخیص داده و اعمال کند. عموما زمانی نیاز به تنظیمات دستی وجود خواهد داشت که قراردادهای توکار رعایت نشوند و یا برای مثال قرار است با یک بانک اطلاعاتی قدیمی از پیش موجود کار کنیم.


مفاهیمی به نام‌های Principal و Dependent

در EF Code first از یک سری واژه‌های خاص جهت بیان ابتدا و انتهای روابط استفاده شده است که عدم آشنایی با آن‌ها درک خطاهای حاصل را مشکل می‌کند:
الف) Principal : طرفی از رابطه است که ابتدا در بانک اطلاعاتی ذخیره خواهد شد.
ب) Dependent : طرفی از رابطه است که پس از ثبت Principal در بانک اطلاعاتی ذخیره می‌شود.
Principal می‌تواند بدون نیاز به Dependent وجود داشته باشد. وجود Dependent بدون Principal ممکن نیست زیرا ارتباط بین این دو توسط یک کلید خارجی تعریف می‌شود.


کدهای مثال مدیریت روابط بین جداول

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

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Customer
{
public int Id { set; get; }
public string FirstName { set; get; }
public string LastName { set; get; }

public virtual AlimentaryHabits AlimentaryHabits { set; get; }
public virtual ICollection<CustomerAlias> Aliases { get; set; }
public virtual ICollection<Role> Roles { get; set; }
public virtual Address Address { get; set; }
}
}

namespace EF_Sample35.Models
{
public class CustomerAlias
{
public int Id { get; set; }
public string Aka { get; set; }

public virtual Customer Customer { get; set; }
}
}

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Role
{
public int Id { set; get; }
public string Name { set; get; }

public virtual ICollection<Customer> Customers { set; get; }
}
}

namespace EF_Sample35.Models
{
public class AlimentaryHabits
{
public int Id { get; set; }
public bool LikesPasta { get; set; }
public bool LikesPizza { get; set; }
public int AverageDailyCalories { get; set; }

public virtual Customer Customer { get; set; }
}
}

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Address
{
public int Id { set; get; }
public string City { set; get; }
public string StreetAddress { set; get; }
public string PostalCode { set; get; }

public virtual ICollection<Customer> Customers { set; get; }
}
}



همچنین تعاریف نگاشت‌های برنامه نیز مطابق کد‌های زیر است:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerAliasConfig : EntityTypeConfiguration<CustomerAlias>
{
public CustomerAliasConfig()
{
// one-to-many
this.HasRequired(x => x.Customer)
.WithMany(x => x.Aliases)
.WillCascadeOnDelete();
}
}
}

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
public CustomerConfig()
{
// one-to-one
this.HasOptional(x => x.AlimentaryHabits)
.WithRequired(x => x.Customer)
.WillCascadeOnDelete();

// many-to-many
this.HasMany(p => p.Roles)
.WithMany(t => t.Customers)
.Map(mc =>
{
mc.ToTable("RolesJoinCustomers");
mc.MapLeftKey("RoleId");
mc.MapRightKey("CustomerId");
});

// many-to-one
this.HasOptional(x => x.Address)
.WithMany(x => x.Customers)
.WillCascadeOnDelete();
}
}
}


به همراه Context زیر:

using System.Data.Entity;
using System.Data.Entity.Migrations;
using EF_Sample35.Mappings;
using EF_Sample35.Models;

namespace EF_Sample35.DataLayer
{
public class Sample35Context : DbContext
{
public DbSet<AlimentaryHabits> AlimentaryHabits { set; get; }
public DbSet<Customer> Customers { set; get; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new CustomerConfig());
modelBuilder.Configurations.Add(new CustomerAliasConfig());

base.OnModelCreating(modelBuilder);
}
}

public class Configuration : DbMigrationsConfiguration<Sample35Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}

protected override void Seed(Sample35Context context)
{
base.Seed(context);
}
}
}


که نهایتا منجر به تولید چنین ساختاری در بانک اطلاعاتی می‌گردد:



توضیحات کامل کدهای فوق:

تنظیمات روابط one-to-one و یا one-to-zero

زمانیکه رابطه‌ای 0..1 و یا 1..1 است، مطابق قراردادهای توکار EF Code first تنها کافی است یک navigation property را که بیانگر ارجاعی است به شیء دیگر، تعریف کنیم (در هر دو طرف رابطه).
برای مثال در مدل‌های فوق یک مشتری که در حین ثبت اطلاعات اصلی او، «ممکن است» اطلاعات جانبی دیگری (AlimentaryHabits) نیز از او تنها در طی یک رکورد، دریافت شود. قصد هم نداریم یک ComplexType را تعریف کنیم. نیاز است جدول AlimentaryHabits جداگانه وجود داشته باشد.

namespace EF_Sample35.Models
{
public class Customer
{
// ...
public virtual AlimentaryHabits AlimentaryHabits { set; get; }
}
}

namespace EF_Sample35.Models
{
public class AlimentaryHabits
{
// ...
public virtual Customer Customer { get; set; }
}
}

در اینجا خواص virtual تعریف شده در دو طرف رابطه، به EF خواهد گفت که رابطه‌ای، 1:1 برقرار است. در این حالت اگر برنامه را اجرا کنیم، به خطای زیر برخواهیم خورد:

Unable to determine the principal end of an association between 
the types 'EF_Sample35.Models.Customer' and 'EF_Sample35.Models.AlimentaryHabits'.
The principal end of this association must be explicitly configured using either
the relationship fluent API or data annotations.

EF تشخیص داده است که رابطه 1:1 برقرار است؛ اما با قاطعیت نمی‌تواند طرف Principal را تعیین کند. بنابراین باید اندکی به او کمک کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
public CustomerConfig()
{
// one-to-one
this.HasOptional(x => x.AlimentaryHabits)
.WithRequired(x => x.Customer)
.WillCascadeOnDelete();
}
}
}


همانطور که ملاحظه می‌کنید در اینجا توسط متد WithRequired طرف Principal و توسط متد HasOptional، طرف Dependent تعیین شده است. به این ترتیب EF می‌توان یک رابطه 1:1 را تشکیل دهید.
توسط متد WillCascadeOnDelete هم مشخص می‌کنیم که اگر Principal حذف شد، لطفا Dependent را به صورت خودکار حذف کن.

توضیحات ساختار جداول تشکیل شده:
هر دو جدول با همان خواص اصلی که در دو کلاس وجود دارند، تشکیل شده‌اند.
فیلد Id جدول AlimentaryHabits اینبار دیگر Identity نیست. اگر به تعریف قید FK_AlimentaryHabits_Customers_Id دقت کنیم، در اینجا مشخص است که فیلد Id جدول AlimentaryHabits، به فیلد Id جدول مشتری‌ها متصل شده است (یعنی در آن واحد هم primary key است و هم foreign key). به همین جهت به این روش one-to-one association with shared primary key هم گفته می‌شود (کلید اصلی جدول مشتری با جدول AlimentaryHabits به اشتراک گذاشته شده است).


تنظیمات روابط one-to-many

برای مثال همان مشتری فوق را درنظر بگیرید که دارای تعدادی نام مستعار است:

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Customer
{
// ...
public virtual ICollection<CustomerAlias> Aliases { get; set; }
}
}

namespace EF_Sample35.Models
{
public class CustomerAlias
{
// ...
public virtual Customer Customer { get; set; }
}
}

همین میزان تنظیم کفایت می‌کند و نیازی به استفاده از Fluent API برای معرفی روابط نیست.
در طرف Principal، یک مجموعه یا لیستی از Dependent وجود دارد. در Dependent هم یک navigation property معرف طرف Principal اضافه شده است.
جدول CustomerAlias اضافه شده، توسط یک کلید خارجی به جدول مشتری مرتبط می‌شود.

سؤال: اگر در اینجا نیز بخواهیم CascadeOnDelete را اعمال کنیم، چه باید کرد؟
پاسخ: جهت سفارشی سازی نحوه تعاریف روابط حتما نیاز به استفاده از Fluent API به نحو زیر می‌باشد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerAliasConfig : EntityTypeConfiguration<CustomerAlias>
{
public CustomerAliasConfig()
{
// one-to-many
this.HasRequired(x => x.Customer)
.WithMany(x => x.Aliases)
.WillCascadeOnDelete();
}
}
}

اینکار را باید در کلاس تنظیمات CustomerAlias انجام داد تا بتوان Principal را توسط متد HasRequired به Customer و سپس dependent را به کمک متد WithMany مشخص کرد. در ادامه می‌توان متد WillCascadeOnDelete یا هر تنظیم سفارشی دیگری را نیز اعمال نمود.
متد HasRequired سبب خواهد شد فیلد Customer_Id، به صورت not null در سمت بانک اطلاعاتی تعریف شود؛ متد HasOptional عکس آن است.


تنظیمات روابط many-to-many

برای تنظیم روابط many-to-many تنها کافی است دو سر رابطه ارجاعاتی را به یکدیگر توسط یک لیست یا مجموعه داشته باشند:

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Role
{
// ...
public virtual ICollection<Customer> Customers { set; get; }
}
}

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Customer
{
// ...
public virtual ICollection<Role> Roles { get; set; }
}
}

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

CREATE TABLE [dbo].[RolesJoinCustomers](
[RoleId] [int] NOT NULL,
[CustomerId] [int] NOT NULL,
)

سؤال: نام‌های خودکار استفاده شده را می‌خواهیم تغییر دهیم. چکار باید کرد؟
پاسخ: اگر بانک اطلاعاتی برای بار اول است که توسط این روش تولید می‌شود شاید این پیش فرض‌ها اهمیتی نداشته باشد و نسبتا هم مناسب هستند. اما اگر قرار باشد از یک بانک اطلاعاتی موجود که امکان تغییر نام فیلدها و جداول آن وجود ندارد استفاده کنیم، نیاز به سفارشی سازی تعاریف نگاشت‌ها به کمک Fluent API خواهیم داشت:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
public CustomerConfig()
{
// many-to-many
this.HasMany(p => p.Roles)
.WithMany(t => t.Customers)
.Map(mc =>
{
mc.ToTable("RolesJoinCustomers");
mc.MapLeftKey("RoleId");
mc.MapRightKey("CustomerId");
});
}
}
}


تنظیمات روابط many-to-one

در تکمیل مدل‌های مثال جاری، به دو کلاس زیر خواهیم رسید. در اینجا تنها در کلاس مشتری است که ارجاعی به کلاس آدرس او وجود دارد. در کلاس آدرس، یک navigation property همانند حالت 1:1 تعریف نشده است:

namespace EF_Sample35.Models
{
public class Address
{
public int Id { set; get; }
public string City { set; get; }
public string StreetAddress { set; get; }
public string PostalCode { set; get; }
}
}

using System.Collections.Generic;

namespace EF_Sample35.Models
{
public class Customer
{
// …
public virtual Address Address { get; set; }
}
}

این رابطه توسط EF Code first به صورت خودکار به یک رابطه many-to-one تفسیر خواهد شد و نیازی به تنظیمات خاصی ندارد.
زمانیکه جداول برنامه تشکیل شوند، جدول Addresses موجودیتی مستقل خواهد داشت و جدول مشتری با یک فیلد به نام Address_Id به جدول آدرس‌ها متصل می‌گردد. این فیلد نال پذیر است؛ به عبارتی ذکر آدرس مشتری الزامی نیست.
اگر نیاز بود این تعاریف نیز توسط Fluent API سفارشی شوند، باید خاصیت public virtual ICollection<Customer> Customers به کلاس Address نیز اضافه شود تا بتوان رابطه زیر را توسط کدهای برنامه تعریف کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample35.Models;

namespace EF_Sample35.Mappings
{
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
public CustomerConfig()
{
// many-to-one
this.HasOptional(x => x.Address)
.WithMany(x => x.Customers)
.WillCascadeOnDelete();
}
}
}

متد HasOptional سبب می‌شود تا فیلد Address_Id اضافه شده به جدول مشتری‌ها، null پذیر شود.

اشتراک‌ها
سری Refactoring

From Legacy Code to Testable Code #8: Convert If-Else’s to Guard Blocks

سری Refactoring
مطالب
افزونه جی کوئری RowAdder
دیروز در یک برنامه میخواستم کاربر بتواند لیست مواد مصرفی یک کارخانه را ایجاد کند که نیاز بود کاربر بتواند از هر سطر به تعداد نامحدود ایجاد کند و برای انتخاب هر یک از مواد به همراه جزئیات آن یک سطر به لیست اضافه شود. برای اینکار میتوانیم با استفاده از فناوری جی کوئری اینکار را انجام دهیم ولی بهتر بود که این مورد به یک افزونه تبدیل میشد تا در دفعات بعدی بسیار راحت‌تر باشیم. جهت آشنایی با پلاگین نویسی بهتر هست این مقالات (+) را مطالعه فرمایید.

نحوه استفاده
نحوه استفاده از آن بسیار راحت است و در دموی html همراه آن به طور ساده در سه مثال توضیح داده شده است. ابتدا از این آدرس کتابخانه آن را دریافت کنید. این کتابخانه شامل یک فایل js که شامل کدهای پلاگین است، یک فایل css جهت تغییر استایل کدهایی است که پلاگین تولید میکند که اسامی آن دقیقا مشخص می‌کند که هر کلاس متعلق به چه بخشی است.

گام اول:
فایل‌های مورد نظر را بعد از صدا زدن کتابخانه‌ی جی کوئری صدا بزنید.
<link type="text/css" href="css/RowAdder.css" rel="stylesheet" />
    <script src="js/RowAdder.js" type="text/javascript"></script>


گام دوم :
 در تکه کدهای html، کدی را که قرار است در هر سطر تکرار شود، داخل یک div قرار داده و نامی مثل row-sample را برای آن قرار دهید (فعلا حتما این نام باشد)، بعدها پلاگین، کدهای داخل این تگ div را به عنوان هر سطر خواهد شناخت:
<div id="row-sample">
    <form style="margin: 0; padding: 0;">
        Name:<input type="text"/>
        <input type="radio" name="Gender" value="male" checked="checked">Male
        <input type="radio" name="Gender" value="female">Female
    </form>
</div>


گام سوم:
 سپس یک div دیگر ایجاد کنید و نامی مثل mypanel را به آن بدهید تا سطرهایی که ایجاد می‌شوند داخل این div قرار بگیرند.
<div id="mypanel"></div>

گام چهارم:
در بخش head یک تگ اسکریپت باز کرده و کدهای زیر را به آن اضافه می‌کنیم. این کد باعث می‌شود که پلاگین فعال شود.
<script>
$(document).ready(function() {
$("#mypanel").RowAdder();
});
</script>
گام پنجم:
 یک دکمه جهت افزودن سطر به صفحه اضافه می‌کنیم
<button id="addanotherform">Add New Form</button>

و در قسمت تگ اسکریپت هم کد زیر را اضافه می‌کنیم:
$("#addanotherform").on('click', function() {
                $("#mypanel").RowAdder('add');
            });

حال از صفحه تست می‌گیریم: با هر بار کلیک بر روی دکمه‌ی Add New Form یک سطر جدید ایجاد می‌گردد.


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

جهت مخفی سازی:
 $("#mypanel").RowAdder('hide');

چهت نمایش:
$("#mypanel").RowAdder('show');

جهت افزودن سطر با کد:
$("#mypanel").RowAdder('add');

جهت دریافت تعداد سطرهای ایجاد شده:
$("#mypanel").RowAdder('count')


جهت دریافت کدهای یک سطر در اندیس x

$("#mypanel").RowAdder('content', 3)

جهت حذف یک سطر با اندیس x
$("#mypanel").RowAdder('remove', 3);

همانطور که با صدا زدن اولین متد پلاگین متوجه شدید و نتیجه‌ی آن را در دمو دیدید، این پلاگین از پیش فرض‌هایی جهت راه اندازی اولیه استفاده می‌کند که این پیش فرض‌ها عبارتند از تگ row-sample که بدون معرفی رسمی، آن را شناسایی کرد. همچنین ممکن است بخواهید عبارت Remove را با کلمه‌ی فارسی «حذف» جایگزین نمایید. برای اینکار می‌توانید پلاگین را به شکل زیر به کار ببرید:
    $("#mypanel").RowAdder({
                sample: '#my-custom-sample',
                type: 'text',
                value:'حذف'
        });

تغییر اولین پیش فرض، تغییر نام تگ row-sample به my-custom-sample بود و در مرحله‌ی بعد هم نام فارسی حذف را جایگزین remove کردیم. عبارت type به طور پیش فرض بر روی text قرار دارد که اجباری به ذکر آن در کد بالا نبود. ولی اگر دوست دارید که به جای نمایش عبارت حذف، از یک آیکن یا تصویر استفاده کنید، کد را به شکل زیر تغییر دهید:
  $("#mypanel").RowAdder({
                type: 'image',
                value: 'images/remove.png'
            });
در خطوط بالا عبارت type با image مقدار دهی شد و به پلاگین می‌گوید که به جای متن، از تصویر استفاده کن. همچنین value را به جای متن با آدرس تصویر مقداردهی کرده‌ایم و نتیجه را می‌توانید در دموی قرار گرفته در گیت هاب ببینید.

فایل RowAdder.css
در بردارنده هر سطر
.each-section {
    margin: 20px;
    padding: 5px;
}

جهت استایل بندی لینک چه تصویر و چه متن
.remove-link {
    color:#999;
     text-decoration: none;
}

a:hover.remove-link {
   color:#802727;
}
جهت تغییر استایل بر روی خود تصویر
.remove-image {
    
}

آشنایی با کد پلاگین
(function ($) {
    
    var settings = null;
  $.fn.RowAdder = function (method) {
    
            // call methods
            if (methods[method]) {
                return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
            } else if (typeof method === 'object' || !method) {
                return methods.init.apply(this, arguments);
            } else {
                $.error('Method ' + method + ' does not exist on jQuery.RowAdder');
            }
      

    };
})(jQuery);
در قسمت دوم آموزش پلاگین نویسی برای جی کوئری، متدها به طور واضح توضیح داده شده‌اند. این کدها وظیفه دارند متدهایی را که کاربر درخواست داده است، شناسایی و به همراه آرگومان‌های آن به سمت توابعی که به هر نام متد اختصاص داده‌ایم، ارسال کنند. در صورتیکه متدی با آرگومان‌های ناهماهنگی ارسال شوند، پیام خطایی ارسال می‌گردد و در صورتیکه تعریف نشود، به طور مستقیم init را صدا می‌زند. متغیر settings هم بعدا با تنظیمات پیش فرض پر می‌شود.

متدها
//methods
    var methods = {
        init: function (options) {
            //default-settings
             settings = $.extend({
                'sample': '#row-sample',
                'type': 'text',
                'value': 'Remove'
             }, options);
             this.attr('data-sample', settings.sample);
             this.attr('data-type', settings.type);
             this.attr('data-value', settings.value);
            Do(this);
        },
        show: function () {
            this.css("display", "inline");
        },
        hide: function () {
            this.css("display", "none");
        },
        add: function () {
            Do(this);
        },
        remove: function (index) {
            console.log(index);
           this.find(".each-section")[index].remove();
        },
        content: function (index) {
            return this.find(".each-section")[index];
        },
        count: function (index) {
            return this.find(".each-section").size();
        }
    };
متد init تنظیمات پیش فرض را دریافت می‌نماید و سپس بر روی المانی که پلاگین روی آن واقع شده‌است، مقادیر را ذخیره می‌کند تا در آینده با صدا زدن متدهای دیگر آن را استفاده نماید. کلمه‌ی this در واقع به تگی اشاره می‌کند که پلاگین روی آن اعمال شده است که در مثال‌های بالا mypanel نام داشت. متد Do تابع اصلی ما را در بر دارد که کدهای اصلی پلاگین را شامل می‌شود. مابقی متدها در واقع  جست و جویی بر المان‌ها هستند.

تابع Do
    function Do(panelDiv) {

        settings.sample = panelDiv.data('sample');
        settings.type = panelDiv.data('type');
        settings.value = panelDiv.data('value');
        //find sample code
        var rowsample = $(settings.sample);
        rowsample.css("display", "none");
        var sample = rowsample.html();


        var i = panelDiv.find(".each-section").size();
        //add html details to create a correct template
        var sectionDiv = $('<div />', { "class": 'each-section', 'id': 'section'+i });
        var image = $("<img />", { "src": settings.value,"class":"remove-image" });
        var link = $("<a />", { "text": settings.value,"class":"remove-link" });
        //remove event for remove selected form

        //create new form
        sectionDiv.html(sample);

        link.on('click', function (e) {

            e.preventDefault();
            var $this = $(this);
            $this.closest(".each-section").remove();
        });

        if (i > 0) {
            if (settings.type == 'image') {
                link.text('');
                link.append(image);

            }
            sectionDiv.append(link);
        }

        //add new created form on document
        panelDiv.append(sectionDiv);
       
    }
آرگومان داده شده، در واقع همان this هست که به این تابع ارسال شده است. در اولین گام تنظیمات ذخیره شده را که قبلا ذخیره کرده‌ایم، واکشی می‌کنیم. سپس تگ row-sample یا هر نامی را که به آن اختصاص داده شده است، می‌یابیم و محتوای آن را به شکل html در قالب string بیرون می‌کشیم. این کد html در واقع نمونه‌ای است که قرار است در سطر تکرار شود. البته تگ نمونه فقط برای نمونه به کار می‌رود و نیازی نیست روی صفحه نمایش داده شود؛ پس آن را مخفی می‌کنیم. از آنجا که ممکن است این سطری که ایجاد می‌شود، سطر اول نباشد و قبلا هم سطرهایی توسط همین متد ایجاد شده‌اند، بررسی می‌کنیم چند تگ با کلاس each-section داریم. اگر بیشتر از صفر باشد یعنی قبلا سطرهایی ایجاد شده است. در غیر اینصورت این اولین سطر ماست. اولین سطر توسط init صدا زده می‌شود و مابقی توسط متد add انجام می‌گیرد.
        settings.sample = panelDiv.data('sample');
        settings.type = panelDiv.data('type');
        settings.value = panelDiv.data('value');
        //find sample code
        var rowsample = $(settings.sample);
        rowsample.css("display", "none");
        var sample = rowsample.html();


        var i = panelDiv.find(".each-section").size();
در خطوط بعدی یک سری متغیر داریم که برای هر کدام یک قالب تگ div با کلاس‌های مختلف می‌سازیم. sectionDiv یک تگ  div  با کلاس each-section است که هر سطر را به طور کامل در خود قرار می‌دهد. link، جهت ساخت لینک حذف با کلاس remove-link به کار می‌رود. image هم یک تگ image می‌سازد تا اگر کاربر درخواست 'type:'image را داد، به جای لینک متنی حذف، از تصویر استفاده شود.
        //add html details to create a correct template
        var sectionDiv = $('<div />', { "class": 'each-section', 'id': 'section'+i });
        var image = $("<img />", { "src": settings.value,"class":"remove-image" });
        var link = $("<a />", { "text": settings.value,"class":"remove-link" });

در خط بعدی محتویات نمونه را داخل تگ sectiondiv قرار می‌دهیم:
//create new form
        sectionDiv.html(sample);

بعد از آن برای رویداد کلیک لینک حذف، کد زیر را وارد می‌کنیم:
   link.on('click', function (e) {

            e.preventDefault();
            var $this = $(this);
            $this.closest(".each-section").remove();
        });
متد closest در جی کوئری این وظیفه را دارد تا به سمت تگ‌های والد تگ this حرکت کند و با برخوردن با اولین تگ والد با کلاس each-section، آن تگ والد را بازگرداند و سپس متد remove را روی آن اجرا کند تا آن تگ به همراه تمام فرزندانش حذف شوند.

اولین شرط زیر بررسی می‌کند که آیا این سطری که ایجاد شده است سطر دوم به بعد است یا خیر؟ اگر آری پس باید دکمه‌ی حذف را به همراه داشته باشد. در صورتیکه سطر دوم به بعد باشد، وارد آن می‌شود. حالا بررسی می‌کند که کاربر برای دکمه‌ی حذف، درخواست لینک تصویری یا لینک متنی داده است و لینک مناسب را ساخته و آن را به انتهای sectionDiv اضافه می‌کند.
   if (i > 0) {
            if (settings.type == 'image') {
                link.text('');
                link.append(image);

            }
            sectionDiv.append(link);
        }

در انتها کل تگ sectionDiv را به تگ داده شده اضافه می‌کنیم تا به کاربر نمایش داده شود.
//add new created form on document
        panelDiv.append(sectionDiv);
مطالب
ایجاد Helper سفارشی جهت نمایش ویدئو در ASP.NET MVC
معرفی HTML Helpers
در صورتی که در مورد Helper‌ها در ASP.NET MVC اطلاعات بیشتری نیاز دارید پیشنهاد می‌کنم ابتدا این مطلب + را مطالعه کنید.
 
ایجاد یک HTML Helper سفارشی برای نمایش ویدئو‌های سایت آپارات
قبل از ایجاد هر Helper ی باید با خروجی نهایی آن آشنا بود. پس از بررسی خروجی نهایی کافیست بخش هایی از آن را Optional کنیم تا در زمان استفاده از آن، نسبت به مقادیر این دسته از پارامترها تصمیم گیری کنیم.
سایت آپارات نمونه‌ی فارسی سایت YouTube است و امکان اشتراک گذاری فایل‌های ویدئویی را برای کاربران مهیا کرده است. در این سایت در صفحه نمایش هر ویدئو، بخشی با عنوان "دریافت کد ویدئو" وجود دارد. با کمی بررسی در کدهای نهایی ایجاد شده توسط دستورات ذکر شده در این بخش، کد نهایی برای نمایش یک ویدئو به صورت زیر خواهد بود:

<embed height="400" width="500" 
flashvars="config=http://www.aparat.com//video/video/config/videohash/BA9Md/watchtype/embed" 
allowfullscreen="true" 
quality="high" name="aparattv_BA9Md" id="aparattv_BA9Md" 
src="http://host10.aparat.com/public/player/aparattv" type="application/x-shockwave-flash">
برای تولید پویای این کد Helper زیر می‌تواند مفید باشد:
using System.Web.Mvc;

namespace MvcApplication1
{
    public static class AparatPlayerHelper
    {
        public static MvcHtmlString AparatPlayer(this HtmlHelper helper, string mediafile, int height, int width)
        {
            var player = @"<embed height=""{0}"" width=""{1}"" flashvars=""config=http://www.aparat.com//video/video/config/videohash/{2}/watchtype/embed"" 
                                allowfullscreen=""true"" 
                                quality=""high"" 
                                name=""aparattv_{2}"" id=""aparattv_{2}"""" src=""http://host10.aparat.com/public/player/aparattv"" 
                                type=""application/x-shockwave-flash"">";

            player = string.Format(player, height, width, mediafile);
            return new MvcHtmlString(player);
        }
    }
}
نحوه استفاده از این Helper:
@Html.AparatPlayer("BA9Md", 400, 500)
اگر به آدرس صفحات در سایت آپارات دقت کنید URL جاری به یک عبارت چند حرقی ختم می‌شود که از این عبارت به عنوان مقدار پارامتر mediafile و شناسه منحصر بفرد فایل ویدئو استفاده شده است.
ایجاد Helper سفارشی جهت نمایش ویدئو‌های YouTube:
با توجه به توضیحات فوق یک Helper سفارشی برای نمایش ویدئو‌های YouTube بصورت زیر است:
using System;
using System.Drawing;
using System.Web.Mvc;

namespace MvcApplication1
{

    public static class YouTubePlayerHelper
    {
        public static MvcHtmlString YouTubePlayer(this HtmlHelper helper, string playerId, string mediaFile, YouTubePlayerOption youtubePlayerOption)
        {

            const string baseURL = "http://www.youtube.com/v/";

            // YouTube Embedded Code
            var player = @"<div id=""YouTubePlayer_{7}""width:{1}px; height:{2}px;"">
                                 <object width=""{1}"" height=""{2}"">
                                 <param name=""movie"" value=""{6}{0}&fs=1&border={3}&color1={4}&color2={5}""></param>
                                 <param name=""allowFullScreen"" value=""true""></param>
                                 <embed src=""{6}{0}&fs=1&border={3}&color1={4}&color2={5}""
                                 type = ""application/x-shockwave-flash""
                                 width=""{1}"" height=""{2}"" allowfullscreen=""true""></embed>
                                 </object>
                             </div>";

            // Replace All The Value
            player = String.Format(player, mediaFile, youtubePlayerOption.Width, youtubePlayerOption.Height, (youtubePlayerOption.Border ? "1" : "0"), ConvertColorToHexa.ConvertColorToHexaString(youtubePlayerOption.PrimaryColor), ConvertColorToHexa.ConvertColorToHexaString(youtubePlayerOption.SecondaryColor), baseURL, playerId);

            //Retrun Embedded Code
            return new MvcHtmlString(player);
        }
    }

    public class YouTubePlayerOption
    {
        int _width = 425;
        int _height = 355;
        Color _color1 = Color.Black;
        Color _color2 = Color.Aqua;

        public YouTubePlayerOption()
        {
            Border = false;
        }

        public int Width { get { return _width; } set { _width = value; } }
        public int Height { get { return _height; } set { _height = value; } }
        public Color PrimaryColor { get { return _color1; } set { _color1 = value; } }
        public Color SecondaryColor { get { return _color2; } set { _color2 = value; } }
        public bool Border { get; set; }
    }

    public class ConvertColorToHexa
    {
        private static readonly char[] HexDigits =
            {
                '0', '1', '2', '3', '4', '5', '6', '7',
                '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
            };

        public static string ConvertColorToHexaString(Color color)
        {
            var bytes = new byte[3];
            bytes[0] = color.R;
            bytes[1] = color.G;
            bytes[2] = color.B;
            var chars = new char[bytes.Length * 2];
            for (int i = 0; i < bytes.Length; i++)
            {
                int b = bytes[i];
                chars[i * 2] = HexDigits[b >> 4];
                chars[i * 2 + 1] = HexDigits[b & 0xF];
            }
            return new string(chars);
        }


    }        

}
نحوه استفاده:
@Html.YouTubePlayer("Casablanca", "iLdqKUkkM6w", new YouTubePlayerOption()
                                {
                                    Border = true
                                })
در اینجا جهت مقداردهی پارمتر‌های پخش کننده ویدئو، از کلاس YouTubePlayerOption استفاده شده است.
مطالب
آموزش LINQ بخش دوم
سبک‌های مختلف نوشتن Query در LINQ
تعریف Query:
عبارتی که اطلاعات را از منبع داده، بازیابی می‌کند، پرس و جو یا Query می‌گوییم. بطور کلی عملیات پرس و جو شامل سه بخش زیر می‌شود:
1- مشخص کردن منبع داده
2- ایجاد پرس و جو (Query)
3- اجرای پرس و جو
// The Three Parts of a LINQ Query:
//  1. منبع داده
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
// 2. ایجاد پرس و جو
// numQuery is an IEnumerable<int>
var numQuery =
    from num in numbers
    where (num % 2) == 0
    select num;
// 3. اجرای پرس و جو
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}
شکل زیر توصیفی از کد‌های بالا می‌باشد :
 
دو سبک برای نوشتن عبارت‌های جستجو در LINQ وجود دارند :
 1- Fluent Style 
 2- Query Expression Style یا Query Syntax
سبک Fluent از متد‌های الحاقی برای عملیات پرس و جو استفاده می‌کند. در کلیه‌ی کدهای بخش اول این سری آموزشی از سبک Fluent استفاده شده است.
در کلاس‌های زیر متد‌های استاتیک مختلفی برای عملیات بر روی توالی‌ها ارائه شده‌اند:
 • System.Linq.Enumerable
 • System.Linq.Queryable
 • System.Linq.ParallelEnumarable
بطور کلی هر نمونه‌ای که اینترفیس <IEnumerable<Tsource را پیاده سازی کرده باشد می‌تواند از این متدهای الحاقی استفاده کند.
عملگرهای جستجو به دو صورت تکی و زنجیره‌ای برای ایجاد پرس و جو‌های پیچیده مورد استفاده قرار می‌گیرند.

پرس و جوی‌های زنجیره‌ای
در ابتدا کلاسی به نام Ingredient را به شکل زیر تعریف می‌کنیم (این کلاس نشان دهنده‌ی نام مواد غذایی و کالری آنهاست):
class Ingredient
{
    public string Name { get; set; }
    public int Calories { get; set; }
}
لیستی از مواد غذایی را ایجاد می‌کنیم:
Ingredient[] ingredients =
{
   new Ingredient {Name = "Suger", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100 },
   new Ingredient {Name = "Milk", Calories = 150 },
   new Ingredient {Name = "Flour", Calories = 50 },
   new Ingredient {Name = "Butter", Calories = 200 }
};
حال می‌خواهیم بصورت زنجیره‌ای از عملگر‌های پرس و جوی Where,OrderBy,Select استفاده کنیم:
Ingredient[] ingredients =
{
   new Ingredient {Name = "Suger", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100 },
   new Ingredient {Name = "Milk", Calories = 150 },
   new Ingredient {Name = "Flour", Calories = 50 },
   new Ingredient {Name = "Butter", Calories = 200 }
};

IEnumerable<string> highCalories =
ingredients.Where(x => x.Calories >= 150)
  .OrderBy(x => x.Name)
  .Select(x => x.Name);

foreach (var item in highCalories)
{
   Console.WriteLine(item);
}
خروجی کد بالا به شکل زیر است :
Butter
Milk
Suger
نمودار زیر نحوه‌ی عملکرد عملگرهای پرس و جو را نشان می‌دهد. هر عملگر بر روی توالی خروجی عملگر قبلی کار می‌کند. توجه کنید که توالی ورودی از نوع <IEnumerable<Ingredient می‌باشد و توالی خروجی تولید شده از نوع <IEnumerable<string است.
در این مثال عملگر‌های پرس و جو بر روی توالی ورودی عمل می‌کنند تا به دستور Select برسند. دستور Select هر عنصر را به یک رشته تبدیل می‌کند. این عملیات را Projection می‌گویند.











عبارت Lambda نوشته شده‌ی در بخش Select مشخص می‌کند که خروجی بر اساس چه خصوصیتی از توالی ورودی باشد. در اینجا نام عناصر به صورت رشته در خروجی ظاهر می‌شوند.


سبک Query Expression (عبارت‌های پرس و جو) 

Query Expression یک گرامر زیبا و روان برای نوشتن پرس و جو‌ها را ارائه می‌دهد. در مثال زیر از سبک Query Expression استفاده کرده‌ایم:

Ingredient[] ingredients =
{
    new Ingredient {Name = "Suger", Calories = 500},
    new Ingredient {Name = "Egg", Calories = 100},
    new Ingredient {Name = "Milk", Calories = 150},
    new Ingredient {Name = "Flour", Calories = 50},
    new Ingredient {Name = "Butter", Calories = 200}
};

IEnumerable<string> highCalories =
    from i in ingredients
    where i.Calories >= 150
    orderby i.Name
    select i.Name;

foreach (var item in highCalories)
{
    Console.WriteLine(item);
}

خروجی کد بالا با خروجی کد به سبک Fluent  یکسان است:

Butter
Milk
Suger

همانطور که می‌بینید ترتیب عملیات همانند روش قبل است. عبارت‌های پرس و جوی (from,where,orderby,select) به ترتیب با اصلاح توالی ورودی و تحویل آن به عبارت جستجوی بعدی کار را انجام می‌دهند.

عبارت جستجوی بالا با کلمه‌ی کلیدی from آغاز شده است. هدف from دو چیز است:

1- مشخص کردن توالی ورودی (منبع داده)

2- معرفی متغیر Range  (مشخص کردن عنصر مورد نظر در منبع داده)

متغیر Range همچون متغیر شمارنده در حلقه هاست. 


در ادامه این سری آموزشی درباره متغیر Range بصورت کاملتری بحث خواهیم کرد.   

مطالب
ایجاد ایندکس منحصربفرد در EF Code first به صورت Fluent API
پیشتر در رابطه با ایجاد ایندکس منحصر به فرد در EF Code first مطالبی در سایت منتشر شده‌اند:
«ایجاد ایندکس منحصربفرد در EF Code first »
در ادامه نحوه‌ی ایجاد آن را به صورت Fluent API بررسی خواهیم کرد:
مدل زیر را در نظر بگیرید:
public class SubCategory : BaseEntity
{
        public string Title { get; set; }
        [ForeignKey("CategoryId")]
        public virtual Category Category { get; set; }
        public Guid CategoryId { get; set; }
}
برای مدل فوق می‌خواهیم بر روی فیلدهای Title و CategoryId ایندکسی را ایجاد کنیم، برای این منظور کلاس زیر را برای ایجاد ایندکس ایجاد خواهیم کرد:
public class SubCategoryConfiguration : EntityTypeConfiguration<SubCategory>
 {
        public SubCategoryConfiguration()
        {
            Property(p => p.CategoryId).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("AK_SubCategory", 1){ IsUnique = true}));
            Property(p => p.Title).HasMaxLength(30).IsRequired().HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("AK_SubCategory", 2){ IsUnique = true}));
            Property(so => so.RowVersion).IsRowVersion();
        }
}

همانطور که مشاهده می‌کنید اینکار را با استفاده از ویژگی IndexAttribute انجام داده‌ایم. تمامی تنظیمات یک ایندکس را توسط این کلاس می‌توانیم انجام دهیم؛ تنظیماتی از قبیل نام ایندکس، منحصر به فرد بودن ایندکس و... را می‌توانیم مشخص کنیم:
public virtual bool IsClustered { get; set; }
public virtual int Order { get; set; }
public virtual bool IsUnique { get; set; }

در نهایت با استفاده از HasColumnAnnotation ویژگی Index را به پراپرتی Title اضافه کرده‌ایم. این متد دو پارامتر از ورودی دریافت می‌کند. پارامتر اول نام annotation می‌باشد که دقیقاً باید همنام با annotation‌های موجود باشد. پارامتر دوم نیز می‌تواند یک رشته و یا یک آبجکت باشد. در حالت دوم آبجکت‌ها باید قابلیت سریالایز شدن توسط اینترفیس IMetadataAnnotationSerializer را داشته باشند. در کد فوق ایندکس را بر روی دو فیلد ایجاد کرده‌ایم. همچنین می‌توان بر روی یک فیلد نیز چندین ایندکس داشته باشید:
Property(p => p.Title).HasMaxLength(30).IsRequired().HasColumnAnnotation("Index", new IndexAnnotation(new[]
{
                            new IndexAttribute("AK_Category_1") { IsUnique = true}, 
                            new IndexAttribute("AK_Category_2"), 
}));
مطالب
تبدیل فایل‌های pfx به snk
مرسوم است و توصیه شده است که جهت ارائه کتابخانه‌های دات نتی خود از امضای دیجیتال استفاده کنید. VS.NET برای این منظور در برگه signing خواص یک پروژه، چنین امکانی را به صورت توکار ارائه می‌دهد.
حال اگر بخواهیم همین پروژه را به صورت سورس باز ارائه دهیم، استفاده کنندگان نهایی به مشکل برخواهند خورد؛ زیرا فایل pfx حاصل، توسط کلمه عبور محافظت می‌شود و در سایر سیستم‌ها بدون درنظر گرفتن این ملاحظات قابل استفاده نخواهد بود.
معادل فایل‌های pfx، فایلهایی هستند با پسوند snk که تنها تفاوت مهم آن‌ها با فایل‌های pfx، عدم محافظت توسط کلمه عبور است و ... برای کارهای خصوصا سورس باز انتخاب مناسبی به شمار می‌روند. اگر دقت کنید، اکثر پروژه‌های سورس باز دات نتی موجود در وب (مانند NHibernate، لوسین، iTextSharp و غیره) از فایل‌های snk برای اضافه کردن امضای دیجیتال به کتابخانه نهایی تولیدی استفاده می‌کنند و نه فایل‌های pfx محافظت شده.
در اینجا اگر فایل pfx ایی دارید و می‌خواهید معادل snk آن‌را تولید کنید، قطعه کد زیر چنین امکانی را مهیا می‌سازد:
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace PfxToSnk
{
    class Program
    {
        /// <summary>
        /// Converts .pfx file to .snk file.
        /// </summary>
        /// <param name="pfxData">.pfx file data.</param>
        /// <param name="pfxPassword">.pfx file password.</param>
        /// <returns>.snk file data.</returns>
        public static byte[] Pfx2Snk(byte[] pfxData, string pfxPassword)
        {
            var cert = new X509Certificate2(pfxData, pfxPassword, X509KeyStorageFlags.Exportable);
            var privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
            return privateKey.ExportCspBlob(true);
        }

        static void Main(string[] args)
        {
            var pfxFileData = File.ReadAllBytes(@"D:\Key.pfx");
            var snkFileData = Pfx2Snk(pfxFileData, "my-pass");
            File.WriteAllBytes(@"D:\Key.snk", snkFileData);
        }
    }
}
 
مطالب
ذخیره تنظیمات متغیر مربوط به یک وب اپلیکیشن ASP.NET MVC با استفاده از EF
طی این  مقاله، نحوه‌ی ذخیره سازی تنظیمات متغیر و پویای یک برنامه را به صورت Strongly Typed ارائه خواهم داد. برای این منظور، یک API را که از Lazy Loading ، Cache ، Reflection و Entity Framework بهره میگیرد، خواهیم ساخت.
برنامه‌ی هدف ما که از این API استفاده می‌کند، یک اپلیکیشن Asp.net MVC است. قبل از شروع به ساخت API مورد نظر، یک دید کلی در مورد آنچه که قرار است در نهایت توسعه یابد، در زیر مشاهده میکنید:
public SettingsController(ISettings settings)
{
  // example of saving 
  _settings.General.SiteName = "دات نت تیپس";
  _settings.Seo.HomeMetaTitle = ".Net Tips";
  _settings.Seo.HomeMetaKeywords = "َAsp.net MVC,Entity Framework,Reflection";
  _settings.Seo.HomeMetaDescription = "ذخیره تنظیمات برنامه";
  _settings.Save();
}

همانطور که در کدهای بالا مشاهده میکنید، شی setting_ ما دارای دو پراپرتی فقط خواندنی بنام‌های General و Seo است که شامل  تنظیمات مورد نظر ما هستند و این دو کلاس از کلاس پایه‌ی SettingBase ارث بری کرده‌اند. دو دلیل برای انجام این کار وجود دارد:
  1. تنظیمات به صورت گروه بندی شده در کنار  هم قرار گرفته‌اند و یافتن تنظیمات برای زمانی که نیاز به دسترسی  به آنها داریم، راحت‌تر و ساده‌تر خواهد بود. 
  2. به این شکل تنظیمات قابل دسترس در یک گروه، از دیتابیس بازیابی خواهند شد.

اصلا چرا باید این تنظیمات را در دیتابیس ذخیره کنیم؟ 

شاید فکر کنید چرا باید تنظیمات را در دیتابیس ذخیره کنیم در حالی که فایل web.config در درسترس است و می‌توان توسط کلاس ConfigurationManager به اطلاعات آن دسترسی داشت.
جواب: دلیل این است که با تغییر فایل web.config، برنامه‌ی وب شما ری استارت خواهد شد (چه زمان‌هایی یک برنامه Asp.net ری استارت میشود).
برای جلوگیری از این مساله، راه حل مناسب برای ذخیره سازی اطلاعاتی که نیاز به تغییر در زمان اجرا دارند، استفاده از از دیتابیس می‌باشد. در این مقاله از Entity Framework و پایگاه داده Sql Sever استفاده می‌کنم.

مراحل ساخت Setting API مورد نظر به شرح زیر است:
  1. ساخت یک Asp.net Web Application 
  2. ساخت مدل Setting و افزودن آن به کانتکست Entity Framework 
  3. ساخت کلاس SettingBase برای بازیابی و ذخیره سازی تنظیمات با رفلکشن
  4. ساخت کلاس GenralSettins و SeoSettings که از کلاس SettingBase ارث بری کرده‌اند.
  5. ساخت کلاس Settings به منظور مدیریت تمام انواع تنظیمات 

یک برنامه‌ی Asp.Net Web Application را از نوع MVC ایجاد کنید. تا اینجا مرحله‌ی اول ما به پایان رسید؛ چرا که ویژوال استودیو کار‌های مورد نیاز ما را انجام خواهد داد.
 لازم است مدل خود را به ApplicationDbContext موجود در فایل IdentityModels.cs معرفی کنیم. به شکل زیر:
namespace DynamicSettingAPI.Models
{
    public interface IUnitOfWork
    {
        DbSet<Setting> Settings { get; set; }
        int SaveChanges();
    }
} 

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>,IUnitOfWork
    {
        public DbSet<Setting> Settings { get; set; }
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }


namespace DynamicSettingAPI.Models
{
    public class Setting
    {
        public string Name { get; set; }
        public string Type { get; set; }
        public string Value { get; set; }
    }
}
مدل تنظیمات ما خیلی ساده است و دارای سه پراپرتی به نام‌های Name ، Type ، Value هست که به ترتیب برای دریافت مقدار تنظیمات، نام کلاسی که از کلاس SettingBase ارث برده و نام تنظیمی که لازم داریم ذخیره کنیم، در نظر گرفته شده‌اند. 
لازم است تا متد OnModelCreating مربوط به ApplicationDbContext را نیز تحریف کنیم تا کانفیگ مربوط به مدل خود را نیز اعمال نمائیم.
 protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Setting>()
                    .HasKey(x => new { x.Name, x.Type });

            modelBuilder.Entity<Setting>()
                        .Property(x => x.Value)
                        .IsOptional();

            base.OnModelCreating(modelBuilder);
        }
ساختاری به شکل زیر مد نظر ماست:

  کلاس SettingBase ما همچین ساختاری را خواهد داشت:
namespace DynamicSettingAPI.Service
{
    public abstract class SettingsBase
    {
        //1
        private readonly string _name;
        private readonly PropertyInfo[] _properties;

        protected SettingsBase()
        {
            //2
            var type = GetType();
            _name = type.Name;
            _properties = type.GetProperties();
        }

        public virtual void Load(IUnitOfWork unitOfWork)
        {
            //3 get setting for this type name
            var settings = unitOfWork.Settings.Where(w => w.Type == _name).ToList();

            foreach (var propertyInfo in _properties)
            {
                //get the setting from setting list
                var setting = settings.SingleOrDefault(s => s.Name == propertyInfo.Name);
                if (setting != null)
                {
                    //4 set 
                    propertyInfo.SetValue(this, Convert.ChangeType(setting.Value, propertyInfo.PropertyType));
                }
            }
        }
        public virtual void Save(IUnitOfWork unitOfWork)
        {
            //5 get all setting for this type name
            var settings = unitOfWork.Settings.Where(w => w.Type == _name).ToList();

            foreach (var propertyInfo in _properties)
            {
                var propertyValue = propertyInfo.GetValue(this, null);
                var value = (propertyValue == null) ? null : propertyValue.ToString();

                var setting = settings.SingleOrDefault(s => s.Name == propertyInfo.Name);
                if (setting != null)
                {
                    // 6 update existing value
                    setting.Value = value;
                }
                else
                {
                    // 7 create new setting
                    var newSetting = new Setting()
                    {
                        Name = propertyInfo.Name,
                        Type = _name,
                        Value = value,
                    };
                    unitOfWork.Settings.Add(newSetting);
                }
            }
        }
    }
}
این کلاس قرار است توسط کلاس‌های تنظیمات ما به ارث برده شود و در واقع کارهای مربوط به رفلکشن را در این کلاس کپسوله کرده‌ایم. همانطور که مشخص است ما دو فیلد را به نام‌های name_ و properties_ به صورت فقط خواندنی در نظر گرفته ایم که نام کلاس مورد نظر ما که از این کلاس به ارث خواهد برد، به همراه پراپرتی‌های آن، در این ظرف‌ها قرار خواهند گرفت.
متد Load وظیفه‌ی واکشی تمام تنظیمات مربوط به Type و ست کردن مقادیر به دست آمده را به خصوصیات کلاس ما، برعهده دارد. کد زیر مقدار دریافتی از دیتابیس را به نوع داده پراپرتی مورد نظر تبدیل کرده و نتیجه را به عنوان Value پراپرتی ست میکند. 
propertyInfo.SetValue(this, Convert.ChangeType(setting.Value, propertyInfo.PropertyType));
متد Save نیز وظیفه‌ی ذخیره سازی مقادیر موجود در خصوصیات کلاس تنظیماتی را که از کلاس SettingBase ما به ارث برده است، به عهده دارد. 
این متد دیتا‌های موجود دردیتابیس را که متعلق به کلاس ارث برده مورد نظر ما هستند، واکشی میکند و در یک حلقه، اگر خصوصیتی در دیتابیس موجود بود، آن را ویرایش کرده وگرنه یک رکورد جدید را ثبت میکند.

  کلاس‌های تنظیمات شخصی سازی شده خود را به شکل زیر تعریف میکنیم :
  public class GeneralSettings : SettingsBase
    {
        public string SiteName { get; set; }
        public string AdminEmail { get; set; }
        public bool RegisterUsersEnabled { get; set; }
    }

 public class GeneralSettings : SettingsBase
    {
        public string SiteName { get; set; }
        public string AdminEmail { get; set; }
    }
نیازی به توضیح ندارد.
برای اینکه تنظیمات را به صورت یکجا داشته باشیم و Abstraction ای را برای استفاده از این API ارائه دهیم، یک اینترفیس و یک کلاس که اینترفیس مذکور را پیاده کرده است در نظر میگیریم: 
public interface ISettings
{
    GeneralSettings General { get; }
    SeoSettings Seo { get; }
    void Save();
}

public class Settings : ISettings
{
    // 1
    private readonly Lazy<GeneralSettings> _generalSettings;
    // 2
    public GeneralSettings General { get { return _generalSettings.Value; } }

    private readonly Lazy<SeoSettings> _seoSettings;
    public SeoSettings Seo { get { return _seoSettings.Value; } }

    private readonly IUnitOfWork _unitOfWork;
    public Settings(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
        // 3
        _generalSettings = new Lazy<GeneralSettings>(CreateSettings<GeneralSettings>);
        _seoSettings = new Lazy<SeoSettings>(CreateSettings<SeoSettings>);
    }

    public void Save()
    {
        // only save changes to settings that have been loaded
        if (_generalSettings.IsValueCreated)
            _generalSettings.Value.Save(_unitOfWork);

        if (_seoSettings.IsValueCreated)
            _seoSettings.Value.Save(_unitOfWork);

        _unitOfWork.SaveChanges();
    }
    // 4
    private T CreateSettings<T>() where T : SettingsBase, new()
    {
        var settings = new T();
        settings.Load(_unitOfWork);
        return settings;
    }
}
این اینترفیس مشخص می‌کند که ما به چه نوع تنظیماتی، دسترسی داریم و متد Save آن برای آپدیت کردن تنظیمات، در نظر گرفته شده است. هر کلاسی که از کلاس SettingBase ارث بری کرده را به صورت فیلد فقط خواندنی و با استفاده از کلاس Lazy درون آن ذکر میکنیم و به این صورت کلاس تنظیمات ما زمانی ساخته خواهد شد که برای اولین بار به آن دسترسی داشته باشیم.
متد CreateSetting وظیفه‌ی لود دیتا را از دیتابیس، بر عهده دارد که برای این منظور، متد لود Type مورد نظر را فراخوانی میکند. این متد وقتی به کلاس تنظیمات مورد نظر برای اولین بار دسترسی پیدا کنیم، فراخوانی خواهد شد.

 حتما امکان این وجود دارد که شما از امکان Caching هم بهره ببرید برای مثال همچین متد و سازنده‌ای را در کلاس Settings در نظر بگیرید:
private readonly ICache _cache;
public Settings(IUnitOfWork unitOfWork, ICache cache)
{
    // ARGUMENT CHECKING SKIPPED FOR BREVITY
    _unitOfWork = unitOfWork;
    _cache = cache;
    _generalSettings = new Lazy<GeneralSettings>(CreateSettingsWithCache<GeneralSettings>);
    _seoSettings = new Lazy<SeoSettings>(CreateSettingsWithCache<SeoSettings>);
}

private T CreateSettingsWithCache<T>() where T : SettingsBase, new()
{
    // this is where you would implement loading from ICache
    throw new NotImplementedException();
}
در آخر هم به شکل زیر میتوان (به عنوان دمو فقط ) از این API استفاده کرد.
   public ActionResult Index()
        {
            using (var uow = new ApplicationDbContext())
            {
                var _settings = new Settings(uow);
                _settings.General.SiteName = "دات نت تیپس";
                _settings.General.AdminEmail = "admin@gmail.com";
                _settings.General.RegisterUsersEnabled = true;
                _settings.Seo.HomeMetaTitle = ".Net Tips";
                _settings.Seo.MetaKeywords = "Asp.net MVC,Entity Framework,Reflection";
                _settings.Seo.HomeMetaDescription = "ذخیره تنظیمات برنامه";

                var settings2 = new Settings(uow);
                var output = string.Format("SiteName: {0} HomeMetaDescription: {1}  MetaKeywords:  {2}  MetaTitle:  {3}  RegisterEnable:  {4}",
                    settings2.General.SiteName,
                    settings2.Seo.HomeMetaDescription,
                    settings2.Seo.MetaKeywords,
                    settings2.Seo.HomeMetaTitle,
                    settings2.General.RegisterUsersEnabled.ToString()
                    );
                return Content(output);
            }

        }

خروجی :

نکته: در پروژه ای که جدیدا در سایت ارائه داده‌ام و در حال تکمیل آن هستم، از بهبود یافته‌ی این مقاله استفاده می‌شود. حتی برای اسلاید شو‌های سایت هم میشود از این روش استفاده کرد و از فرمت json بهره برد برای این منظور. حتما در پروژه‌ی مذکور همچین امکانی را هم در نظر خواهم گرفتم.
پیشنها میکنم سورس SmartStore را بررسی کنید. آن هم به شکل مشابهی ولی پیشرفته‌تر از این مقاله، همچین امکانی را دارد.