مطالب
استفاده از پلاگین DataTables کتابخانه jQuery در برنامه‌های ASP.NET Core
datatable js، کتابخانه‌ای جهت ساخت جداول است و نسبت به رقیب اصلی خودش یعنی kendo telerik، از سادگی بیشتری برخوردار هست و امکانات خوبی هم دارد.

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

در ابتدا به بررسی کد‌ها و تغییرات سمت فرانت‌اند و صفحه‌ی cshtml می‌پردازیم:
1- تابع Ajax ای که وظیفه‌ی دریافت اطلاعات را دارد، به کل پاک کنید. چون Ajax به صورت یک آبجکت، به درون خود دیتاتیبل منتقل خواهد شد.
2- در صفحه خود، کد زیر را قرار دهید (جهت جلوگیری از 400 bad request) که این کار فقط برای هندلر‌های razor page و یا controller نیاز است و اگر از API استفاده میکنید، مسلما نیازی به این مدل تنظیمات نیست.
@Html.AntiForgeryToken()
3- سپس کد زیر را به startup خود اضافه کنید (در قسمتی که دارید اینترفیس‌ها را ثبت میکنید):
//Post in Ajax
services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
4- حالا نوبت کانفیگ‌های دیتاتیبل هست:
function initDataTables() {
        table.destroy();
        table = $("#tblJs").DataTable({
          processing: true,
          serverSide: true,
          filter: true,
          ajax: {
            url: '@Url.Page("yourPage","yourHandler")',
            beforeSend: function (xhr) {
              xhr.setRequestHeader("XSRF-TOKEN",
                $('input:hidden[name="__RequestVerificationToken"]').val());
            },
            type: "POST",
            datatype: "json"
          },
          language: {
            url: "/Persian.json"
          },
          responsive: true,
          select: true,

          columns: scheme,
          select: true,
        });
      }
5-کد بالا، کل تابعی را نشان میدهد که وظیفه‌ی ساخت دیتاتیبل را دارد؛ ولی شما تنها نیاز دارید قسمت زیر را اضافه کنید:
processing: true,
          serverSide: true,
          filter: true,
          ajax: {
            url: '@Url.Page("yourPage","yourHandler  ")',
            beforeSend: function (xhr) {
              xhr.setRequestHeader("XSRF-TOKEN",
                $('input:hidden[name="__RequestVerificationToken"]').val());
            },
            type: "POST",
            datatype: "json"
          },

حالا باید کد‌های سمت سرور را بنویسیم. برای این منظور باید ابتدا مقادیری را که دیتاتیبل برای ما ارسال میکند، از ریکوئست دریافت کنیم.
6- کل دیتایی که دیتا تیبل برای ما میفرستد، به مدل زیر خلاصه میشود:
public class FiltersFromRequestDataTable
    {
        public string length { get; set; }
        public string start { get; set; }
        public string sortColumn { get; set; }
        public string sortColumnDirection { get; set; }
        public string sortColumnIndex { get; set; }
        public string draw { get; set; }
        public string searchValue { get; set; }
        public int pageSize { get; set; }
        public int skip { get; set; }
    }
نکته‌ی مهم این است که پراپرتی‌ها باید با اسم کوچک به سمت فرانت‌اند ارسال شوند.
* (من از razor page  استفاده میکنم؛ ولی مسلما در controller هم به همین شکل و راحت‌تر خواهد بود) 
7- سپس داده‌های ارسال شده‌ی توسط دیتاتیبل، به سمت سرور را با استفاده از متد زیر دریافت میکنیم:
 public static void GetDataFromRequest(this HttpRequest Request, out FiltersFromRequestDataTable filtersFromRequest)
        {
            //TODO: Make Strings Safe String
            filtersFromRequest = new();

            filtersFromRequest.draw = Request.Form["draw"].FirstOrDefault();
            filtersFromRequest.start = Request.Form["start"].FirstOrDefault();
            filtersFromRequest.length = Request.Form["length"].FirstOrDefault();
            filtersFromRequest.sortColumn = Request.Form["columns[" + Request.Form["order[0][column]"].FirstOrDefault() + "][name]"].FirstOrDefault();
            filtersFromRequest.sortColumnDirection = Request.Form["order[0][dir]"].FirstOrDefault();
            filtersFromRequest.searchValue = Request.Form["search[value]"].FirstOrDefault();
            filtersFromRequest.pageSize = filtersFromRequest.length != null ? Convert.ToInt32(filtersFromRequest.length) : 0;
            filtersFromRequest.skip = filtersFromRequest.start != null ? Convert.ToInt32(filtersFromRequest.start) : 0;
            filtersFromRequest.sortColumnIndex = Request.Form["order[0][column]"].FirstOrDefault();

            filtersFromRequest.searchValue = filtersFromRequest.searchValue?.ToLower();
        }
8- نحوه‌ی استفاده از این متد در handler یا action مورد نظر:
Request.GetDataFromRequest(out FiltersFromRequestDataTable filtersFromRequest);

9- با استفاده از متد زیر، مقادیر مورد نیاز دیتاتیبل را به آن ارسال می‌کنیم:
 public static PaginationDataTableResult<T> ToDataTableJs<T>(this IEnumerable<T> source, FiltersFromRequestDataTable filtersFromRequest)
        {
            int recordsTotal = source.Count();
            CofingPaging(ref filtersFromRequest, recordsTotal);
            var result = new PaginationDataTableResult<T>()
            {
                draw = filtersFromRequest.draw,
                recordsFiltered = recordsTotal,
                recordsTotal = recordsTotal,
                data = source.OrderByIndex(filtersFromRequest).Skip(filtersFromRequest.skip).Take(filtersFromRequest.pageSize).ToList()
            };

            return result;
        }

        private static void CofingPaging(ref FiltersFromRequestDataTable filtersFromRequest, int recordsTotal)
        {
            if (filtersFromRequest.pageSize == -1)
            {
                filtersFromRequest.pageSize = recordsTotal;
                filtersFromRequest.skip = 0;
            }
        }
private static IEnumerable<T> OrderByIndex<T>(this IEnumerable<T> source, FiltersFromRequestDataTable filtersFromRequest)
        {
            var props = typeof(T).GetProperties();
            string propertyName = "";
            for (int i = 0; i < props.Length; i++)
            {
                if (i.ToString() == filtersFromRequest.sortColumnIndex)
                    propertyName = props[i].Name;
            }

            System.Reflection.PropertyInfo propByName = typeof(T).GetProperty(propertyName);
            if (propByName is not null)
            {
                if (filtersFromRequest.sortColumnDirection == "desc")
                    source = source.OrderByDescending(x => propByName.GetValue(x, null));
                else
                    source = source.OrderBy(x => propByName.GetValue(x, null));
            }

            return source;
        }
که باهر دیتاتایپی کار می‌کند و خودش به صورت خودکار، عملیات مرتب‌سازی را انجام می‌دهد (ابدایی خودم)
10- نحوه استفاده در هندلر یا اکشن:
var result =  query.ToDataTableJs(filtersFromRequest);
return new JsonResult(result);
یک نکته: پراپرتی‌های شما باید باحروف کوچک باشد وگرنه در سمت جاوااسکریپت، خطای undefined را مشاهده خواهید کرد. در این حالت باید پراپرتی‌ها را با حروف کوچک شروع کنید؛ ولی اگر دارید با کتابخانه‌ی newtonSoft  و jsonCovert سریالایز میکنید، میتوانید از این attribute بالای پراپرتی‌ها استفاده کنید: [JsonProperty("name")]
درکل باید یک iqueryrable را آماده و به متد ToDataTableJs ارسال کنید.

- برای سرچ هم در column‌ها هم  میتوانید به شکل زیر عمل کنید.
ابتدا دو متد زیر را به یک کلاس static اضافه کنید:
 public static IEnumerable<TSource> WhereSearchValue<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
        {
            return source.Where(predicate);
        }
        public static bool ContainsSearchValue(this string source, string toCheck)
        {
            return source != null && toCheck != null && source.IndexOf(toCheck, StringComparison.OrdinalIgnoreCase) >= 0;
        }
بعد به این شکل از آن‌ها بین ایجاد iqueryrable  و جایی که متد todatatableJs فراخوانی می‌شود، استفاده کنید:
if (!string.IsNullOrEmpty(filtersFromRequest.searchValue))
query = query.WhereSearchValue(x => x.title.ContainsSearchValue(filtersFromRequest.searchValue) || x.id.ToString().ContainsSearchValue(filtersFromRequest.searchValue)).AsQueryable();
برای افزایش کارآیی بهتر است مدل اصلی را به ویوو ارسال نکنید و از همان اول یک IQueryrable از جنس ویوومدل یا dto داشته باشید و این سرچ را هم بر روی همان انجام دهید.

کد کامل هندلر یا action  (که ترکیب کد‌های بالا هستش):
 public JsonResult OnPostList()
        {
            Request.GetDataFromRequest(out FiltersFromRequestDataTable filtersFromRequest);
            var query = _Repo.GetQueryable().Select(x => new VmAdminList()
            {
                title = x.Title,
            }
            );

            if (!string.IsNullOrEmpty(filtersFromRequest.searchValue))
                query = query.WhereSearchValue(x => x.title.ContainsSearchValue(filtersFromRequest.searchValue) || x.id.ToString().ContainsSearchValue(filtersFromRequest.searchValue)).AsQueryable();

            var result =  query.ToDataTableJs(filtersFromRequest);
            return new JsonResult(result);
        }

چند نکته:
1- ممکن‌است که بخواهید یکسری فیلتر را بجز مقادیر پیش فرض به سمت سرور ارسال کنید. برای اینکار کد زیر را به قسمت Ajax  فرانت‌اند اضافه کنید:
 data: function (d) {
                    d.parentId = parentID;
                    d.StartDateTime= StartDateTime;
                },
 و آن‌ها را به این شکل در سمت سرور دریافت کنید:
