مطالب
C# 7 - Tuple return types and deconstruction
روش‌های زیادی برای بازگشت چندین مقدار از یک متد وجود دارند؛ مانند استفاده‌ی از آرایه‌ها برای بازگشت اشیایی از یک جنس، ایجاد یک کلاس سفارشی با خواص متفاوت و استفاده از پارامترهای out و ref همانند روش‌های متداول در C و ++C. در این بین روش دیگری نیز به نام Tuples از زمان NET 4.0. برای بازگشت چندین شیء با نوع‌های مختلف، ارائه شده‌است که در C# 7 نحوه‌ی تعریف و استفاده‌ی از آن‌ها بهبود قابل ملاحظه‌ای یافته‌است.


Tuple چیست؟

هدف از کار با Tupleها، عدم تعریف یک کلاس جدید به همراه خواص آن، جهت بازگشت بیش از یک مقدار از یک متد، توسط وهله‌ای از این کلاس جدید می‌باشد. برای مثال اگر بخواهیم از متدی، دو مقدار شهر و ناحیه را بازگشت دهیم، یک روش آن، ایجاد کلاس مکان زیر است:
public class Location   
{ 
     public string City { get; set; } 
     public string State { get; set; } 
 
     public Location(string city, string state) 
     { 
           City = city; 
           State = state; 
     } 
}
و سپس، وهله سازی و بازگشت آن:
 var location = new Location("Lake Charles","LA");
اما توسط Tuples، بدون نیاز به تعریف یک کلاس جدید، باز هم می‌توان به همین دو خروجی، دسترسی یافت:
 var location = new Tuple<string,string>("Lake Charles","LA");   
// Print out the address
var address = $"{location.Item1}, {location.Item2}";


مشکلات نوع Tuple در نگارش‌های قبلی دات نت

هرچند Tuples از زمان دات نت 4 در دسترس هستند، اما دارای این کمبودها و مشکلات می‌باشند:
static Tuple<int, string, string> GetHumanData()
{
   return Tuple.Create(10, "Marcus", "Miller");
}
الف) پارامترهای خروجی آن‌ها ثابت و با نام‌هایی مانند Item1، Item2 و امثال آن هستند که در حین استفاده، به علت ضعف نامگذاری، کاربرد آن‌ها دقیقا مشخص نیست و کاملا بی‌معنا هستند:
 var data = GetHumanData();
Console.WriteLine("What is this value {0} or this {1}",  data.Item1, data.Item3);
ب) Reference Type هستند (کلاس هستند) و در زمان وهله سازی، میزان مصرف حافظه‌ی بیشتری را نسبت به Value Types (معادل Tuples در C# 7) دارند.
ج) Tuples در دات نت 4، صرفا یک کتابخانه‌ی اضافه شده‌ی به فریم ورک بوده و زبان‌های دات نتی، پشتیبانی توکاری را از آن‌ها جهت بهبود و یا ساده سازی تعریف آن‌ها، ارائه نمی‌دهند.


ایجاد Tuples در C# 7

برای ایجاد Tuples در سی شارپ 7، از پرانتزها به همراه ذکر نام و نوع پارامترها استفاده می‌شود.
(int x1, string s1) = (3, "one");
Console.WriteLine($"{x1} {s1}");
در مثال فوق، یک Tuple ایجاد شده‌است و در آن مقدار 3 به x1 و مقدار "one" به s1 انتساب داده شده‌اند. به این عملیات deconstruction هم می‌گویند.
دسترسی به این مقادیر نیز همانند متغیرهای معمولی است.

اگر سعی کنیم این قطعه کد را کامپایل نمائیم، با خطای ذیل متوقف خواهیم شد:
 error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported
برای رفع این مشکل نیاز است بسته‌ی نیوگت ذیل را نیز نصب کرد:
 PM> install-package System.ValueTuple

تعاریف متغیرهای بازگشتی، خارج از پرانتزها هم می‌توانند صورت گیرند:
int x2;
string s2;
(x2, s2) = (42, "two");
Console.WriteLine($"{x2} {s2}");


بازگشت Tuples از متدها

متد ذیل، دو خروجی نتیجه و باقیمانده‌ی تقسیم دو عدد صحیح را باز می‌گرداند:
static (int, int) Divide(int x, int y)
{
   int result = x / y;
   int reminder = x % y;
 
   return (result, reminder);
}
برای این منظور، نوع خروجی متد به صورت (int, int) و همچنین مقدار بازگشتی نیز به صورت یک Tuple از نتیجه و باقیمانده‌ی تقسیم، تعریف شده‌است.
در ادامه نحوه‌ی استفاده‌ی از این متد را مشاهده می‌کنید:
 (int result, int reminder) = Divide(11, 3);
Console.WriteLine($"{result} {reminder}");

در اینجا امکان استفاده‌ی از var نیز برای تعریف نوع متغیرهای دریافتی از یک Tuple نیز وجود دارد و کامپایلر به صورت خودکار نوع آن‌ها را بر اساس نوع خروجی tuple مشخص می‌کند:
 (var result1, var reminder1) = Divide(11, 3);
Console.WriteLine($"{result1} {reminder1}");
و یا حتی چون نوع var پارامترها در اینجا یکی است و در هر دو حالت به int اشاره می‌کند، می‌توان این var را در خارج از پرانتز هم قرار داد:
 var (result1, reminder1) = Divide(11, 3);

و یا برای نمونه متد GetHumanData دات نت 4 ابتدای بحث را به صورت ذیل می‌توان در C# 7 بازنویسی کرد:
static (int, string, string) GetHumanData()
{
   return (10, "Marcus", "Miller");
}
و سپس به نحو واضح‌تری از آن استفاده نمود؛ بدون استفاده‌ی اجباری از Item1 و غیره (هرچند هنوز هم می‌توان از آن‌ها استفاده کرد):
 (int Age, string FirstName, string LastName) results = GetHumanData();
Console.WriteLine(results.Age);
Console.WriteLine(results.FirstName);
Console.WriteLine(results.LastName);


پشت صحنه‌ی Tuples در C# 7

همانطور که عنوان شد، برای اینکه بتوانید قطعه کدهای فوق را کامپایل کنید، نیاز به بسته‌ی نیوگت System.ValueTuple است. در حقیقت کامپایلر خروجی متد فوق را به نحو ذیل تفسیر می‌کند:
 ValueTuple<int, int> tuple1 = Divide(11, 3);
برای مثال قطعه کد
 (int, int) n = (1,1);
System.Console.WriteLine(n.Item1);
توسط کامپایلر به قطعه کد ذیل ترجمه می‌شود:
 ValueTuple<int, int> n = new ValueTuple<int, int>(1, 1);
System.Console.WriteLine(n.Item1);
- برخلاف نگارش‌های پیشین دات نت که Tuples در آن‌ها reference type بودند، این ValueTuple یک struct است و به همین جهت سربار تخصیص حافظه‌ی کمتری را به همراه داشته و از لحاظ کارآیی و میزان مصرف حافظه بهینه‌تر عمل می‌کند.
- همچنین در اینجا محدودیتی از لحاظ تعداد پارامترهای ذکر شده‌ی در یک Tuple وجود ندارد.
 (int,int,int,int,int,int,int,(int,int))
در اینجا هم مانند قبل (دات نت 4) 8 آیتم را می‌توان تعریف کرد؛ اما چون آخرین آیتم ValueTuple تعریف شده نیز یک Tuple است، در عمل محدودیتی از نظر تعداد پارامتر نخواهیم داشت.


مفهوم Tuple Literals

همانند نگارش‌های پیشین دات نت، خروجی یک Tuple را می‌توان به یک متغیر از نوع var و یا ValueType نیز نسبت داد:
 var tuple2 = ("Stephanie", 7);
Console.WriteLine($"{tuple2.Item1}, {tuple2.Item2}");
در این حالت برای دسترسی به مقادیر Tuple همانند قبل باید از فیلدهای Item1 و Item2 و ... استفاده کرد.
به علاوه در سی شارپ 7  می‌توان برای اعضای یک Tuple نام نیز تعریف کرد که به آن‌ها Tuple literals گویند:
 var tuple3 = (Name: "Matthias", Age: 6);
Console.WriteLine($"{tuple3.Name} {tuple3.Age}");
در این حالت زمانیکه Tuple به یک متغیر از نوع var نسبت داده می‌شود، می‌توان به خروجی آن بر اساس نام‌های اعضای Tuple، بجای ذکر Item1 و ... دسترسی یافت که خوانایی بیشتری دارند.

و یا هنگام تعریف نوع خروجی، می‌توان نام پارامترهای متناظر را نیز ذکر کرد که به آن named elements هم می‌گویند:
static (int radius, double area) CalculateAreaOfCircle(int radius)
{
   return (radius, Math.PI * Math.Pow(radius, 2));
}
و نمونه‌ای از کاربرد آن به صورت ذیل است که در اینجا خروجی Tuple صرفا به یک متغیر از نوع var نسبت داده شده‌است و توسط نام پارامترهای خروجی متد، می‌توان به اعضای Tuple دسترسی یافت.
 var circle = CalculateAreaOfCircle(2);
Console.WriteLine($"A circle of radius, {circle.radius}," +
 $" has an area of {circle.area:N2}.");


مفهوم Deconstructing Tuples

مفهوم deconstruction که در ابتدای بحث عنوان شد صرفا مختص به Tuples نیست. در C# 7 می‌توان مشخص کرد که چگونه یک نوع خاص، به اجزای آن تجزیه شود. برای مثال کلاس شخص ذیل را درنظر بگیرید:
class Person
{
    private readonly string _firstName;
    private readonly string _lastName;
 
    public Person(string firstname, string lastname)
    {
        _firstName = firstname;
        _lastName = lastname;
    }
 
    public override String ToString() => $"{_firstName} {_lastName}";
 
    public void Deconstruct(out string firstname, out string lastname)
    {
        firstname = _firstName;
        lastname = _lastName;
    }
}
- در اینجا یک متد جدید را به نام Deconstruct مشاهده می‌کنید. کار این متد جدید که توسط کامپایلر استفاده خواهد شد، ارائه‌ی روشی است برای «تجزیه‌ی» یک نوع، به یک Tuple‌. متد Deconstruct تعریف شده‌ی در اینجا توسط پارامترهایی از نوع out، دو خروجی را مشخص می‌کنند. امکان تعریف این متد ویژه، به صورتیکه یک Tuple را بازگرداند، وجود ندارد.
- علت تعریف این دو خروجی هم به constructor و یا سازنده‌ی کلاس بر می‌گردد که دو ورودی را دریافت می‌کند. اگر یک کلاس چندین سازنده داشته باشد، به همان تعداد می‌توان متد Deconstruct تعریف کرد؛ به همراه خروجی‌هایی متناظر با نوع پارامترهای سازنده‌ها.
- علت استفاده‌ی از نوع خروجی out نیز این است که در #C نمی‌توان چندین overload را صرفا بر اساس نوع خروجی‌های متفاوت متدها تعریف کرد.
- متد Deconstruct به صورت خودکار در زمان تجزیه‌ی یک شیء به یک tuple فراخوانی می‌شود. در مثال زیر، شیء p1 به یک Tuple تجزیه شده‌است و این تجزیه بر اساس متد Deconstruct این کلاس مفهوم پیدا می‌کند:
 var p1 = new Person("Katharina", "Nagel");
(string first, string last) = p1;
Console.WriteLine($"{first} {last}");


امکان تعریف متد Deconstruct‌، به صورت یک متد الحاقی

روش اول تعریف متد ویژه‌ی Deconstruct را در مثال قبل، در داخل کلاس اصلی مشاهده کردید. روش دیگر آن، استفاده‌ی از متدهای الحاقی است که در این مورد خاص نیز مجاز است:
public class Rectangle
{
    public Rectangle(int height, int width)
    {
        Height = height;
        Width = width;
    }
 
    public int Width { get; }
    public int Height { get; }
}
 
public static class RectangleExtensions
{
    public static void Deconstruct(this Rectangle rectangle, out int height, out int width)
    {
        height = rectangle.Height;
        width = rectangle.Width;
    }
}
در اینجا کلاس مستطیل دارای سازنده‌ای با دو پارامتر است؛ اما متد Deconstruct آن به صورت یک متد الحاقی، خارج از کلاس اصلی تعریف شده‌است.
اکنون امکان انتساب وهله‌ای از این کلاس به یک Tuple وجود دارد:
 var r1 = new Rectangle(100, 200);
(int height, int width) = r1;
Console.WriteLine($"height: {height}, width: {width}");


امکان جایگزین کردن Anonymous types با Tuples

قطعه کد ذیل را در نظر بگیرید:
List<Employee> allEmployees = new List<Employee>()
{
  new Employee { ID = 1L, Name = "Fred", Salary = 50000M },
  new Employee { ID = 2L, Name = "Sally", Salary = 60000M },
  new Employee { ID = 3L, Name = "George", Salary = 70000M }
};
var wellPaid =
  from oneEmployee in allEmployees
  where oneEmployee.Salary > 50000M
  select new { EmpName = oneEmployee.Name,
               Income = oneEmployee.Salary };
در اینجا خروجی LINQ تهیه شده یک لیست anonymously typed است؛ با محدودیت‌هایی مانند عدم امکان استفاده‌ی از خروجی آن در سایر اسمبلی‌ها. این نوع‌های ویژه تنها محدود هستند به همان اسمبلی که در آن تعریف می‌شوند. اما در C# 7 می‌توان قطعه کد فوق را با Tuples به صورت ذیل بازنویسی کرد که این محدودیت‌ها را هم ندارد (با هدف به حداقل رساندن تعداد ViewModel‌های تعریفی یک برنامه):
var wellPaid =
  from oneEmployee in allEmployees
  where oneEmployee.Salary > 50000M
  orderby oneEmployee.Salary descending
  select (EmpName: oneEmployee.Name,
          Income: oneEmployee.Salary);
var highestPaid = wellPaid.First().EmpName;


سایر کاربردهای Tuples

از Tuples صرفا برای تعریف چندین خروجی از یک متد استفاده نمی‌شود. در ذیل نحوه‌ی استفاده‌ی از آن‌ها را جهت تعریف کلید ترکیبی یک شیء دیکشنری و یا استفاده‌ی از آن‌ها را در آرگومان جنریک یک متد async هم مشاهده می‌کنید:
public Task<(int index, T item)> FindAsync<T>(IEnumerable<T> input, Predicate<T> match)
{
   var dictionary = new Dictionary<(int, int), string>();
   throw new NotSupportedException();
}
مطالب
EF Code First #1