if (!int.TryParse(Request.Form["parentId"].FirstOrDefault(), out int parentId))
                throw new NullReferenceException();
2- در کانفیگ‌های Ajax مربوط به دیتاتیبل، دیگر کلید Success را نداریم؛ ولی به این شکل میتوانید این قسمت را شبیه سازی کنید:
 dataSrc: function (json) {
                    $("#count").val(json.data.length);
                    var sum = 0;
                    json.data.forEach(function (item) {
                        if (!isNullOrEmpty(item.credit))
                            sum += parseInt(item.credit);
                    })
                    $("#sum").val(separate(sum));

                    return json.data;
                }
که return آن الزامی هست؛ وگرنه به خطا میخورید.

تا به اینجا کار کاملا تمام شده؛ ولی من برای داینامیک کردن schema و column‌ها هم کلاسی را نوشته‌ام که فکر میکنم کار را راحت‌تر کند. چون شما برای تعداد ستون‌ها باید یک آبجکت را به شکل زیر تعریف کنید:
columns: [
        { data: 'name' },
        { data: 'position' },
        { data: 'salary' },
        { data: 'office' }
    ]
در اینجا اگر کلید‌ها و یا ستون‌ها (<th>) جابجا باشند، خطا می‌دهد و توسعه را بعدا سخت می‌کند؛ چون بعد هر بار تغییر، باید دستی این آبجکت‌ها و ستون‌ها را هم جابجا کنید. ولی با استفاده از کد‌های زیر، خودش به صورت داینامیک تولید می‌شود. کدزیر این کار رو انجام می‌دهد:
public class JsDataTblGeneretaor<T>
    {
        public readonly DataTableSchemaResult DataTableSchemaResult = new();
        public JsDataTblGeneretaor<T> CreateTableSchema()
        {
            var props = typeof(T).GetProperties();

            foreach (var prop in props)
            {
                DataTableSchemaResult.SchemaResult.Add(new()
                {
                    data = prop.Name,
                    sortable = (prop.PropertyType == typeof(int)) || (prop.PropertyType == typeof(bool)) || (prop.PropertyType == typeof(DateTime)),
                    width = "",
                    visible = (prop.PropertyType != typeof(DateTime))
                });
            }

            return this;
        }

        public JsDataTblGeneretaor<T> CreateTableColumns()
        {
            var props = typeof(T).GetProperties();

            CustomAttributeData displayAttribute;

            foreach (var prop in props)
            {
                string displayName = prop.Name;
                displayAttribute = prop.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == "DisplayAttribute");
                if (displayAttribute != null)
                {
                    displayName = displayAttribute.NamedArguments.FirstOrDefault().TypedValue.Value.ToString();
                }

                DataTableSchemaResult.Colums.Add(displayName);
            }

            return this;
        }

        public JsDataTblGeneretaor<T> AddCustomSchema(string data, bool? sortable = null, bool? visible = null, string width = null, string className = null)
        {
            if (DataTableSchemaResult.SchemaResult == null || !DataTableSchemaResult.SchemaResult.Any())
                return this;

            foreach (var item in DataTableSchemaResult.SchemaResult.Where(x => x.data == data))
            {
                if (sortable != null)
                    item.sortable = sortable.Value;

                if (visible != null)
                    item.visible = visible.Value;

                if (width != null)
                    item.width = width;

                if (className != null)
                    item.className = className;
            }

            return this;
        }
        public JsDataTblGeneretaor<T> SerializeSchema()
        {
            if (DataTableSchemaResult.SchemaResult == null || !DataTableSchemaResult.SchemaResult.Any())
                return this;

            DataTableSchemaResult.SerializedSchemaResult = JsonSerializer.Serialize(DataTableSchemaResult.SchemaResult);

            return this;
        }
    }
    public class DataTableSchema
    {
        public string data { get; set; }
        public bool sortable { get; set; }
        public string width { get; set; }
        public bool visible { get; set; }
        public string className { get; set; }
    }
    public class DataTableSchemaResult
    {
        public readonly List<DataTableSchema> SchemaResult = new();
        public readonly List<string> Colums = new();
        public string SerializedSchemaResult = "";
    }
متد CreateTableSchema، آبجکت هایی را که دیتاتیبل نیاز دارد، ایجاد می‌کند (فرقی ندارد مدلت شما از چه جنسی باشد) که شامل یک لیست از آبجکت‌هاست و شما میتوانید بااستفاده از متد AddCustomSchema، آن را سفارشی سازی کنید؛ مثلا بگوئید فلان کلید نمایش داده نشود و یا عرضش را مشخص کنید و ...  
متد CreateTableColumns  خیلی ساده هست و فقط یک لیست از استرینگ‌ها را برمیگرداند.
SerializeSchema هم که لیست آبجکت‌های مورد نیاز دیتاتیبل را سریالایز می‌کند.

نحوه استفاده:
در متد آغازین برنامه باید این کلاس را صدا بزنید و با هر روشی که دوست دارید، به view یا razor page ارسال کنید:
public void OnGet()
        {
            //Create Data Table Js Schema and Columns Dynamicly
            JsDataTblGeneretaor<yourVM> tblGeneretaor = new();
            DataTableSchemaResult = tblGeneretaor.CreateTableColumns().CreateTableSchema().SerializeSchema().DataTableSchemaResult;
        }

نحوه سفارشی سازی:
.AddCustomSchema("yourProperty",visible:false)
که به سمت View ارسال می‌شودو حالا نحوه‌ی استفاده کردن از scheme ساخته شده:
var scheme = JSON.parse('@Html.Raw(Model.DataTableSchemaResult.SerializedSchemaResult)')
و استفاده‌ی از آن در گزینه‌های دیتاتیبل  columns: scheme,

نحوه ساخت ستون‌ها در view:
@foreach (var col in Model.DataTableSchemaResult.Colums)
              {
                <th>@col</th>
              }

نظرات مطالب
آشنایی با مفاهیم نوع داده Enum و توسعه آن - قسمت دوم
وقتی کلاس Description در   فضای نام  System.ComponentModel وجود داره دلیلی نداره  کلاس مشابه ای تعریف کنیم.
بخاطر اینکه مصرف کننده محض نباشیم یک متد الحاقی به نام ()GetEnumList اضافه کردم که لیست اعضای یک Enum  رو برای استفاده در کمبو باکس و ... بر می‌گرودنه :
ابتدا کلاس زیر به کلاس ExtensionMethodCls اضافه می‌کنیم :
 public class EnumObject
        {
            public Enum ValueMember { get; set; }
            public int intValueMember
            {
                get { return int.Parse(ValueMember.ToString("D")); }
            }
            public string stringValueMember
            {
                get { return ValueMember.ToString(""); }
            }
            public string DisplayMember
            {
                get { return ValueMember.GetDescription(); }
            }
        }
و متد الحاقی زیر رو برای گرفتن لیست تعریف می‌کنیم:
      public static List<EnumObject> GetEnumList(this Enum enu)
        {
            List<EnumObject> li = new List<EnumObject>();
            foreach (var item in enu.GetType().GetEnumValues())
            {
                li.Add(new EnumObject { ValueMember = (Enum)item });
            }
            return li;
        }
نحوه استفاده  :
          comboBox1.DataSource = Grade.VeryGood.GetEnumList();
          comboBox1.DisplayMember = "DisplayMember";
          comboBox1.ValueMember = "ValueMember";
همون طوری که در کد بالا می‌بینید برای گرفتن لیست مجبور شدیم یکی از اعضای enum  رو انتخاب کنیم (Grade.VeryGood.GetEnumList()) شاید انتخاب یکی از اعضا و بعد درخواست لیست اعضا رو کردن کار قشنگی نباشه به همین دلیل متد زیر رو تعریف کردیم :
        public static List<EnumObject> EnumToList<T>()
        {
            Type enumType = typeof(T);
            if (enumType.BaseType != typeof(Enum))
                throw new ArgumentException("T must be of type System.Enum");

            List<EnumObject> li = new List<EnumObject>();
            foreach (var item in enumType.GetEnumValues())
            {
                li.Add(new EnumObject { ValueMember = (Enum)item });
            }
            return li;
        }
نحوه استفاده :
    comboBox1.DataSource =ExtensionMethodCls.EnumToList<Grade>();
          comboBox1.DisplayMember = "DisplayMember";
          comboBox1.ValueMember = "ValueMember";
کد کامل :
 public static class ExtensionMethodCls
    {
        public class EnumObject
        {
            public Enum ValueMember { get; set; }
            public int intValueMember
            {
                get { return int.Parse(ValueMember.ToString("D")); }
            }
            public string stringValueMember
            {
                get { return ValueMember.ToString(""); }
            }
            public string DisplayMember
            {
                get { return ValueMember.GetDescription(); }
            }
        }
 
    

        public static List<EnumObject> EnumToList<T>()
        {
            Type enumType = typeof(T);
            if (enumType.BaseType != typeof(Enum))
                throw new ArgumentException("T must be of type System.Enum");

            List<EnumObject> li = new List<EnumObject>();
            foreach (var item in enumType.GetEnumValues())
            {
                li.Add(new EnumObject { ValueMember = (Enum)item });
            }
            return li;
        }

        public static List<EnumObject> GetEnumList(this Enum enu)
        {
            List<EnumObject> li = new List<EnumObject>();
            foreach (var item in enu.GetType().GetEnumValues())
            {
                li.Add(new EnumObject { ValueMember = (Enum)item });
            }
            return li;
        }

        public static string GetDescription(this Enum enu)
        {

            Type type = enu.GetType();

            MemberInfo[] memInfo = type.GetMember(enu.ToString());

            if (memInfo != null && memInfo.Length > 0)
            {

                object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
                    return ((DescriptionAttribute)attrs[0]).Description;
            }

            return enu.ToString();

        }
    }