در ادامه بحث ASP.NET MVC می‌شود به ابزاری به نام MVC Scaffolding اشاره کرد. کار این ابزار که توسط یکی از اعضای تیم ASP.NET MVC به نام استیو اندرسون تهیه شده، تولید کدهای اولیه یک برنامه کامل ASP.NET MVC از روی مدل‌های شما می‌باشد. حجم بالایی از کدهای تکراری آغازین برنامه را می‌شود توسط این ابزار تولید و بعد سفارشی کرد. MVC Scaffolding حتی قابلیت تولید کد بر اساس الگوی Repository و یا نوشتن Unit tests مرتبط را نیز دارد. بدیهی است این ابزار جای یک برنامه نویس را نمی‌تواند پر کند اما کدهای آغازین یک سری کارهای متداول و تکراری را به خوبی می‌تواند پیاده سازی و ارائه دهد. زیر ساخت این ابزار، علاوه بر ASP.NET MVC، آشنایی با Entity framework code first است.
در طی سری ASP.NET MVC که در این سایت تا به اینجا مطالعه کردید من به شدت سعی کردم از ابزارگرایی پرهیز کنم. چون شخصی که نمی‌داند مسیریابی چیست، اطلاعات چگونه به یک کنترلر منتقل یا به یک View ارسال می‌شوند، قراردادهای پیش فرض فریم ورک چیست یا زیر ساخت امنیتی یا فیلترهای ASP.NET MVC کدامند، چطور می‌تواند از ابزار پیشرفته Code generator ایی استفاده کند، یا حتی در ادامه کدهای تولیدی آن‌را سفارشی سازی کند؟ بنابراین برای استفاده از این ابزار و درک کدهای تولیدی آن، نیاز به یک پیشنیاز دیگر هم وجود دارد: «Entity framework code first»
امسال دو کتاب خوب در این زمینه منتشر شده‌اند به نام‌های:
Programming Entity Framework: DbContext, ISBN: 978-1-449-31296-1
Programming Entity Framework: Code First, ISBN: 978-1-449-31294-7
که هر دو به صورت اختصاصی به مقوله EF Code first پرداخته‌اند.


در طی روزهای بعدی EF Code first را با هم مرور خواهیم کرد و البته این مرور مستقل است از نوع فناوری میزبان آن؛ می‌خواهد یک برنامه کنسول باشد یا WPF یا یک سرویس ویندوز NT و یا ... یک برنامه وب.
البته از دیدگاه مایکروسافت، M در MVC به معنای EF Code first است. به همین جهت MVC3 به صورت پیش فرض ارجاعی را به اسمبلی‌های آن دارد و یا حتی به روز رسانی که برای آن ارائه داده نیز در جهت تکمیل همین بحث است.


مروری سریع بر تاریخچه Entity framework code first

ویژوال استودیو 2010 و دات نت 4، به همراه EF 4.0 ارائه شدند. با این نگارش امکان استفاده از حالت‌های طراحی database first و model first مهیا است. پس از آن، به روز رسانی‌های EF خارج از نوبت و به صورت منظم، هر از چندگاهی ارائه می‌شوند و در زمان نگارش این مطلب، آخرین نگارش پایدار در دسترس آن 4.3.1 می‌باشد. از زمان EF 4.1 به بعد، نوع جدیدی از مدل سازی به نام Code first به این فریم ورک اضافه شد و در نگارش‌های بعدی آن، مباحث DB migration جهت ساده سازی تطابق اطلاعات مدل‌ها با بانک اطلاعاتی، اضافه گردیدند. در روش Code first، کار با طراحی کلاس‌ها که در اینجا مدل داده‌ها نامیده می‌شوند، شروع گردیده و سپس بر اساس این اطلاعات، تولید یک بانک اطلاعاتی جدید و یا استفاده از نمونه‌ای موجود میسر می‌گردد.
پیشتر در روش database first ابتدا یک بانک اطلاعاتی موجود، مهندسی معکوس می‌شد و از روی آن فایل XML ایی با پسوند EDMX تولید می‌گشت. سپس به کمک entity data model designer ویژوال استودیو، این فایل نمایش داده شده و یا امکان اعمال تغییرات بر روی آن میسر می‌شد. همچنین در روش دیگری به نام model first نیز کار از entity data model designer جهت طراحی موجودیت‌ها آغاز می‌گشت.
اما با روش Code first دیگر در ابتدای امر مدل فیزیکی و یک بانک اطلاعاتی وجود خارجی ندارد. در اینجا EF تعاریف کلاس‌های شما را بررسی کرده و بر اساس آن، اطلاعات نگاشت‌های خواص کلاس‌ها به جداول و فیلدهای بانک اطلاعاتی را تشکیل می‌دهد. البته عموما تعاریف ساده کلاس‌ها بر این منظور کافی نیستند. به همین جهت از یک سری متادیتا به نام ویژگی‌ها یا اصطلاحا data annotations مهیا در فضای نام System.ComponentModel.DataAnnotations برای افزودن اطلاعات لازم مانند نام فیلدها، جداول و یا تعاریف روابط ویژه نیز استفاده می‌گردد. به علاوه در روش Code first یک API جدید به نام Fluent API نیز جهت تعاریف این ویژگی‌ها و روابط، با کدنویسی مستقیم نیز درنظر گرفته شده است. نهایتا از این اطلاعات جهت نگاشت کلاس‌ها به بانک اطلاعاتی و یا برای تولید ساختار یک بانک اطلاعاتی خالی جدید نیز می‌توان کمک گرفت.



مزایای EF Code first

- مطلوب برنامه نویس‌ها! : برنامه نویس‌هایی که مدتی تجربه کار با ابزارهای طراح را داشته باشند به خوبی می‌دانند این نوع ابزارها عموما demo-ware هستند. چندجا کلیک می‌کنید، دوبار Next، سه بار OK و ... به نظر می‌رسد کار تمام شده. اما واقعیت این است که عمری را باید صرف نگهداری و یا پیاده سازی جزئیاتی کرد که انجام آن‌ها با کدنویسی مستقیم بسیار سریعتر، ساده‌تر و با کنترل بیشتری قابل انجام است.
- سرعت: برای کار با EF Code first نیازی نیست در ابتدای کار بانک اطلاعاتی خاصی وجود داشته باشد. کلا‌س‌های خود را طراحی و شروع به کدنویسی کنید.
- سادگی: در اینجا دیگر از فایل‌های EDMX خبری نیست و نیازی نیست مرتبا آن‌ها را به روز کرده یا نگهداری کرد. تمام کارها را با کدنویسی و کنترل بیشتری می‌توان انجام داد. به علاوه کنترل کاملی بر روی کد نهایی تهیه شده نیز وجود دارد و توسط ابزارهای تولید کد، ایجاد نمی‌شوند.
- طراحی بهتر بانک اطلاعاتی نهایی: اگر طرح دقیقی از مدل‌های برنامه داشته باشیم، می‌توان آن‌ها را به المان‌های کوچک و مشخصی، تقسیم و refactor کرد. همین مساله در نهایت مباحث database normalization را به نحوی مطلوب و با سرعت بیشتری میسر می‌کند.
- امکان استفاده مجدد از طراحی کلاس‌های انجام شده در سایر ORMهای دیگر. چون طراحی مدل‌های برنامه به بانک اطلاعاتی خاصی گره نمی‌خورند و همچنین الزاما هم قرار نیست جزئیات کاری EF در آن‌ها لحاظ شود، این کلاس‌ها در صورت نیاز در سایر پروژه‌ها نیز به سادگی قابل استفاده هستند.
- ردیابی ساده‌تر تغییرات: روش اصولی کار با پروژه‌های نرم افزاری همواره شامل استفاده از یک ابزار سورس کنترل مانند SVN، Git، مرکوریال و امثال آن است. به این ترتیب ردیابی تغییرات انجام شده به سادگی توسط این ابزارها میسر می‌شوند.
- ساده‌تر شدن طراحی‌های پیچیده‌تر: برای مثال پیاده سازی ارث بری،‌ ایجاد کلاس‌های خود ارجاع دهنده و امثال آن با کدنویسی ساده‌تر است.


دریافت آخرین نگارش EF


برای دریافت و نصب آخرین نگارش EF نیاز است از NuGet استفاده شود و این مزایا را به همراه دارد:
به کمک NuGet امکان با خبر شدن از به روز رسانی جدید صورت گرفته به صورت خودکار درنظر گرفته شده است و همچنین کار دریافت بسته‌های مرتبط و به روز رسانی ارجاعات نیز در این حالت خودکار است. به علاوه توسط NuGet امکان دسترسی به کتابخانه‌هایی که مثلا در گوگل‌کد قرار دارند و به صورت معمول امکان دریافت آن‌ها برای ما میسر نیست، نیز بدون مشکل فراهم است (برای نمونه ELMAH، که اصل آن از گوگل‌کد قابل دریافت است؛ اما بسته نیوگت آن نیز در دسترس می‌باشد).
پس از نصب NuGet، تنها کافی است بر روی گره References در Solution explorer ویژوال استودیو، کلیک راست کرده و به کمک NuGet آخرین نگارش EF را نصب کرد. در گالری آنلاین آن، عموما EF اولین گزینه است (به علت تعداد بالای دریافت آن).
حین استفاده از NuGet جهت نصب Ef، ابتدا ارجاعاتی به اسمبلی‌های زیر به برنامه اضافه خواهند شد:
System.ComponentModel.DataAnnotations.dll
System.Data.Entity.dll
EntityFramework.dll
بدیهی است بدون استفاده از NuGet، تمام این کارها را باید دستی انجام داد.
سپس در پوشه‌ای به نام packages، فایل‌های مرتبط با EF قرار خواهند گرفت که شامل اسمبلی آن به همراه ابزارهای DB Migration است. همچنین فایل packages.config که شامل تعاریف اسمبلی‌های نصب شده است به پروژه اضافه می‌شود. NuGet به کمک این فایل و شماره نگارش درج شده در آن، شما را از به روز رسانی‌های بعدی مطلع خواهد ساخت:

<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="4.3.1" />
</packages>

همچنین اگر به فایل app.config یا web.config برنامه نیز مراجعه کنید، یک سری تنظیمات ابتدایی اتصال به بانک اطلاعاتی در آن ذکر شده است:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
<parameters>
<parameter value="Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=True" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
</configuration>

همانطور که ملاحظه می‌کنید بانک اطلاعاتی پیش فرضی که در اینجا ذکر شده است، LocalDB می‌باشد. این بانک اطلاعاتی را از این آدرس‌ نیز می‌توانید دریافت کنید.

البته ضرورتی هم به استفاده از آن نیست و از سایر نگارش‌های SQL Server نیز می‌توان استفاده کرد ولی خوب ... مزیت استفاده از آن برای کاربر نهایی این است که «نیازی به یک مهندس برای نصب، راه اندازی و نگهداری ندارد». تنها مشکل آن این است که از ویندوز XP پشتیبانی نمی‌کند. البته SQL Server CE 4.0 این محدودیت را ندارد.
ضمن اینکه باید درنظر داشت EF به فناوری میزبان خاصی گره نخورده است و مثال‌هایی که در اینجا بررسی می‌شوند صرفا تعدادی برنامه کنسول معمولی هستند و نکات عنوان شده در آن‌ها در تمام فناوری‌های میزبان موجود به یک نحو کاربرد دارند.


قراردادهای پیش فرض EF Code first

عنوان شد که اطلاعات کلاس‌های ساده تشکیل دهنده مدل‌های برنامه، برای تعریف جداول و فیلدهای یک بانک اطلاعات و همچنین مشخص سازی روابط بین آن‌ها کافی نیستند و مرسوم است برای پر کردن این خلاء از یک سری متادیتا و یا Fluent API مهیا نیز استفاده گردد. اما در EF Code first یک سری قرار داد توکار نیز وجود دارند که مطلع بودن از آن‌ها سبب خواهد شد تا حجم کدنویسی و تنظیمات جانبی این فریم ورک به حداقل برسند. برای نمونه مدل‌های معروف بلاگ و مطالب آن‌را درنظر بگیرید:

namespace EF_Sample01.Models
{
public class Post
{
public int Id { set; get; }
public string Title { set; get; }
public string Content { set; get; }
public virtual Blog Blog { set; get; }
}
}

using System.Collections.Generic;

namespace EF_Sample01.Models
{
public class Blog
{
public int Id { set; get; }
public string Title { set; get; }
public string AuthorName { set; get; }
public IList<Post> Posts { set; get; }
}
}


یکی از قراردادهای EF Code first این است که کلاس‌های مدل شما را جهت یافتن خاصیتی به نام Id یا ClassId مانند BlogId، جستجو می‌کند و از آن به عنوان primary key و فیلد identity جدول بانک اطلاعاتی استفاده خواهد کرد.
همچنین در کلاس Blog، خاصیت لیستی از Posts و در کلاس Post خاصیت virtual ایی به نام Blog وجود دارند. به این ترتیب روابط بین دو کلاس و ایجاد کلید خارجی متناظر با آن‌را به صورت خودکار انجام خواهد داد.
نهایتا از این اطلاعات جهت تشکیل database schema یا ساختار بانک اطلاعاتی استفاده می‌گردد.
اگر به فضاهای نام دو کلاس فوق دقت کرده باشید، به کلمه Models ختم شده‌اند. به این معنا که در پوشه‌ای به همین نام در پروژه جاری قرار دارند. یا مرسوم است کلاس‌های مدل را در یک پروژه class library مجزا به نام DomainClasses نیز قرار دهند. این پروژه نیازی به ارجاعات اسمبلی‌های EF ندارد و تنها به اسمبلی System.ComponentModel.DataAnnotations.dll نیاز خواهد داشت.


EF Code first چگونه کلاس‌های مورد نظر را انتخاب می‌کند؟

ممکن است ده‌ها و صدها کلاس در یک پروژه وجود داشته باشند. EF Code first چگونه از بین این کلاس‌ها تشخیص خواهد داد که باید از کدامیک استفاده کند؟ اینجا است که مفهوم جدیدی به نام DbContext معرفی شده است. برای تعریف آن یک کلاس دیگر را به پروژه برای مثال به نام Context اضافه کنید. همچنین مرسوم است که این کلاس را در پروژه class library دیگری به نام DataLayer اضافه می‌کنند. این پروژه نیاز به ارجاعی به اسمبلی‌های EF خواهد داشت. در ادامه کلاس جدید اضافه شده باید از کلاس DbContext مشتق شود:

using System.Data.Entity;
using EF_Sample01.Models;

namespace EF_Sample01
{
public class Context : DbContext
{
public DbSet<Blog> Blogs { set; get; }
public DbSet<Post> Posts { set; get; }
}
}