نظرات مطالب
EF Code First #8
سلام در قسمت Self Referencing Entity اگر بخواهیم کلید خارجی نام دیگری داشته باشد طبق گفته شما که عمل کردم خطای Sequence contains no elements را میدهد.
using System.Collections.Generic;
 
namespace EF_Sample04.Models
{
    public class Employee
    {
        public int Id { set; get; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
 
        public int? ManagerID { get; set; }
        public virtual Employee Manager { get; set; }       
    }
}
using EF_Sample04.Models;
using System.Data.Entity.ModelConfiguration; 
namespace EF_Sample04.Mappings
{
    public class EmployeeConfig : EntityTypeConfiguration<Employee>
    {
        public EmployeeConfig()
        {
            this.HasOptional(x => x.Manager)
                .WithMany()
                .HasForeignKey(x => x.ManagerID)
                .WillCascadeOnDelete(false);
        }
    }
}
مطالب
Functional Programming یا برنامه نویسی تابعی - قسمت دوم – مثال‌ها
در قسمت قبلی این مقاله، با مفاهیم تئوری برنامه نویسی تابعی آشنا شدیم. در این مطلب قصد دارم بیشتر وارد کد نویسی شویم و الگوها و ایده‌های پیاده سازی برنامه نویسی تابعی را در #C مورد بررسی قرار دهیم.


Immutable Types

هنگام ایجاد یک Type جدید باید سعی کنیم دیتای داخلی Type را تا حد ممکن Immutable کنیم. حتی اگر نیاز داریم یک شیء را برگردانیم، بهتر است که یک instance جدید را برگردانیم، نه اینکه همان شیء موجود را تغییر دهیم. نتیحه این کار نهایتا به شفافیت بیشتر و Thread-Safe بودن منجر خواهد شد.
مثال:
public class Rectangle
{
    public int Length { get; set; }
    public int Height { get; set; }

    public void Grow(int length, int height)
    {
        Length += length;
        Height += height;
    }
}

Rectangle r = new Rectangle();
r.Length = 5;
r.Height = 10;
r.Grow(10, 10);// r.Length is 15, r.Height is 20, same instance of r
در این مثال، Property های کلاس، از بیرون قابل Set شدن می‌باشند و کسی که این کلاس را فراخوانی میکند، هیچ ایده‌ای را درباره‌ی مقادیر قابل قبول آن‌ها ندارد. بعد از تغییر بهتر است وظیفه‌ی ایجاد آبجکت خروجی به عهده تابع باشد، تا از شرایط ناخواسته جلوگیری شود:
// After
public class ImmutableRectangle
{
    int Length { get; }
    int Height { get; }

    public ImmutableRectangle(int length, int height)
    {
        Length = length;
        Height = height;
    }

    public ImmutableRectangle Grow(int length, int height) =>
          new ImmutableRectangle(Length + length, Height + height);
}

ImmutableRectangle r = new ImmutableRectangle(5, 10);
r = r.Grow(10, 10);// r.Length is 15, r.Height is 20, is a new instance of r
با این تغییر در ساختار کد، کسی که یک شیء از کلاس ImmutableRectangle را ایجاد میکند، باید مقادیر را وارد کند و مقادیر Property ها به صورت فقط خواندنی از بیرون کلاس در دسترس هستند. همچنین در متد Grow، یک شیء جدید از کلاس برگردانده می‌شود که هیچ ارتباطی با کلاس فعلی ندارد.


استفاده از Expression بجای Statement

یکی از موارد با اهمیت در سبک کد نویسی تابعی را در مثال زیر ببینید:
public static void Main()
{
    Console.WriteLine(GetSalutation(DateTime.Now.Hour));
}

// imparitive, mutates state to produce a result
/*public static string GetSalutation(int hour)
{
    string salutation; // placeholder value

    if (hour < 12)
        salutation = "Good Morning";
    else
        salutation = "Good Afternoon";

    return salutation; // return mutated variable
}*/

public static string GetSalutation(int hour) => hour < 12 ? "Good Morning" : "Good Afternoon";
به خط‌های کامنت شده دقت کنید؛ می‌بینیم که یک متغیر، تعریف شده که نگه دارنده‌ای برای خروجی خواهد بود. در واقع به اصطلاح آن را mutate می‌کند؛ در صورتیکه نیازی به آن نیست. ما می‌توانیم این کد را به صورت یک عبارت (Expression) در آوریم که خوانایی بیشتری دارد و کوتاه‌تر است.


استفاده از High-Order Function ها برای ایجاد کارایی بیشتر

در قسمت قبلی درباره توابع HOF صحبت کردیم. به طور خلاصه توابعی که یک تابع را به عنوان ورودی میگیرند و یک تابع را به عنوان خروجی برمی‌گردانند. به مثال زیر توجه کنید:
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    int count = 0;

    foreach (TSource element in source)
    {
        checked
        {
            if (predicate(element))
            {
                count++;
            }
        }
    }

    return count;
}
این قطعه کد، مربوط به متد Count کتابخانه‌ی Linq می‌باشد. در واقع این متد تعدادی از چیز‌ها را تحت شرایط خاصی می‌شمارد. ما دو راهکار داریم، برای هر شرایط خاص، پیاده سازی نحوه‌ی شمردن را انجام دهیم و یا یک تابع بنویسیم که شرط شمردن را به عنوان ورودی دریافت کند و تعدادی را برگرداند.


ترکیب توابع

ترکیب توابع به عمل پیوند دادن چند تابع ساده، برای ایجاد توابعی پیچیده گفته می‌شود. دقیقا مانند عملی که در ریاضیات انجام می‌شود. خروجی هر تابع به عنوان ورودی تابع بعدی مورد استفاده قرار میگیرد و در آخر ما خروجی آخرین فراخوانی را به عنوان نتیجه دریافت میکنیم. ما میتوانیم در #C به روش برنامه نویسی تابعی، توابع را با یکدیگر ترکیب کنیم. به مثال زیر توجه کنید:
public static class Extensions
{
    public static Func<T, TReturn2> Compose<T, TReturn1, TReturn2>(this Func<TReturn1, TReturn2> func1, Func<T, TReturn1> func2)
    {
        return x => func1(func2(x));
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        Func<int, int> square = (x) => x * x;
        Func<int, int> negate = x => x * -1;
        Func<int, string> toString = s => s.ToString();
        Func<int, string> squareNegateThenToString = toString.Compose(negate).Compose(square);
        Console.WriteLine(squareNegateThenToString(2));
    }
}
در مثال بالا ما سه تابع جدا داریم که میخواهیم نتیجه‌ی آن‌ها را به صورت پشت سر هم داشته باشیم. ما میتوانستیم هر کدام از این توابع را به صورت تو در تو بنویسیم؛ ولی خوانایی آن به شدت کاهش خواهد یافت. بنابراین ما از یک Extension Method استفاده کردیم.


Chaining / Pipe-Lining و اکستنشن‌ها

یکی از روش‌های مهم در سبک برنامه نویسی تابعی، فراخوانی متد‌ها به صورت زنجیره‌ای و پاس دادن خروجی یک متد به متد بعدی، به عنوان ورودی است. به عنوان مثال کلاس String Builder یک مثال خوب از این نوع پیاده سازی است. کلاس StringBuilder از پترن Fluent Builder استفاده می‌کند. ما می‌توانیم با اکستنشن متد هم به همین نتیجه برسیم. نکته مهم در مورد کلاس StringBuilder این است که این کلاس، شیء string را mutate نمیکند؛ به این معنا که هر متد، تغییری در object ورودی نمی‌دهد و یک خروجی جدید را بر می‌گرداند.
string str = new StringBuilder()
  .Append("Hello ")
  .Append("World ")
  .ToString()
  .TrimEnd()
  .ToUpper();
در این مثال  ما کلاس StringBuilder را توسط یک اکستنشن متد توسعه داده‌ایم:
public static class Extensions
{
    public static StringBuilder AppendWhen(this StringBuilder sb, string value, bool predicate) => predicate ? sb.Append(value) : sb;
}

public class Program
{
    public static void Main(string[] args)
    {
        // Extends the StringBuilder class to accept a predicate
        string htmlButton = new StringBuilder().Append("<button").AppendWhen(" disabled", false).Append(">Click me</button>").ToString();
    }
}


نوع‌های اضافی درست نکنید ، به جای آن از کلمه‌ی کلیدی yield استفاده کنید!

گاهی ما نیاز داریم لیستی از آیتم‌ها را به عنوان خروجی یک متد برگردانیم. اولین انتخاب معمولا ایجاد یک شیء از جنس List یا به طور کلی‌تر Collection و سپس استفاده از آن به عنوان نوع خروجی است:
public static void Main()
{
    int[] a = { 1, 2, 3, 4, 5 };

    foreach (int n in GreaterThan(a, 3))
    {
        Console.WriteLine(n);
    }
}


/*public static IEnumerable<int> GreaterThan(int[] arr, int gt)
{
    List<int> temp = new List<int>();
    foreach (int n in arr)
    {
        if (n > gt) temp.Add(n);
    }
    return temp;
}*/

public static IEnumerable<int> GreaterThan(int[] arr, int gt)
{
    foreach (int n in arr)
    {
        if (n > gt) yield return n;
    }
}
همانطور که مشاهده میکنید در مثال اول، ما از یک لیست موقت استفاده کرد‌ه‌ایم تا آیتم‌ها را نگه دارد. اما میتوانیم از این مورد با استفاده از کلمه کلیدی yield اجتناب کنیم. این الگوی iterate بر روی آبجکت‌ها در برنامه نویسی تابعی، خیلی به چشم میخورد.