سپس در اینجا به کمک نوع جنریکی به نام DbSet، کلاس‌های دومین برنامه را معرفی می‌کنیم. به این ترتیب، EF Code first ابتدا به دنبال کلاسی مشتق شده از DbContext خواهد گشت. پس از یافتن آن‌، خواصی از نوع DbSet را بررسی کرده و نوع‌های متناظر با آن‌را به عنوان کلاس‌های دومین درنظر می‌گیرد و از سایر کلاس‌های برنامه صرفنظر خواهد کرد. این کل کاری است که باید انجام شود.
اگر دقت کرده باشید، نام کلاس‌های موجودیت‌ها، مفرد هستند و نام خواص تعریف شده به کمک DbSet‌، جمع می‌باشند که نهایتا متناظر خواهند بود با نام جداول بانک اطلاعاتی تشکیل شده.


تشکیل خودکار بانک اطلاعاتی و افزودن اطلاعات به جداول

تا اینجا بدون تهیه یک بانک اطلاعاتی نیز می‌توان از کلاس Context تهیه شده استفاده کرد و کار کدنویسی را آغاز نمود. بدیهی است جهت اجرای نهایی کدها، نیاز به یک بانک اطلاعاتی خواهد بود. اگر تنظیمات پیش فرض فایل کانفیگ برنامه را تغییر ندهیم، از همان defaultConnectionFactory یاده شده استفاده خواهد کرد. در این حالت نام بانک اطلاعاتی به صورت خودکار تنظیم شده و مساوی «EF_Sample01.Context» خواهد بود.
برای سفارشی سازی آن نیاز است فایل app.config یا web.config برنامه را اندکی ویرایش نمود:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
...
</configSections>
<connectionStrings>
<clear/>
<add name="Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
...
</configuration>

در اینجا به بانک اطلاعاتی testdb2012 در وهله پیش فرض SQL Server نصب شده، اشاره شده است. فقط باید دقت داشت که تگ configSections باید در ابتدای فایل قرار گیرد و مابقی تنظیمات پس از آن.
یا اگر علاقمند باشید که از SQL Server CE استفاده کنید، تنظیمات رشته اتصالی را به نحو زیر مقدار دهی نمائید:

<connectionStrings> 
              <add name="MyContextName"
                         connectionString="Data Source=|DataDirectory|\Store.sdf"
                         providerName="System.Data.SqlServerCe.4.0" />
</connectionStrings>

در هر دو حالت، name باید به نام کلاس مشتق شده از DbContext اشاره کند که در مثال جاری همان Context است.
یا اگر علاقمند بودید که این قرارداد توکار را تغییر داده و نام رشته اتصالی را با کدنویسی تعیین کنید، می‌توان به نحو زیر عمل کرد:

public class Context : DbContext
{
    public Context()
      : base("ConnectionStringName")
    {
    }


البته ضرورتی ندارد این بانک اطلاعاتی از پیش موجود باشد. در اولین بار اجرای کدهای زیر، به صورت خودکار بانک اطلاعاتی و جداول Blogs و Posts و روابط بین آن‌ها تشکیل می‌گردد:

using EF_Sample01.Models;

namespace EF_Sample01
{
class Program
{
static void Main(string[] args)
{
using (var db = new Context())
{
db.Blogs.Add(new Blog { AuthorName = "Vahid", Title = ".NET Tips" });
db.SaveChanges();
}
}
}
}


در این تصویر چند نکته حائز اهمیت هستند:
الف) نام پیش فرض بانک اطلاعاتی که به آن اشاره شد (اگر تنظیمات رشته اتصالی قید نگردد).
ب) تشکیل خودکار primary key از روی خواصی به نام Id
ج) تشکیل خودکار روابط بین جداول و ایجاد کلید خارجی (به کمک خاصیت virtual تعریف شده)
د) تشکیل جدول سیستمی به نام dbo.__MigrationHistory که از آن برای نگهداری سابقه به روز رسانی‌های ساختار جداول کمک گرفته خواهد شد.
ه) نوع و طول فیلدهای متنی، nvarchar از نوع max است.

تمام این‌ها بر اساس پیش فرض‌ها و قراردادهای توکار EF Code first انجام شده است.

در کدهای تعریف شده نیز، ابتدا یک وهله از شیء Context ایجاد شده و سپس به کمک آن می‌توان به جدول Blogs اطلاعاتی را افزود و در آخر ذخیره نمود. استفاده از using هم دراینجا نباید فراموش شود، زیرا اگر استثنایی در این بین رخ دهد، کار پاکسازی منابع و بستن اتصال گشوده شده به بانک اطلاعاتی به صورت خودکار انجام خواهد شد.
در ادامه اگر بخواهیم مطلبی را به Blog ثبت شده اضافه کنیم، خواهیم داشت:

using EF_Sample01.Models;

namespace EF_Sample01
{
class Program
{
static void Main(string[] args)
{
//addBlog();
addPost();
}

private static void addPost()
{
using (var db = new Context())
{
var blog = db.Blogs.Find(1);
db.Posts.Add(new Post
{
Blog = blog,
Content = "data",
Title = "EF"
});
db.SaveChanges();
}
}

private static void addBlog()
{
using (var db = new Context())
{
db.Blogs.Add(new Blog { AuthorName = "Vahid", Title = ".NET Tips" });
db.SaveChanges();
}
}
}
}

متد db.Blogs.Find، بر اساس primary key بلاگ ثبت شده، یک وهله از آن‌را یافته و سپس از آن جهت تشکیل شیء Post و افزودن آن به جدول Posts استفاده می‌شود. متد Find ابتدا Contxet جاری را جهت یافتن شیءایی با id مساوی یک جستجو می‌کند (اصطلاحا به آن first level cache هم گفته می‌شود). اگر موفق به یافتن آن شد، بدون صدور کوئری اضافه‌ای به بانک اطلاعاتی از اطلاعات همان شیء استفاده خواهد کرد. در غیراینصورت نیاز خواهد داشت تا ابتدا کوئری لازم را به بانک اطلاعاتی ارسال کرده و اطلاعات شیء Blog متناظر با id=1 را دریافت کند. همچنین اگر نیاز داشتیم تا تنها با سطح اول کش کار کنیم، در EF Code first می‌توان از خاصیتی به نام Local نیز استفاده کرد. برای مثال خاصیت db.Blogs.Local بیانگر اطلاعات موجود در سطح اول کش می‌باشد.
نهایتا کوئری Insert تولید شده توسط آن به شکل زیر خواهد بود (لاگ شده توسط برنامه SQL Server Profiler):

exec sp_executesql N'insert [dbo].[Posts]([Title], [Content], [Blog_Id])
values (@0, @1, @2)
select [Id]
from [dbo].[Posts]
where @@ROWCOUNT > 0 and [Id] = scope_identity()',
N'@0 nvarchar(max) ,@1 nvarchar(max) ,@2 int',
@0=N'EF',
@1=N'data',
@2=1


این نوع کوئرهای پارامتری چندین مزیت مهم را به همراه دارند:
الف) به صورت خودکار تشکیل می‌شوند. تمام کوئری‌های پشت صحنه EF پارامتری هستند و نیازی نیست مرتبا مزایای این امر را گوشزد کرد و باز هم عده‌ای با جمع زدن رشته‌ها نسبت به نوشتن کوئری‌های نا امن SQL اقدام کنند.
ب) کوئرهای پارامتری در مقابل حملات تزریق اس کیوال مقاوم هستند.
ج) SQL Server با کوئری‌های پارامتری همانند رویه‌های ذخیره شده رفتار می‌کند. یعنی query execution plan محاسبه شده آن‌ها را کش خواهد کرد. همین امر سبب بالا رفتن کارآیی برنامه در فراخوانی‌های بعدی می‌گردد. الگوی کلی مشخص است. فقط پارامترهای آن تغییر می‌کنند.
د) مصرف حافظه SQL Server کاهش می‌یابد. چون SQL Server مجبور نیست به ازای هر کوئری اصطلاحا Ad Hoc رسیده یکبار execution plan متفاوت آن‌ها را محاسبه و سپس کش کند. این مورد مشکل مهم تمام برنامه‌هایی است که از کوئری‌های پارامتری استفاده نمی‌کنند؛ تا حدی که گاهی تصور می‌کنند شاید SQL Server دچار نشتی حافظه شده، اما مشکل جای دیگری است.


مشکل! ساختار بانک اطلاعاتی تشکیل شده مطلوب کار ما نیست.

تا همینجا با حداقل کدنویسی و تنظیمات مرتبط با آن، پیشرفت خوبی داشته‌ایم؛ اما نتیجه حاصل آنچنان مطلوب نیست و نیاز به سفارشی سازی دارد. برای مثال طول فیلدها را نیاز داریم به مقدار دیگری تنظیم کنیم، تعدادی از فیلدها باید به صورت not null تعریف شوند یا نام پیش فرض بانک اطلاعاتی باید مشخص گردد و مواردی از این دست. با این موارد در قسمت‌های بعدی بیشتر آشنا خواهیم شد.
مطالب
تهیه یک DynamicXml برای خواندن اطلاعات فایل Xml با استفاده از انقیاد پویا در سی‌شارپ

در فریمورک NET. ابزارهای مختلفی برای کار با داده‌های XML در نظر گرفته شده‌است که بعد از نسخه 3.5 آن، انتخاب اول LINQ to XML می باشد. در این مطلب قصد داریم API ای را برای خواندن اطلاعات فایل‌های XML با استفاده از LINQ to XML و انقیاد پویا در سی‌شارپ (Dynamic Binding) تهیه کنیم.


راه حل اول: استفاده از ExpandoObject

public static class ExpandoXml
{
    public static dynamic AsExpando(this XDocument document)
    {
        return CreateExpando(document.Root);
    }

    private static dynamic CreateExpando(XElement element)
    {
        var result = new ExpandoObject() as IDictionary<string, object>;
        if (element.Elements().Any(e => e.HasElements))
        {
            var list = new List<ExpandoObject>();
            result.Add(element.Name.ToString(), list);
            foreach (var childElement in element.Elements())
            {
                list.Add(CreateExpando(childElement));
            }
        }
        else
        {
            foreach (var leafElement in element.Elements())
            {
                result.Add(leafElement.Name.ToString(), leafElement.Value);
            }
        }
        return result;
    }
}

در تکه کد بالا از طریق متد CreateExpando به صورت بازگشتی ابتدا بررسی می‌شود که آیا عنصر جاری دارای عناصری می‌باشد و همچنین آیا آنها دارای فرزند می‌باشند یا خیر؛ در صورت برقراری شرط، نتیجه‌ی اجرای متد CreateExpando بر روی تک تک عناصر فرزند را درون لیستی از ExpandoObject قرار داده و سپس آن لیست نیز به عنوان Value عنصر جاری در نظر گرفته می‌شود. در صورت عدم برقراری شرط مذکور، مقادیر مربوط به عناصر فرزند را در قالب یک ExpandoObject به عنوان خروجی بازگشت خواهد داد.


راه حل دوم: استفاده از DynamicObject

برای این منظور کلاس زیر را در نظر بگیرید:
    public class DynamicXml : DynamicObject, IEnumerable
    {
        private readonly dynamic _xml;
        public DynamicXml(string fileName)
        {
            _xml = XDocument.Load(fileName);
        }

        public DynamicXml(dynamic xml)
        {
            _xml = xml;
        }

        public IEnumerator GetEnumerator()
        {
            foreach (var item in _xml.Elements())
            {
                yield return new DynamicXml(item);
            }
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var xml = _xml.Element(binder.Name);
            if (xml != null)
            {
                result = new DynamicXml(xml);
                return true;
            }

            var attribute = _xml.Attribute(binder.Name);
            if (attribute != null)
            {
                result = new DynamicXml(attribute);
                return true;
            }

            result = null;
            return false;
        }

        public static implicit operator string(DynamicXml xml)
        {
            return xml._xml.Value;
        }
    }

کلاس DynamicXml از طریق سازنده اول، نام فایل را دریافت کرده و از طریق LINQ to XML با استفاده از متد Load کلاس XDocument، فایل مورد نظر بارگذاری شده و درون فیلدی به نام xml_ از نوع dynamic نگه داشته می‌شود. کار بعدی، بازنویسی متد TryGetMember می‌باشد. در بدنه بازنویسی شده این متد ابتدا بررسی می‌شود که آیا با نام خصوصت درخواست شده عنصری در داده XML وجود دارد یا خیر؛ در صورت موجود بودن، پارامتر result با یک  وهله جدید از DynamicXml مقدار دهی می‌شود که عنصر یافت شده از طریق سازنده دوم، به عنوان داده xml برای مقدار دهی فیلد xml_ به عنوان آرگومان ارسال می‌شود. در صورت عدم وجود عنصر مذکور، بدنبال خصوصیتی با آن نام بوده و در صورت یافت شدن، باز به عنوان یک وهله DynamicXml برای مقدار دهی result استفاده می‌شود.

در ادامه برای نسبت دادن یک وهله از DynamicXml به یک متغیر string و دستیابی به مقدار یک عنصر که از طریق خصوصیت، درخواست می‌شود نیاز است تا اپراتور ضمنی string را نیز برای کلاس بالا نظر بگیریم. همچنین برای ایجاد امکان پیمایش برروی عناصر فرزند از طریق foreach، لازم است واسط IEnumerable را نیز پیاده سازی کرده باشیم.


طریقه استفاده

    class Program
    {
        static void Main(string[] args)
        {
            var doc1 = XDocument.Load("Employees.xml");
            
            foreach (var element in doc1.Element("Employees").Elements("Employee"))
            {
                Console.WriteLine(element.Element("FirstName").Value);
            }

            dynamic doc2 = XDocument.Load("Employees.xml").AsExpando();
            foreach (var employee in doc2.Employees)
            {
                Console.WriteLine(employee.FirstName);
            }

            dynamic doc3 = new DynamicXml("Employees.xml");
            foreach (var employee in doc3.Employees)
            {
                Console.WriteLine(employee.FirstName);
                Console.WriteLine(employee.Id);
            }
        }
    }
و فایل Employees.xml
<?xml version="1.0" encoding="utf-8" ?>
<Employees>
    <Employee Id="1">
        <FirstName>
            Employee1
        </FirstName>
    </Employee>
     <Employee Id="2">
        <FirstName>
            Employee2
        </FirstName>
    </Employee>
     <Employee Id="3">
        <FirstName>
            Employee3
        </FirstName>
    </Employee>
     <Employee Id="4">
        <FirstName>
            Employee4
        </FirstName>
    </Employee>
</Employees>

مطالب
شروع به کار با DNTFrameworkCore - قسمت 6 - پیاده‌سازی عملیات CRUD موجودیت‌ها با استفاده از ASP.NET Core MVC
به عنوان آخرین قسمت مرتبط با تفکر مبتنی‌بر CRUD‏ ‎(‎CRUD-based thinking)‎، روش پیاده‌سازی عملیات CRUD موجودیت‌ها را با استفاده از ASP.NET Core MVC و افزونه jquery-unobtrusive-ajax بررسی خواهیم کرد. 


برای شروع لازم است بسته نیوگت زیر را نصب کنید: 
PM> Install-Package DNTFrameworkCore.Web