برنامه نویسی declarative به جای imperative با استفاده از Linq

در قسمت قبلی به طور کلی درباره برنامه نویسی Imperative صحبت کردیم. در مثال زیر یک نمونه از تبدیل یک متد که با استایل Imperative نوشته شده به declarative را می‌بینید. شما میتوانید ببینید که چقدر کوتاه‌تر و خواناتر شده:
List<int> collection = new List<int> { 1, 2, 3, 4, 5 };

// Imparative style of programming is verbose
List<int> results = new List<int>();

foreach(var num in collection)
{
  if (num % 2 != 0) results.Add(num);
}

// Declarative is terse and beautiful
var results = collection.Where(num => num % 2 != 0);


Immutable Collection

در مورد اهمیت immutable قبلا صحبت کردیم؛ Immutable Collection ها، کالکشن‌هایی هستند که به جز زمانیکه ایجاد می‌شنود، اعضای آن‌ها نمی‌توانند تغییر کنند. زمانیکه یک آیتم به آن اضافه یا کم شود، یک لیست جدید، برگردانده خواهد شد. شما می‌توانید انواع این کالکشن‌ها را در این لینک ببینید.
به نظر میرسد که ایجاد یک کالکشن جدید میتواند سربار اضافی بر روی استفاده از حافظه داشته باشد، اما همیشه الزاما به این صورت نیست. به طور مثال اگر شما f(x)=y را داشته باشید، مقادیر x و y به احتمال زیاد یکسان هستند. در این صورت متغیر x و y، حافظه را به صورت مشترک استفاده می‌کنند. به این دلیل که هیچ کدام از آن‌ها Mutable نیستند. اگر به دنبال جزییات بیشتری هستید این مقاله به صورت خیلی جزیی‌تر در مورد نحوه پیاده سازی این نوع کالکشن‌ها صحبت میکند. اریک لپرت یک سری مقاله در مورد Immutable ها در #C دارد که میتوانید آن هار در اینجا پیدا کنید.

 

Thread-Safe Collections

اگر ما در حال نوشتن یک برنامه‌ی Concurrent / async باشیم، یکی از مشکلاتی که ممکن است گریبانگیر ما شود، race condition است. این حالت زمانی اتفاق می‌افتد که دو ترد به صورت همزمان تلاش میکنند از یک resource استفاده کنند و یا آن را تغییر دهند. برای حل این مشکل میتوانیم آبجکت‌هایی را که با آن‌ها سر و کار داریم، به صورت immutable تعریف کنیم. از دات نت فریمورک نسخه 4 به بعد  Concurrent Collection‌ها معرفی شدند. برخی از نوع‌های کاربردی آن‌ها را در لیست پایین می‌بینیم:
Collection
توضیحات
 ConcurrentDictionary 
  پیاده سازی thread safe از دیکشنری key-value 
 ConcurrentQueue 
  پیاده سازی thread safe از صف (اولین ورودی ، اولین خروجی) 
 ConcurrentStack 
  پیاده سازی thread safe از پشته (آخرین ورودی ، اولین خروجی) 
 ConcurrentBag 
  پیاده سازی thread safe از لیست نامرتب 

این کلاس‌ها در واقع همه مشکلات ما را حل نخواهند کرد؛ اما بهتر است که در ذهن خود داشته باشیم که بتوانیم به موقع و در جای درست از آن‌ها استفاده کنیم.

در این قسمت از مقاله سعی شد با روش‌های خیلی ساده، با مفاهیم اولیه برنامه نویسی تابعی درگیر شویم. در ادامه مثال‌های بیشتری از الگوهایی که میتوانند به ما کمک کنند، خواهیم داشت.   
مطالب
ارسال انواع بی نام (Anonymous) بازگشتی توسط Entity framework به توابع خارجی
فرض کنید ساختار زیر را در مدل ساخته شده به وسیله‌ی Entity framework در پروژه‌ی خود داریم .



جدول Post با جدول GroupsDetail ارتباط یک به چند و در مقابل ان جدول GroupsDetail با جدول PostGroup ارتباط چند به یک دارد . به زبان ساده ما تعدادی گروه بندی برای  مطالب داریم (در جدول PostGroup) و میتوانیم برای هر مطلب تعدادی از گروه‌ها را در جدول GroupsDetail مشخص کنیم ...
فکر میکنم همین مقدار توضیح به اندازه‌ی کافی برای درک روابط مشخص شده در مدل رابطه ای مفروض کفایت کند.
حالا فرض کنید ما میخواهیم لیستی از عنوان مطالب موجود به همراه [نام] گروه‌های هر مطلب را داشته باشیم .
توجه شما رو به قطعه کد ساده‌ی زیر جلب میکنم:
var context = new Models.EntitiesConnection();
var query = context.Posts.Select(pst => new {
             id = pst.id, 
             Title = pst.Title, 
             GNames = pst.GroupsDetails.Select(grd => new { Name = grd.PostGroup.name }) 
             })
             .OrderByDescending(c => c.id)
             .ToList();
همانطور که شاهد هستید در قطعه کد بالا توسط خواص راهبری (Navigation Properties ) به صورت مستقیم نام گروه‌های ثبت شده برای هر مطلب در جدول GroupsDetail را از جدول  PostGroup استخراج کردیم . خب تا اینجا مسئله ای وجود نداشت ( البته با ORM‌ها این کار بسیار سهل و اسان شده است تا جایی که در حالت عادی برای همین کار باید کلی join نویسی کرد و .... در کل خدارو شکر که از دست کارهای تکراری و خسته کننده توسط این موجودات مهربان (ORM's) نجات یافتیم )...

خب میرسیم به ادامه‌ی مطلبمون . نتیجه‌ی این query چه خواهد بود ؟ کاملا واضح است که ما تعداد دلخواهی از فیلد‌ها رو برای واکشی مشخص کردیم پس در نتیجه نوع داده ای که توسط این query بازگشت داده خواهد شد یک لیست از یک نوع بی نام میباشد ... چگونه از نتیجه‌ی بازگشتی این query در صورت ارسال اون به عنوان یک پارامتر به یک تابع استفاده کنیم ؟ اگر ما تمامی فیلد‌های جدول Post را واکشی میکریم مقدار بازگشتی یک لیست از نوع Post بود که به راحتی قابل استفاده بود مثل مثال زیر

public bool myfunc(List<Post> query)
        {
            foreach (var item in query)
            {
            .....  string title =  item.Title;
            }
...return true;
        }
.
.
.

var context = new Models.EntitiesConnection();
var queryx = context.Posts.ToList();
.... myfunc(queryx );
اما حالا با یک لیست از یک نوع بی نام  رو به رو هستیم ... چند راه مختلف برای دسترسی به این گونه از مقادیر بازگشتی داریم .
1 : استفاده از Reflection برای دسترسی به فیلدهای مشخص شده .
2 : تعریف یک مدل کامل بر اساس فیلدهای مشخص شده‌ی بازگشتی و ارسال یک لیست از نوع تعریف شده به تابع . ( به نظرم این روش خسته کنندست و زیاد شکیل نیست چون برای هر query خاص مجبوریم یک مدل کلی تعریف کنیم و این باعث میشه تعداد زیادی مدل در نهایت داشته باشیم که استفاده‌ی زیادی از اونها نمیشه عملا )
3 : استفاده از یکی از روش‌های خلاقانه‌ی تبدیل نوع Anonymous‌ها .

روش سوم روش مورد علاقه‌ی من هست و انعطاف بالایی داره و خیلی هم شیکو مجلسیه ! در ضمن این روش که قراره ذکر بشه کاربردهای فراوانی داره که این مورد فقط یکی از اونهاست
خب بریم سر اصل مطلب . به کد زیر توجه کنید :

        public static List<T> CreateGenericListFromAnonymous<T>(object obj, T example)
        {
            return (List<T>)obj;
        }
        public static IEnumerable<T> CreateEmptyAnonymousIEnumerable<T>(T example)
        {
            return new List<T>(); ;
        }

////

        public bool myfunc(object query)
        {
            var cquery = CreateGenericListFromAnonymous(query, 
                              new { 
                                       id = 0, 
                                       Title = string.Empty, 
                                       GNames = CreateEmptyAnonymousIEnumerable(new { Name = string.Empty }) 
                              });
            foreach (var item in cquery)
            {
                string title = item.Title;
                foreach (var gname in item.GNames)
                {
                    string gn = gname.Name;
                }
.
.
.
            }
            return false;
        }

.
.
.
var context = new Models.EntitiesConnection();
var query = context.Posts.Select(pst => new
                              { 
                                   id = pst.id, 
                                   Title = pst.Title,
                                   GNames = pst.GroupsDetails.Select(grd => new { Name = grd.PostGroup.name }) 
                             }).OrderByDescending(c => c.id);
            myfunc(query.ToList());
در کد بالا به صورت تو در تو از انواع بی نام استفاده شده تا مطلب براتون خوب جا بیوفته . یعنی یک نوع بی نام که یکی از فیلدهای اون یک لیست از یک نوع بی نام دیگست ... خب میبینید که خیلی راحت با دو تابع ما تبدیل انواع بی نام رو به صورت inline انجام دادیم و ازش استفاده کردیم . بدون اینکه نیاز باشه ما یک مدل مجزا ایجاد کنیم ...
تابع CreateGenericListFromAnonymous : یک object رو میگیره و اون رو به یک لیست تبدیل میکنه بر اساس نوعی که به صورت inline براش مشخص میکنیم .
تابع CreateEmptyAnonymousIEnumerable یک لیست از نوع IEnumerable رو بر اساس نوعی که به صورت inline براش مشخص کردیم بر میگردونه . دلیل اینکه من در اینجا این تابع رو نوشتم این بود که ما در query یک فیلد با نام GNames داشتیم که مجموعه ای از نام گروه‌های هر مطلب بود که از نوع IEnumerable هستش . در واقع ما در اینجا نوع بی نامی داریم که یکی از فیلدهای اون یک لیست از یک نوع بی نام دیگست .  امیدوارم مطلب براتون جا افتاده باشه.
دوستان عزیز سوالی بود در قسمت نظرات مطرح کنید.  
مطالب
استفاده از دیتابیس Sqlite در الکترون (قسمت دوم)
در مقاله قبلی با یکی از کتابخانه‌های مدیریت دیتابیس sqlite آشنا شدیم و و یاد گرفتیم که چگونه یک دیتابیس جدید را بسازیم و اطلاعات را از آن دریافت کنیم. در این مقاله قصد داریم، بیشتر در مورد دستورات این کتابخانه بدانیم و بفهمیم که چگونه باید آن‌ها را به کار بست.

دستورات بدون خروجی:
یک سری از دستورات هستند که خروجی ندارند و رکوردی را باز نمی‌گردانند و برای اجرای دستوراتی چون افزودن، به روزرسانی و حذف بسیار مناسبند. اجرای  این دستورات را ما به متدی به نام run می‌سپاریم. در دفعه قبل که از این دستور استفاده کردیم، پارامتری برای تعیین کردن نداشت؛ ولی در این مقاله، دستور با پارامتر آن را اجرا می‌کنیم:
ابتدا کدهای زیر را به فایل html، برای درج رکورد جدید اضافه می‌کنیم:
First Name:<br/>
<input type="text" id="txtfname" /><br/>
Last Name:<br/>
<input type="text" id=txtlname /><br/>
Number:<br/>
<input type="tel" id="txttel" /><br/>
<button id="btnsubmit">Save</button><br/>
برای خواندن مقادیر هم از این تابع کمک می‌گیریم:
function GetValues()
{
  let fname=$("#txtfname").val();
  let lname=$("#txtlname").val()
  let tel=$("#txttel").val();
  let row=
  {
    fname:fname,
    lname:lname,
    number:tel
  };
  return row;
}
سپس تکه کد زیر را  با کمک  جی کوئری اضافه می‌کنیم:
$("#btnsubmit").click((e)=>{
  e.preventDefault();
  let row=GetValues();

  //save in db
  //get last id
  let statement==db.prepare("select id from numbers order by id desc limit 1");
  let lastRecord=statement.getAsObject({}).id;
  row.id=lastRecord++;
  let count=db.prepare("select count(*) as count from numbers order by id desc").getAsObject({}).count;
  statement.free();
  let insertCommand="insert into numbers values(?,?,?,?)";
  db.run(insertCommand,[row.id,row.fname,row.lname,String(row.number)])
  let newcount=db.prepare("select count(*) as count from numbers order by id desc").getAsObject({}).count;
  SaveChanges();

//show in the table
if(count<newcount)
{
  AddToTable(row);
}
});
});
متد GetValues  مقادیر را از input‌ها دریافت و در متغیر row نگهداری می‌کند. سپس برای درج رکورد، از آنجاکه ما فیلد id را افزایشی تعیین نکرده‌ایم، باید آخرین رکورد موجود در جدول را واکشی کنیم که برای این منظور از متد prepare کمک می‌گیریم. این متد در عوض کوئری داده شده، یک شیء statement را بر می‌گرداند که حاوی نتایج کوئری داده شده است؛ ولی هنوز به مرحله اجرا نرسیده است. برای اجرای دستور کوئری، دو متد وجود دارند که یکی step است و دیگری getAsObject می‌باشد. متد getAsObject برای زمانی خوب است که شما در حد یک رکورد خروجی دارید و می‌خواهید همان یک رکورد را به صورت شیء برگردانید؛ یعنی با "نام شی.نام خصوصیت" به آن دسترسی پیدا کنید. همانطور که می‌بینید ما درخواست فیلد id را کرده‌ایم و تنها یک رکورد را درخواست کرده‌ایم که باعث می‌شود به راحتی به فیلد id دسترسی داشته باشیم. وجود علامت {} داخل این متد به این معنی است که پارامتری برای ارسال وجود ندارد. ولی اگر قرار بود پارامتری را داشته باشیم، به این شکل می‌نوشتیم:
var statement= db.prepare("SELECT * FROM NUMBERS WHERE fname=@fname AND lname=@lname");

var result = statement .getAsObject({'@fname' :'ali', '@lname' : 'yeganeh'});
اگر دوست داشتید دوباره از این دستور کوئری بگیرید ولی با مقادیر متفاوت، می‌توانید از متد bind کمک بگیرید:
statement.bind(['hossein','yeganeh']);
متد دیگری هم برای پاکسازی پارامترها به نام reset، وجود دارد که فضای اختصاصی و گرفته شده توسط پارامترها را باز می‌گرداند.

متد step همانند متدهای next در cursor یا read در datareader عمل میکند و با هر بار صدا زدن، یک رکورد، به سمت جلو حرکت می‌کند. دریافت هر رکورد جاری توسط متد get و نوع خروجی آرایه انجام می‌شود:
while(statement.step())
{
     var rec=statement.get();
}
بعد از اینکه کارمان با آن تمام شد، برای پاکسازی حافظه از متد free استفاده می‌کنیم. در دستورات بعد، شیء statement را مستقیما مورد استفاده قرار داده‌ایم و توسط آن تعداد رکوردها را دریافت کرده‌ایم. سپس با استفاده از متد run دستور درج را داده‌ایم. اینبار این متد را به شکل متفاوتی استفاده کردیم و به آن پارامتری هم دادیم. نحوه ارائه پارامتر به این متد، باید به صورت آرایه و به ترتیب علامت‌های ؟ باشد. نهایتا با دریافت تعداد رکوردهای جاری و مقایسه با تعداد رکوردهای سابق متوجه می‌شویم که آیا رکوردی اضافه شده است یا خیر؟ در صورتی که اضافه شده است، باید رکورد جدید در جدول، توسط جی کوئری نمایش داده شود و تغییرات دیتابیس هم روی دیسک سخت ذخیره شوند. چون دیتابیس مورد استفاده به صورت in-memory یعنی مقیم در حافظه مورد استفاده قرار میگیرد، باید کل دیتابیس، بر روی دیسک سخت رونویسی شود. متد SaveChanges شامل کد زیر است که حاوی کد ارسال پیام به Main Thread یا Main Process می‌باشد تا در دیسک سخت بنویسد:
  const {ipcRenderer} = require('electron');
function SaveChanges()
{
    ipcRenderer.send("SaveToDb");
}
و برای ترد اصلی:
const {ipcMain} = require('electron');
ipcMain.on("SaveToDb", (event, arg) => {
  SaveToDb();
});

function SaveToDb()
{
  var data=db.export();
  var buffer=new Buffer(data);
  fs.writeFileSync(dbPath,buffer);
}

پی نوشت :یکی از بهترین روش‌ها این است که از همین تعداد سطرها برای id رکورد استفاده کنیم. چرا که فیلد id به عنوان کلید اصلی است و چینش فیزیکی دارد و از لحاظ کارایی بهتر است ولی به علت آموزشی بودن مطلب و آشنایی با دیگر دستورات، این شیوه را انتخاب کردیم.

ویرایش رکورد
ابتدا template string سطر جدول را به شکل زیر تغییر می‌دهیم:
function AddToTable(row)
{
  let tableBody=$("#people");
  let rowTemplate=`<tr><td>${row.fname}</td><td>${row.lname}</td><td>${row.number}</td><td><button class=
  "btn btn-success btnupdate" data-id="${row.id}" >Edit</button></td></tr>`;
  tableBody.append(rowTemplate);
}
تغییری که رخ داده است این است که یک دکمه ویرایش، به صفحه اضافه شده‌است که خصوصیت data-id آن با id رکورد پر خواهد شد.
سپس در تگ اسکریپت، در رویداد ready جی کوئری، این دستورات را اضافه می‌کنیم:
$("#people").on('click','.btnupdate',function(e)
{
  e.preventDefault();

  row.id=$(this).data("id");
  let row=GetValues();
  db.run("UPDATE NUMBERS SET FNAME=?,LNAME=?,NUMBER=? WHERE ID=?",[row.fname,row.lname,row.number,row.id]);
  SaveChanges();
  
  tr=$(this).closest("tr");
  let column=0;
  tr.find("td").each(function(index)
  {
    oldRow=$(this);
    switch(column)
    {
      case 0: //fname
        oldRow.text(row.fname);
        break;
      case 1: //lname
        oldRow.text(row.lname);
        break;
      case 2: //number
        oldRow.text(row.number);
        break;
    }
    column++;
  });
});
ابتدای id ذخیره شده در المان و مقادیر جدید را دریافت می‌کنیم. با استفاده از متد run کوئری به روزرسانی را به همراه پارامترها ارسال میکنیم و نتیجه را بر روی دیسک سخت ذخیره می‌کنیم. از اینجا به بعد نقش جی کوئری پر رنگ‌تر می‌شود و به خوبی می‌توانیم اهمیت آن را درک کنیم. سطر دکمه جاری را پیدا می‌کنیم و مقادیر جدید را ستون به ستون تغییر می‌دهیم.

خواندن و بازگردانی رکوردها