قراردادها، مفاهیم و نکات اولیه 
  1. Refactor کردن فرم‌های ثبت و ویرایش مرتبط با یک Aggregate، به یک PartialView که با یک ViewModel کار می‌کند. برای موجودیت‌های ساده و پایه، همان Model/DTO، به عنوان Model متناظر با یک ویو یا به اصطلاح ViewModel استفاده می‌شود؛ ولی برای سایر موارد، از مدلی که نام آن با نام موجودیت + کلمه ModalViewModel یا FormViewModel تشکیل می‌شود، استفاده خواهیم کرد.
  2. یک فرم، در قالب یک پارشال‌ویو، به صورت Ajaxای با استفاده از افزونه  jquery-unobtrusive-ajax بارگذاری شده و به سرور ارسال خواهد شد.
  3. یک فرم براساس طراحی خود می‌تواند در قالب یک مودال باز شود، یا به منظور inline-editing آن را بارگذاری و به قسمتی از صفحه که مدنظرتان می‌باشد اضافه شود.
  4. وجود ویو Index به همراه پارشال‌ویو ‎_List برای نمایش لیستی و یک پارشال‌ویو برای عملیات ثبت و ویرایش الزامی ‌می‌باشد. البته اگر از مکانیزمی که در مطلب « طراحی یک گرید با jQuery Ajax و ASP.NET MVC به همراه پیاده سازی عملیات CRUD»  مطرح شد، استفاده نمی‌کنید و نیاز دارید تا اطلاعات صفحه‌بندی شده، مرتب شده و فیلتر شده‌ای را در قالب JSON دریافت کنید، از اکشن‌متد ReadPagedList کنترلر پایه استفاده کنید.

مثال اول: پیاده‌سازی BlogsController و طراحی فرم ثبت و ویرایش
کار با ارث‌بری از کنترلر پایه طراحی شده در زیرساخت به شکل زیر آغاز می‌شود:
public class BlogsController : CrudController<IBlogService, int, BlogModel>
{
    public BlogsController(IBlogService service) : base(service)
    {
    }

    protected override string CreatePermissionName => PermissionNames.Blogs_Create;
    protected override string EditPermissionName => PermissionNames.Blogs_Edit;
    protected override string ViewPermissionName => PermissionNames.Blogs_View;
    protected override string DeletePermissionName => PermissionNames.Blogs_Delete;
    protected override string ViewName => "_BlogModal";
}
در اینجا مشخص کردن ViewName متناظر با موجودیت جاری، الزامی می‌باشد. همانطور که عنوان شد، یک پارشال‌ویو برای عملیات ثبت و ویرایش کفایت می‌کند و طراحی پشت این کنترلر پایه نیز بر این اساس می‌باشد. گام بعدی، طراحی پارشال‌ویو مذکور می‌باشد. 

<div>
    <h4 asp-if="Model.IsNew()">Create New Blog</h4>
    <h4 asp-if="!Model.IsNew()">Edit Blog</h4>
    <button type="button" data-dismiss="modal">&times;</button>
</div>
در اینجا با استفاده از تگ‌هلپر asp-if موجود در زیرساخت و متد IsNew متناظر با مدل جاری، عنوان نمایشی مودال را مشخص کرده‌ایم. 
 ‌


<form asp-action="@(Model.IsNew() ? "Create" : "Edit")" asp-controller="Blogs" asp-modal-form="BlogForm">
    <div>
        <input type="hidden" name="save-continue" value="true"/>
        <input asp-for="RowVersion" type="hidden"/>
        <input asp-for="Id" type="hidden"/>
        <div>
            <div>
                <label asp-for="Title"></label>
                <input asp-for="Title" autocomplete="off"/>
                <span asp-validation-for="Title"></span>
            </div>
        </div>
        <div>
            <div>
                <label asp-for="Url"></label>
                <input asp-for="Url" type="url"/>
                <span asp-validation-for="Url"></span>
            </div>
        </div>
    </div>

...

</form>
با استفاده از متد IsNew مدل جاری، اکشن متد متناظر با درخواست POST مشخص شده و سپس از طریق تگ‌هلپر asp-modal-form، فرآیند افزودن ویژگی‌های data-ajax به فرم را خودکار کرده‌ایم. سپس با استفاده از یک Hidden Input با نام save-continue و مقدار true به فرم، عملیات ذخیره کردن بدون بسته شدن فرم را شبیه سازی کرده‌ایم؛ این Input می‌تواند به صورت شرطی به فرم اضافه شود (دکمه‌های ذخیره/ذخیره و بستن و ...) که در سمت سرور با توجه به مقدار ارسالی تصمیم گرفته می‌شود که دوباره فرم با اطلاعات جدید (در اینجا Id و RowVersion) به کلاینت ارسال شود یا خیر.

<div>
    <a asp-modal-delete-link asp-model-id="@Model.Id" asp-modal-toggle="false"
       asp-controller="Blogs" asp-action="Delete" asp-if="!Model.IsNew()" asp-permission="@PermissionNames.Blogs_Delete"
       title="Delete Blog">
        <i></i>
    </a>
    <a title="Refresh Blog" asp-if="!Model.IsNew()" asp-modal-link asp-modal-toggle="false"
       asp-controller="Blogs" asp-action="Edit" asp-route-id="@Model.Id">
        <i></i>
    </a>
    <a title="New Blog" asp-modal-link asp-modal-toggle="false"
       asp-controller="Blogs" asp-action="Create">
        <i></i>
    </a>
    <button type="button" data-dismiss="modal">
        <i></i>&nbsp; Cancel
    </button>
    <button type="submit">
        <i></i>&nbsp; Save Changes
    </button>
</div>
بخش فوتر این مودال در برگیرنده دکمه‌های متناظر با اکشن‌های قابل انجام بر روی موجودیت جاری می‌باشد.
در این فرم ابتدا 3 دکمه میانبر برای عملیات حذف، بازنشانی و جدید درنظر گرفته شده‌اند؛ برای حذف که از طریق تگ‌هلپر asp-modal-delete-link کار افزودن خودکار ویژگی‌های data-ajax انجام شده و در نهایت با توجه به اکشن‌متد و کنترلر مشخص شده، یک مودال، برای گرفتن تأیید از کاربر باز می‌شود و در زمان پذیرش درخواست، حذف مدل جاری به سرور ارسال خواهد شد. با استفاده از تگ‌هلپر asp-permission نیز دسترسی مورد نیاز برای مشاهده دکمه مورد نظر را مشخص کرده‌ایم. دکمه‌های بازنشانی و جدید، در اینجا از طریق تگ‌هلپر asp-modal-link تبدیل به یک لینک Ajaxای شده است که کار بارگذاری یک پارشال‌ویو را انجام می‌دهند؛ همچنین با استفاده از ویژگی asp-modal-toggle متناظر با تگ‌هلپر مذکور با مقدار false، مشخص کرده‎ایم که صرفا محتوای مودال جاری جایگزین شود و نیاز به گشوده شدن دوباره آن نیست.


خروجی نهایی در حالت ویرایش به شکل زیر می‌باشد:

‌‌
مثال دوم: پیاده‌سازی RolesController و طراحی فرم ثبت و ویرایش
‌‌نکته قابل توجه در طراحی فرم‌های ثبت و ویرایش موجودیت‌های پیچیده‌تر مربوط است به ارسال اطلاعات اضافه‌تر از هر آنچه در مدل وجود دارد به کلاینت و همچنین دریافت اطلاعات در ساختار تودرتو یا به اصطلاح یک گراف از اشیاء. به عنوان مثال برای ثبت یک گروه کاربری نیاز است لیست دسترسی‌ها را نیز در فرم نمایش دهیم تا توسط کاربر انتخاب شود؛ در این حالت نیاز است یک مدل متناظر و متناسب را به شکل زیر طراحی کرد:
public class RoleModalViewModel : RoleModel
{
    public IReadOnlyList<LookupItem> PermissionList { get; set; }
}
در اینجا با ارث‌بری از RoleModel متناظر با موجودیت گروه کاربری، لیست دسترسی‌ها نیز در طریق خصوصیت PermissionList قابل استفاده می‌باشد. حال برای مقداردهی خصوصیت اضافه شده و به طور کلی برای ارسال اطلاعات اضافه به کلاینت در زمان بارگذاری فرم، نیاز است متد RenderView را به شکل زیر بازنویسی کنید:
protected override IActionResult RenderView(RoleModel role)
{
    var model = _mapper.Map<RoleModalViewModel>(role);
    model.PermissionList = ReadPermissionList();

    return PartialView(ViewName, model);
}
به عنوان مثال در اینجا از AutoMapper برای وهله سازی از RoleModalViewModel و نگاشت خصوصیات RoleModel، استفاده شده است. به این ترتیب خروجی نهایی در حالت ویرایش به شکل زیر می‌باشد:



برای مدیریت سناریوهای Master-Detail به مانند قسمت مدیریت دسترسی‌ها در تب Permissions فرم بالا، امکاناتی در زیرساخت تعبیه شده است ولی پیاده‌سازی آن را به عنوان یک تمرین و با توجه به سری مطالب «Editing Variable Length Reorderable Collections in ASP.NET MVC» به شما واگذار می‌کنم.


نکته تکمیلی: برای ارسال اطلاعات اضافی به ویو Index متناظر با یک موجودیت می‌توانید متد RenderIndex را به شکل زیر بازنویسی کنید:

protected override IActionResult RenderIndex(IPagedQueryResult<RoleReadModel> model)
{
    var indexModel = new RoleIndexViewModel
    {
        Items = model.Items,
        TotalCount = model.TotalCount,
        Permissions = ReadPermissionList()
    };

    return Request.IsAjaxRequest()
        ? (IActionResult) PartialView(indexModel)
        : View(indexModel);
}

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

public class RoleIndexViewModel : PagedQueryResult<RoleReadModel>
{
    public IReadOnlyList<LookupItem> Permissions { get; set; }
}


فرآیند بارگذاری یک پارشال‌ویو در مودال

به عنوان مثال برای استفاده از مودال‌های بوت استرپ، ایده کار به این شکل است که یک مودال را به شکل زیر در فایل Layout قرار دهید:

<div class="modal fade" @*tabindex="-1"*@ id="main-modal" data-keyboard="true" data-backdrop="static" role="dialog" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered" role="document">
        <div class="modal-content">
            <div class="modal-body">
                Loading...
            </div>
        </div>
    </div>
</div>

سپس در زمان کلیک بروی یک دکمه Ajaxای، ابتدا main-modal را نمایش داده و بعد از دریافت پارشال‌ویو از سرور، آن را با محتوای modal-content جایگزین می‌کنیم. به همین دلیل Tag Halperهای مطرح شده در مطلب جاری، callbackهای failure/complete/success متناظر با unobtrusive-ajax را نیز مقداردهی می‌کنند. برای این منظور نیاز است تا متدهای جاوااسکریپتی زیر نیز در سطح شیء window تعریف شده باشند:‌‌

    /*----------------------------------  asp-modal-link ---------------------------*/
    window.handleModalLinkLoaded = function (data, status, xhr) {
        prepareForm('#main-modal.modal form');
    };
    window.handleModalLinkFailed = function (xhr, status, error) {
        //....
    };
    /*----------------------------------  asp-modal-form ---------------------------*/
    window.handleModalFormBegin = function (xhr) {
        $('#main-modal a').addClass('disabled');
        $('#main-modal button').attr('disabled', 'disabled');
    };
    window.handleModalFormComplete = function (xhr, status) {
        $('#main-modal a').removeClass('disabled');
        $('#main-modal button').removeAttr('disabled');
    };
    window.handleModalFormSucceeded = function (data, status, xhr) {
        if (xhr.getResponseHeader('Content-Type') === 'text/html; charset=utf-8') {
            prepareForm('#main-modal.modal form');
        } else {
            hideMainModal();
        }
    };
    window.handleModalFormFailed = function (xhr, status, error, formId) {
        if (xhr.status === 400) {
            handleBadRequest(xhr, formId);
        }
    };


برای بررسی بیشتر، پیشنهاد می‌کنم پروژه DNTFrameworkCore.TestWebApp موجود در مخزن این زیرساخت را بازبینی کنید. 
مطالب
Bundling and Minifying Inline Css and Js
افزایش Performance یک سایت از موارد بسیار مهمی است که هر برنامه نویسی باید به آن توجه ویژه‌ای داشته باشد و در این زمینه لینک Best Practices می‌تواند بسیار کاربردی باشد. 
حال در این پست قصد داریم Style‌ها و Js‌های نوشته شده در سطح هر View را با Bundling and Minifying در Asp.Net MVC 4 بهینه نماییم .
در ابتدا با استفاده از  Nuget پکیج BundleMinifyInlineJsCss  را به پروژه MVC خود مطابق شکل زیر اضافه می‌نماییم .  

در مرحله بعدی کلاسی را با نام BundleMinifyingInlineCssJSAttribute ایجاد کرده و با ارث بردن از کلاس ActionFilterAttribute متد OnActionExecuting را override می‌نماییم . اکنون کلاس ما به شکل زیر است :
using System.Web;
using System.Web.Mvc;
using BundlingAndMinifyingInlineCssJs.ResponseFilters;
namespace UILayer.Filters
{
    public class BundleMinifyingInlineCssJSAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            filterContext.HttpContext.Response.Filter = new BundleAndMinifyResponseFilter(filterContext.HttpContext.Response.Filter);
        }
    }
}
و برای استفاده می‌توانیم بالای کنترلر خود کد زیر را اضافه نماییم .
[BundleMinifyingInlineCssJS]
    public partial class HomeController : Controller
    {
}
در ادامه پروژه را اجرا می‌کنیم. Style‌ها و Js‌های نوشته شده در سطح هر View به صورت زیر در می‌آیند.

نظرات مطالب
مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
کوئری رو که در پاسخ سؤال قبلی شما نوشته شد، یکبار اجرا کنید.
تمام رکوردها رو واکشی نمی‌کنه. فقط از یک آی دی خاص شروع می‌کنه و مواردی رو که ReplyId اون‌ها با id‌ها یکی است انتخاب می‌کنه. یعنی فقط یک سری زیر خانواده خاص.

مسایل و پروژه‌های شخصی خودتون رو هم در انجمن‌ها پیگیری کنید.
نظرات مطالب
EF Code First #12
خیر. زمانیکه از EF استفاده می‌کنید، DAL همان EF است و تنظیمات آن.
پیشنهاد من این است که یکبار پروژه مربوطه رو دریافت کنید و بعد پروژه‌های DataLayer و همچنین ServiceLayer و غیره آن‌را بررسی کنید.
در این مثال‌ها فقط از اینترفیس‌های ServiceLayer (و نه DataLayer مجزای آن) به کمک ترزیق وابستگی‌ها در لایه نمایشی استفاده شده.