در مقاله قبلی با دستور each آشنا شدیم که یک متد غیرهمزمان بود و نتیجه هر رکورد را با یک callback به ما بازگشت میداد. در اصل این متد شامل 4 پارامتر است: پارامتر اول آن، کوئری ارسالی است. پارامتر دوم آن، پارامتر کوئری‌ها ، پارامتر سوم، تابع callback که به ازای هر رکورد اجرا می‌شود و پارامتر چهارم، تابع done می باشد. یعنی زمانی که کلیه رکوردها بازگشت داده شدند. شکل کامل آن به این صورت است:
db.each("SELECT name,age FROM users WHERE age >= $majority",
  {$majority:18},
  function(row){console.log(row.name)},
                                function(){console.log("done");}
  );


در این مقاله با متد دیگری به نام exec نیز آشنا می‌شویم که بازگردانی مقادیر در آن به صورت همزمان صورت می‌گیرد و توانایی آن را دارد که چندین دستور select را بازگردانی کند. به عنوان مثال دستور زیر را در نظر بگیرید:
SELECT ID FROM NUMBERS;SELECT FNAME,LNAME FROM NUMBERS
شکل خروجی آن به این صورت خواهد بود:
 [
         {columns: ['id'], values:[[1],[2],[3]]},
         {columns: ['fname','lname'], values:[['ali','yeganeh'],['hossein','yeganeh'],['mohammad','yeganeh']]}
    ]
پس اگر بخواهیم کد بازیابی رکورهای جدول numbers را در این پروژه با این متد بازنویسی کنیم، از کد زیر استفاده می‌کنیم:
var records=db.exec("select * from numbers");
let values=records[0].values;
let length=values.length;

for(let i=0;i<length;i++)
{
  let object=values[i];
  let row={
    id:object[0],
    fname:object[1],
    lname:object[2],
    number:object[3]
  };
  AddToTable(row);
}
در کد بالا از آنجا که تنها یک data result یا query result داریم، فقط اندیس 0 آن را می‌خوانیم و سپس تعداد آرایه‌ها را که برابر تعداد رکوردهاست، دریافت می‌کنیم و در یک حلقه، رکورد به رکورد، در جدول اضافه می‌کنیم.
نظرات مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
یک نکته‌ی تکمیلی: اعتبارسنجی خواص تو در تو

DataAnnotationsValidator ای که در این مطلب معرفی شد، کار اعتبارسنجی خواص تو در تو را انجام نمی‌دهد. برای این موارد، بسته‌ی آزمایشی به نام Microsoft.AspNetCore.Components.DataAnnotations.Validation وجود دارد که پس از نصب، روش استفاده‌ی از آن به صورت زیر است:
public class Employee
{
    [Required]
    public string FirstName { get; set; }

    [Required]
    public string LastName { get; set; }

    [ValidateComplexType]
    public Department Department { get; set; } = new Department();
}

public class Department
{
    [Required]
    public string DepartmentName { get; set; }
}
- ابتدا باید خاصیت تو در تویی که قرار است اعتبارسنجی شود با ویژگی ValidateComplexType مشخص شود.
- سپس تعریف ویژگی‌های اعتبارسنجی بر روی خواص کلاس تو در توی مورد استفاده، همانند قبل خواهد بود و تفاوتی نمی‌کند.
- در آخر جهت انجام عملیات اعتبارسنجی، بجای DataAnnotationsValidator قبلی باید از ObjectGraphDataAnnotationsValidator به صورت زیر استفاده کرد:
<EditForm Model="@Employee">
    <ObjectGraphDataAnnotationsValidator />
    <InputText Id="name" Class="form-control" @bind-Value="@Model.Department.DepartmentName"></InputText>
    <ValidationMessage For="@(() => Model.Department.DepartmentName)" />
</EditForm>
مطالب
آشنایی با Saltarelle کامپایلر قدرتمند #C به جاوااسکریپت

شاید ساده‌ترین تعریف برای  Saltarelle  این باشد که «کامپایلریست که کد‌های C# را به جاوا اسکریپت تبدیل می‌کند». محاسن زیادی را می‌توان برای اینگونه کامپایلر‌ها نام برد؛ مخصوصا در پروژه‌های سازمانی که نگهداری از کد‌های جاوا اسکریپت بسیار سخت و گاهی خارج از توان است و این شاید مهمترین عامل ظهور ابزارهای جدید از قبیل Typescript باشد.

در هر صورت اگر حوصله و وقت کافی برای تجهیز تیم نرم افزاری، به دانش یک زبان جدید مانند Typescript نباشد، استفاده از توان و دانش تیم تولید، از زبان C# ساده‌ترین راه حل است و اگر ابزاری مطمئن برای استفاده از حداکثر قدرت JavaScript همراه با امکانات نگهداری و توسعه کد‌ها وجود داشته باشد، بی شک Saltarelle یکی از بهترین‌های آنهاست.

قبلا کامپایلر هایی از این دست مانند  Script# وجود داشتند، اما فاقد همه امکانات C# بوده وعملا قدرت کامل C# در کد نویسی وجود نداشت. اما با توجه به ادعای توسعه دهندگان این کامپایلر سورس باز در استفاده‌ی حداکثری از کلیه ویژگی‌های C# 5 و با وجود Library ‌های متعدد می‌توان Saltarelle  را عملا یک کامپایلر موفق در این زمینه دانست.

برای استفاده از Saltarelle در یک برنامه وب ساده باید یک پروژه Console Application به Solution اضافه کرد و پکیج Saltarelle.Compiler را از nuget نصب نمایید. بعد از نصب این پکیج، کلیه Reference ‌ها از پروژه حدف می‌شوند و هر بار Build توسط کامپایلر Saltarelle  انجام می‌شود. البته با اولین Build، مقداری Error را خواهید دید که برای از بین بردنشان نیاز است پکیج Saltarelle.Runtime را نیز در این پروژه نصب نمایید:

PM> Install-Package Saltarelle.Compiler
PM> Install-Package Saltarelle.Runtime

در صورتیکه کماکان Build  نهایی با Error همرا بود، یکبار این پروژه را Unload  و سپس مجددا Load نمایید



UI یک پروژه وب MVC است و Client یک Console Application که پکیج‌های مورد نیاز Saltarelle  روی آن نصب شده است.

در صورتیکه پروژه را Build نماییم و نگاهی به پوشه‌ی Debug بیاندازیم، یک فایل JavaScript همنام پروژه وجود دارد:


برای اینکه بعد از هر بار Build ، فایل اسکریپت به پوشه‌ی مربوطه در پروژه UI منتقل شود کافیست کد زیر را در Post Build  پروژه Client بنویسیم: 

copy "$(TargetDir)$(TargetName).js" "$(SolutionDir)SalratelleSample.UI\Scripts"

اکنون پس از هر بار Build ، فایل اسکریپت مورد نظر در پوشه‌ی Scripts پروژه UI  آپدیت می‌شود:


در ادامه کافیست فایل اسکریپت را به layout اضافه کنیم. 

<script src="~/Scripts/SaltarelleSample.Client.js"></script>

در پوشه‌ی Saltarelle.Runtime در پکیج‌های نصب شده، یک فایل اسکریپت به نام mscorlib.min.js نیز وجود دارد که حاوی اسکریپت‌های مورد نیاز Saltarelle در هنگام اجراست. آن را به پوشه اسکریپت‌های پروژه UI کپی نمایید و سپس به Layout  اضافه کنید. 

<script src="~/Scripts/mscorlib.min.js"></script>
<script src="~/Scripts/SaltarelleSample.Client.js"></script>

حال نوبت به اضافه نمودن library‌های مورد نیازمان است. برای دسترسی به آبجکت هایی از قبیل document, window, element و غیره در جاوااسکریپت می‌توان پکیج Saltarelle.Web را در پروژه‌ی Client نصب نمود و برای دسترسی به اشیاء و فرمانهای jQuery، پکیج Salratelle.jQuery را نصب نمایید. 

> Install-Package Saltarelle.Web
> Install-Package Saltarelle.jQuery

به این library‌ها imported library می‌گویند. در واقع، در زمان کامپایل، برای این library‌ها فایل اسکریپتی تولید نمی‌شود و فقط آبجکت‌های #C هستند که که هنگام کامپایل تبدیل به کدهای ساده اسکریپت می‌شوند که اگر اسکریپت مربوط به آنها به صفحه اضافه نشده باشد، اجرای اسکریپت با خطا مواجه می‌شود.

به طور ساده‌تر وقتی از jQuery library استفاده می‌کنید هیچ فایل اسکریپت اضافه‌ای تولید نمی‌شود، اما باید اسکریپت jQuery به صفحه شما اضافه شده باشد.

<script src="~/Scripts/jquery-1.10.2.min.js"></script>

مثال ما یک اپلیکیشن ساده برای خواندن فید‌های همین سایت است. ابتدا کد‌های سمت سرور را در پروژه UI  می نویسیم.

کلاس‌های مورد نیاز ما برای این فید ریدر: 

public class Feed
    {
        public string FeedId { get; set; }
        public string Title { get; set; }
        public string Address { get; set; }

    }
    public class Item
    {
        public string Title { get; set; }
        public string Link { get; set; }
        public string Description { get; set; }
    }

و یک کلاس برای مدیریت منطق برنامه 

 public class SiteManager
    {
        private static List<Feed> _feeds;
        public static List<Feed> Feeds
        {
            get
            {
                if (_feeds == null)
                    _feeds = CreateSites();
                return _feeds;
            }
        }
        private static List<Feed> CreateSites()
        {
            return new List<Feed>() { 
                new Feed(){
                    FeedId = "1",
                    Title = "آخرین تغییرات سایت",
                    Address = "https://www.dntips.ir/rss.xml"
                },
                 new Feed(){
                    FeedId = "2",
                    Title = "مطالب سایت",
                    Address = "https://www.dntips.ir/feeds/posts"
                },
                 new Feed(){
                    FeedId = "3",
                    Title = "نظرات سایت",
                    Address = "https://www.dntips.ir/feeds/comments"
                },
                 new Feed(){
                    FeedId = "4",
                    Title = "خلاصه اشتراک ها",
                    Address = "https://www.dntips.ir/feed/news"
                },
            };
        }

        public static IEnumerable<Item> GetNews(string id)
        {
            XDocument feedXML = XDocument.Load(Feeds.Find(s=> s.FeedId == id).Address);
            var feeds = from feed in feedXML.Descendants("item")
                        select new Item
                        {
                            Title = feed.Element("title").Value,
                            Link = feed.Element("link").Value,
                            Description = feed.Element("description").Value
                        };
            return feeds;
        }

    }

کلاس SiteManager فقط یک لیست از فید‌ها دارد و متدی که با گرفتن شناسه‌ی فید ، یک لیست از آیتم‌های موجود در آن فید ایجاد می‌کند.

حال دو ApiController برای دریافت داده‌ها ایجاد می‌کنیم

public class FeedController : ApiController  
{
        // GET api/<controller>
        public IEnumerable<Feed> Get()
        {
            return SiteManager.Feeds;
        }
    }

public class ItemsController : ApiController
    {
        // GET api/<controller>/5
        public IEnumerable<Item> Get(string id)
        {
            return SiteManager.GetNews(id);
        }
    }

در View پیش‌فرض که Index از کنترلر Home  است،  یک Html ساده برای فرم  صفحه اضافه می‌کنیم 

<div>
    <div>
        <h2>Feeds</h2>
        <ul id="Feeds">
           
        </ul>
    </div>
    <div>
        <h2>Items</h2>
        <p id="FeedItems">
        </p>
    </div>
   
</div>

در المنت Feeds لیست فید‌ها را قرار می‌دهیم و در FeedItems آیتم‌های مربوط به هر فید. حال به سراغ کد‌های سمت کلاینت می‌رویم و به جای جاوا اسکریپت از Saltarelle استفاده می‌کنیم.

کلاس Program را از پروژه Client باز می‌کنیم و متد Main را به شکل زیر تغییر می‌دهیم:

static void Main()
        {
            jQuery.OnDocumentReady(() => {
                FillFeeds();
            });
        }

بعد از کامپایل شدن، کد #C شارپ بالا به صورت زیر در می‌آید: 

$SaltarelleSample_Client_$Program.$main = function() {
$(function() {
$SaltarelleSample_Client_$Program.$fillFeeds();
});
};
$SaltarelleSample_Client_$Program.$main();

و این همان متد معروف jQuery است که Saltarelle.jQuery برایمان ایجاد کرده است.

متد FillFeeds را به شکل زیر پیاده سازی می‌کنیم

private static void FillFeeds()
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/feed",
                Type = "GET",
                Success = (d,t,r) => {

                    // Fill 
                    var ul = jQuery.Select("#Feeds");
                    jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });
                }
            });
        }

آبجکت jQuery، متدی به نام Ajax دارد که یک شی از کلاس jQueryAjaxOptions را به عنوان پارامتر می‌پذیرد. این کلاس کلیه خصوصیات متد Ajax در jQuery را پیاده سازی می‌کند. نکته شیرین آن توانایی نوشتن lambda برای Delegate هاست.

خاصیت Success یک Delegate است که 3 پارامتر ورودی را می‌پذیرد.

public delegate void AjaxRequestCallback(object data, string textStatus, jQueryXmlHttpRequest request);

data همان مقداریست که api باز می‌گرداند که یک لیست از Feed هاست. برای زیبایی کار، من یک کلاس Feed در پروژه Client اضافه می‌کنم که خصوصیاتی مشترک با کلاس اصلی سمت سرور دارد و مقدار برگشی Ajax را به آن تبدیل می‌کنم.

کلاس Feed و Item

 [PreserveMemberCase()]
    public class Feed
    {
        //[ScriptName("FeedId")]
        public string FeedId;

        //[ScriptName("Title")]
        public string Title;

        //[ScriptName("Address")]
        public string Address;

    }

    [PreserveMemberCase()]
    public class Item
    {
        // [ScriptName("Title")]
        public string Title;

        // [ScriptName("Link")]
        public string Link;

        // [ScriptName("Description")]
        public string Description;
    }
Attrubute‌های زیادی در Saltarelle وجود دارند و از آنجایی که کامپایلر اسم فیلد‌ها را camelCase کامپایل می‌کند من برای جلوگیری از آن از PreserveMemberCase  بر روی هر کلاس استفاده کردم. می‌توانید اسم هر فیلد را سفارشی کامپایل نمایید. 
jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });

به ازای هر آیتمی که در شیء بازگشتی وجود دارد، با استفاد از متد each در jQuery یک li ایجاد می‌کنیم. همان طور که می‌بینید کلیه خواص، به شکل Fluent قابل اضافه شدن می‌باشد. سپس برای li یک رویداد کلیک که در صورت وقوع، متد FillData را با شناسه فید کلیک شده فراخوانی می‌کند و در آخر li را به المنت ul اضافه می‌کنیم.

برای هر کلیک هم مانند مثال بالا api را با شناسه‌ی فید مربوطه فراخوانی کرده و به ازای هر آیتم، یک سطر ایجاد می‌کنیم.

private static void FillData(string p)
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/items/" + p,
                Type = "GET",
                Success = (d, t, r) => {
                    var content = jQuery.Select("#FeedItems");
                    content.Html("");
                    foreach (var item in (List<Item>)d)
                    {
                        var row = jQuery.Select("<div>").AddClass("row").CSS("direction", "rtl");
                        var link = jQuery.Select("<a>").Attribute("href", item.Link).Text(item.Title);
                        row.Append(link);
                        content.Append(row);
                    }
                }
            });
        }
خروجی برنامه به شکل زیر است: 

در این مثال ما از Saltarelle.jQuery برای استفاده از jQuery.js استفاده نمودیم. library‌های متعددی برای Saltarelle  از قبیل  linq,angular,knockout,jQueryUI,nodeJs ایجاد شده و همچنین قابلیت‌های زیادی برای نوشتن imported library‌های سفارشی نیز وجود دارد. 

مطمئنا استفاده از چنین کامپایلرهایی راه حلی سریع برای رهایی از مشکلات متعدد کد نویسی با جاوا اسکریپت در نرم افزارهای بزرگ مقیاس است. اما مقایسه آنها با ابزارهایی از قبیل typescript احتیاج به زمان و تجربه کافی در این زمینه دارد.

نظرات مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
یک نکته‌ی تکمیلی: روش تهیه‌ی ویژگی‌های سفارشی اعتبارسنجی، در برنامه‌های Blazor

اگر ویژگی‌های پیش‌فرض مهیا، پاسخگوی اعتبارسنجی مدنظر نبودند، می‌توان یک attribute سفارشی را تهیه کرد:
using System.ComponentModel.DataAnnotations;

namespace CustomValidators
{
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
    public class EmailDomainValidator : ValidationAttribute
    {
        public string AllowedDomain { get; set; }

        protected override ValidationResult IsValid(object value, 
            ValidationContext validationContext)
        {
            string[] strings = value.ToString().Split('@');
            if (strings[1].ToUpper() == AllowedDomain.ToUpper())
            {
                return null;
            }

            return new ValidationResult($"Domain must be {AllowedDomain}",
            new[] { validationContext.MemberName });
        }
    }
}
توضیحات:
- کار با ارث بری از کلاس پایه‌ی ValidationAttribute شروع می‌شود و باید متد IsValid آن‌را بازنویسی کرد.
- اگر متد IsValid، نال برگرداند، یعنی مشکلی نیست؛ در غیراینصورت خروجی آن باید از نوع ValidationResult باشد.
- پارامتر validationContext اطلاعاتی مانند نام خاصیت در حال بررسی را ارائه می‌دهد.
- در اینجا متد ()ValidationContext.GetService نال را بر می‌گرداند؛ یعنی فعلا از تزریق وابستگی‌ها در آن پشتیبانی نمی‌شود.

و در آخر روش استفاده‌ی از آن، همانند سایر ویژگی‌های اعتبارسنجی است:
public class Employee
{
    [EmailDomainValidator(AllowedDomain = "site.com")]
    public string Email { get; set; }
}
مطالب
بررسی تفصیلی رابطه Many-to-Many در EF Code first
رابطه چند به چند در مطالب EF Code first سایت جاری، در حد تعریف نگاشت‌های آن بررسی شده، اما نیاز به جزئیات بیشتری برای کار با آن وجود دارد که در ادامه به بررسی آن‌ها خواهیم پرداخت:


1) پیش فرض‌های EF Code first در تشخیص روابط چند به چند

تشخیص اولیه روابط چند به چند، مانند یک مطلب موجود در سایت و برچسب‌های آن؛ که در این حالت یک برچسب می‌تواند به چندین مطلب مختلف اشاره کند و یا برعکس، هر مطلب می‌تواند چندین برچسب داشته باشد، نیازی به تنظیمات خاصی ندارد. همینقدر که دو طرف رابطه توسط یک ICollection به یکدیگر اشاره کنند، مابقی مسایل توسط EF Code first به صورت خودکار حل و فصل خواهند شد:
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Data.Entity.ModelConfiguration;

namespace Sample
{
    public class BlogPost
    {
        public int Id { set; get; }

        [StringLength(maximumLength: 450, MinimumLength = 1), Required]
        public string Title { set; get; }

        [MaxLength]
        public string Body { set; get; }

        public virtual ICollection<Tag> Tags { set; get; } // many-to-many

        public BlogPost()
        {
            Tags = new List<Tag>();
        }
    }

    public class Tag
    {
        public int Id { set; get; }

        [StringLength(maximumLength: 450), Required]
        public string Name { set; get; }

        public virtual ICollection<BlogPost> BlogPosts { set; get; } // many-to-many

        public Tag()
        {
            BlogPosts = new List<BlogPost>();
        }
    }

    public class MyContext : DbContext
    {
        public DbSet<BlogPost> BlogPosts { get; set; }
        public DbSet<Tag> Tags { get; set; }
    }

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

        protected override void Seed(MyContext context)
        {
            var tag1 = new Tag { Name = "Tag1" };
            context.Tags.Add(tag1);

            var post1 = new BlogPost { Title = "Title...1", Body = "Body...1" };
            context.BlogPosts.Add(post1);

            post1.Tags.Add(tag1);

            base.Seed(context);
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());

            using (var ctx = new MyContext())
            {
                var post1 = ctx.BlogPosts.Find(1);
                if (post1 != null)
                {
                    Console.WriteLine(post1.Title);
                }
            }
        }
    }
}
در این مثال، رابطه بین مطالب ارسالی در یک سایت و برچسب‌های آن به صورت many-to-many تعریف شده است و همینقدر که دو طرف رابطه توسط یک ICollection به هم اشاره می‌کنند، رابطه زیر تشکیل خواهد شد:


در اینجا تمام تنظیمات صورت گرفته بر اساس یک سری از پیش فرض‌ها است. برای مثال نام جدول واسط تشکیل شده، بر اساس تنظیم پیش فرض کنار هم قرار دادن نام دو جدول مرتبط تهیه شده است.
همچنین بهتر است بر روی نام برچسب‌ها، یک ایندکس منحصربفرد نیز تعیین شود: (^ و ^)


2) تنظیم ریز جزئیات روابط چند به چند در EF Code first

تنظیمات پیش فرض انجام شده آنچنان نیازی به تغییر ندارند و منطقی به نظر می‌رسند. اما اگر به هر دلیلی نیاز داشتید کنترل بیشتری بر روی جزئیات این مسایل داشته باشید، باید از Fluent API جهت اعمال آن‌ها استفاده کرد:
    public class TagMap : EntityTypeConfiguration<Tag>
    {
        public TagMap()
        {
            this.HasMany(x => x.BlogPosts)
                .WithMany(x => x.Tags)
                .Map(map =>
                    {
                        map.MapLeftKey("TagId");
                        map.MapRightKey("BlogPostId");
                        map.ToTable("BlogPostsJoinTags");
                    });
        }
    }

    public class MyContext : DbContext
    {
        public DbSet<BlogPost> BlogPosts { get; set; }
        public DbSet<Tag> Tags { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new TagMap());
            base.OnModelCreating(modelBuilder);
        }
    }
در اینجا توسط متد Map، نام کلیدهای تعریف شده و همچنین جدول واسط تغییر داده شده‌اند:


3) حذف اطلاعات چند به چند

برای حذف تگ‌های یک مطلب، کافی است تک تک آن‌ها را یافته و توسط متد Remove جهت حذف علامتگذاری کنیم. نهایتا با فراخوانی متد SaveChanges، حذف نهایی انجام و اعمال خواهد شد.
            using (var ctx = new MyContext())
            {
                var post1 = ctx.BlogPosts.Find(1);
                if (post1 != null)
                {
                    Console.WriteLine(post1.Title);
                    foreach (var tag in post1.Tags.ToList())
                        post1.Tags.Remove(tag);
                    ctx.SaveChanges();
                }
            }
در اینجا تنها اتفاقی که رخ می‌دهد، حذف اطلاعات ثبت شده در جدول واسط BlogPostsJoinTags است. Tag1 ثبت شده در متد Seed فوق، حذف نخواهد شد. به عبارتی اطلاعات جداول Tags و BlogPosts بدون تغییر باقی خواهند ماند. فقط یک رابطه بین آن‌ها که در جدول واسط تعریف شده است، حذف می‌گردد.

در ادامه اینبار اگر خود post1 را حذف کنیم:
                var post1 = ctx.BlogPosts.Find(1);
                if (post1 != null)
                {
                    ctx.BlogPosts.Remove(post1);
                    ctx.SaveChanges();
                }
علاوه بر حذف post1، رابطه تعریف شده آن در جدول BlogPostsJoinTags نیز حذف می‌گردد؛ اما Tag1 حذف نخواهد شد.
بنابراین دراینجا cascade delete ایی که به صورت پیش فرض وجود دارد، تنها به معنای حذف تمامی ارتباطات موجود در جدول میانی است و نه حذف کامل طرف دوم رابطه. اگر مطلبی حذف شد، فقط آن مطلب و روابط برچسب‌های متعلق به آن از جدول میانی حذف می‌شوند و نه برچسب‌های تعریف شده برای آن.
البته این تصمیم هم منطقی است. از این لحاظ که اگر قرار بود دو طرف یک رابطه چند به چند با هم حذف شوند، ممکن بود با حذف یک مطلب، کل بانک اطلاعاتی خالی شود! فرض کنید یک مطلب دارای سه برچسب است. این سه برچسب با 20 مطلب دیگر هم رابطه دارند. اکنون مطلب اول را حذف می‌کنیم. برچسب‌های متناظر آن نیز باید حذف شوند. با حذف این برچسب‌ها طرف دوم رابطه آن‌ها که چندین مطلب دیگر است نیز باید حذف شوند!


4) ویرایش و یا افزودن اطلاعات چند به چند

در مثال فوق فرض کنید که می‌خواهیم به اولین مطلب ثبت شده، تعدادی تگ جدید را اضافه کنیم:
                var post1 = ctx.BlogPosts.Find(1);
                if (post1 != null)
                {
                    var tag2 = new Tag { Name = "Tag2" };                    
                    post1.Tags.Add(tag2);
                    ctx.SaveChanges();
                }
در اینجا به صورت خودکار، ابتدا tag2 ذخیره شده و سپس ارتباط آن با post1 در جدول رابط ذخیره خواهد شد.

در مثالی دیگر اگر یک برنامه ASP.NET را درنظر بگیریم، در هربار ویرایش یک مطلب، تعدادی Tag به سرور ارسال می‌شوند. در ابتدای امر هم مشخص نیست کدامیک جدید هستند، چه تعدادی در لیست تگ‌های قبلی مطلب وجود دارند، یا اینکه کلا از لیست برچسب‌ها حذف شده‌اند:
                //نام تگ‌های دریافتی از کاربر  
                var tagsList = new[] { "Tag1", "Tag2", "Tag3" };

                //بارگذاری یک مطلب به همراه تگ‌های آن
                var post1 = ctx.BlogPosts.Include(x => x.Tags).FirstOrDefault(x => x.Id == 1);
                if (post1 != null)
                {
                    //ابتدا کلیه تگ‌های موجود را حذف خواهیم کرد
                    if (post1.Tags != null && post1.Tags.Any())
                        post1.Tags.Clear();

                    //سپس در طی فقط یک کوئری بررسی می‌کنیم کدامیک از موارد ارسالی موجود هستند
                    var listOfActualTags = ctx.Tags.Where(x => tagsList.Contains(x.Name)).ToList();
                    var listOfActualTagNames = listOfActualTags.Select(x => x.Name.ToLower()).ToList();

                    //فقط موارد جدید به تگ‌ها و ارتباطات موجود اضافه می‌شوند
                    foreach (var tag in tagsList)
                    {
                        if (!listOfActualTagNames.Contains(tag.ToLowerInvariant().Trim()))
                        {
                            ctx.Tags.Add(new Tag { Name = tag.Trim() });
                        }
                    }
                    ctx.SaveChanges(); // ثبت موارد جدید

                    //موارد قبلی هم حفظ می‌شوند
                    foreach (var item in listOfActualTags)
                    {
                        post1.Tags.Add(item);
                    }
                    ctx.SaveChanges();
                }
در این مثال فقط تعدادی رشته از کاربر دریافت شده است، بدون Id آن‌ها. ابتدا مطلب متناظر، به همراه تگ‌های آن توسط متد Include دریافت می‌شود. سپس نیاز داریم به سیستم ردیابی EF اعلام کنیم که اتفاقاتی قرار است رخ دهد. به همین جهت تمام تگ‌های مطلب یافت شده را خالی خواهیم کرد. سپس در یک کوئری، بر اساس نام تگ‌های دریافتی، معادل آن‌ها را از بانک اطلاعاتی دریافت خواهیم کرد؛ کوئری tagsList.Contains به where in در طی یک رفت و برگشت، ترجمه می‌شود:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name]
FROM [dbo].[Tags] AS [Extent1]
WHERE [Extent1].[Name] IN (N'Tag1',N'Tag2',N'Tag3')
 آن‌هایی که جدید هستند به بانک اطلاعاتی اضافه شده (بدون نیاز به تعریف قبلی آن‌ها)، آن‌هایی که در لیست قبلی برچسب‌های مطلب بوده‌اند، حفظ خواهند شد.
لازم است لیست موارد موجود را (listOfActualTags) از بانک اطلاعاتی دریافت کنیم، زیرا به این ترتیب سیستم ردیابی EF آن‌ها را به عنوان رکوردی جدید و تکراری ثبت نخواهد کرد.


5) تهیه کوئری‌های LINQ بر روی روابط چند به چند

الف) دریافت یک مطلب خاص به همراه تمام تگ‌های آن:
 ctx.BlogPosts.Where(p => p.Id == 1).Include(p => p.Tags).FirstOrDefault()
ب) دریافت کلیه مطالبی که شامل Tag1 هستند:

var posts = from p in ctx.BlogPosts
                 from t in p.Tags
                 where t.Name == "Tag1"
                 select p;
و یا :
 var posts = ctx.Tags.Where(x => x.Name == "Tag1").SelectMany(x => x.BlogPosts);