@Html.AntiForgeryToken()
//Post in Ajax services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
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, }); }
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" },
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; } }
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(); }
Request.GetDataFromRequest(out FiltersFromRequestDataTable filtersFromRequest);
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; }
var result = query.ToDataTableJs(filtersFromRequest); return new JsonResult(result);
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; }
if (!string.IsNullOrEmpty(filtersFromRequest.searchValue)) query = query.WhereSearchValue(x => x.title.ContainsSearchValue(filtersFromRequest.searchValue) || x.id.ToString().ContainsSearchValue(filtersFromRequest.searchValue)).AsQueryable();
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); }
data: function (d) { d.parentId = parentID; d.StartDateTime= StartDateTime; },
if (!int.TryParse(Request.Form["parentId"].FirstOrDefault(), out int parentId)) throw new NullReferenceException();
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; }
columns: [ { data: 'name' }, { data: 'position' }, { data: 'salary' }, { data: 'office' } ]
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 = ""; }
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)
var scheme = JSON.parse('@Html.Raw(Model.DataTableSchemaResult.SerializedSchemaResult)')
@foreach (var col in Model.DataTableSchemaResult.Colums) { <th>@col</th> }
گزارش درصد پیشرفت عملیات در اعمال غیرهمزمان
- اینترفیس جنریک IProgress واقع در فضای نام System
- کلاس جنریک Progress واقع در فضای نام System
در اینجا وهلهی از پیاده سازی اینترفیس IProgress به Task ارسال میشود. در این بین، عملیات در حال انجام با فراخوانی متد Report آن میتواند در هر زمانیکه نیاز باشد، درصد پیشرفت کار را گزارش کند.
namespace System { public interface IProgress<in T> { void Report( T value ); } }
namespace System { public class Progress<T> : IProgress<T> { public Progress(); public Progress( Action<T> handler ); protected virtual void OnReport( T value ); } }
یک مثال از گزارش درصد پیشرفت عملیات به همراه پشتیبانی از لغو آن
using System; using System.Threading; using System.Threading.Tasks; namespace Async09 { public class TestProgress { public async Task DoProcessingReportProgress() { var progress = new Progress<int>(percent => { Console.WriteLine(percent + "%"); }); var cts = new CancellationTokenSource(); // call some where cts.Cancel(); try { await doProcessing(progress, cts.Token); } catch (OperationCanceledException ex) { //todo: handle cancellations Console.WriteLine(ex); } Console.WriteLine("Done!"); } private static async Task doProcessing(IProgress<int> progress, CancellationToken ct) { await Task.Run(async () => { for (var i = 0; i != 100; ++i) { await Task.Delay(100, ct); if (progress != null) progress.Report(i); ct.ThrowIfCancellationRequested(); } }, ct); } } }
برای تدارک این وهله، از کلاس توکار Progress دات نت در متد public async Task DoProcessingReportProgress استفاده شدهاست.
این متد جنریک بوده و برای مثال نوع آن در اینجا int تعریف شدهاست. سازندهی آن میتواند یک callback را قبول کند. هر زمانیکه متد Report در متد doProcessing فراخوانی گردد، این callback در سمت کدهای استفاده کننده، فراخوانی خواهد شد. مثلا توسط مقدار آن میتوان یک Progress bar را نمایش داد.
به علاوه روش دیگری را در مورد لغو یک عملیات در اینجا ملاحظه میکنید. متد ThrowIfCancellationRequested نیز سبب خاتمهی عملیات میگردد؛ البته اگر در کدهای برنامه در جایی متد Cancel توکن، فراخوانی گردد. برای مثال یک دکمهی لغو عملیات در صفحه قرارگیرد و کار آن صرفا فراخوانی cts.Cancel باشد.
عملگرهای پرس و جوی تبدیل، توالیهایی را که از جنس <IEnumerable<T هستند، به انواع دیگر مجموعه تبدیل میکنند.
از عملگرهای پرس و جوی زیر میتوان برای تبدیل توالیها استفاده کرد :
- OfType
- Cast
- ToArray
- ToList
- ToDictionary
- ToLookup
عملگر OfType
این عملگر عناصری از توالی را که نوع آنها را مشخص میکنیم باز میگرداند.
امضاء عملگر پرس و جوی OfType به صورت زیر است :
public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source)
IEnumerable input = new object[] { "Apple", 33, "Sugar", 44, 'a', new DateTime()}; IEnumerable<string> query = input.OfType<string>(); foreach (var item in query) { Console.WriteLine(item); }
Apple Sugar
مثال :کد زیر یک ساختار سلسله مراتبی شیء گرا را نمایش میدهد:
class Ingredient { public string Name { get; set; } } class DryIngredient : Ingredient { public int Grams { get; set; } } class WetIngredient : Ingredient { public int Millilitres { get; set; } }
IEnumerable<Ingredient> input = new Ingredient[] { new DryIngredient { Name = "Flour" }, new WetIngredient { Name = "Milk" }, new WetIngredient { Name = "Water" } }; IEnumerable<WetIngredient> query = input.OfType<WetIngredient>(); foreach (WetIngredient item in query) { Console.WriteLine(item.Name); }
Milk Water
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Cast
عملگر Cast همانند عملگر OfType رفتار میکند. این عملگر یک توالی ورودی را دریافت و بر اساس نوع مشخص شده، توالی خروجی را تولید میکند. همهی عناصر توالی ورودی به نوع مشخص شده Cast میشوند. اما بر عکس عملگر OfType که عناصری را که با نوع دادهی ما سازگاری نداشت، نادیده میگرفت، این عملگر در صورت عدم موفقیت در عملیات تغییر نقش (Cast)، یک استثناء را پرتاب میکند.
IEnumerable input = new object[] { "Apple", 33, "Sugar", 44, 'a', new DateTime() }; IEnumerable<string> query = input.Cast<string>(); foreach (string item in query) { Console.WriteLine(item); }
Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'.
پیاده سازی توسط عبارتهای جستجو
کلمهی کلیدی جایگزینی برای عملگر Cast، در عبارتهای جستجو وجود ندارد.این عملگر با استفاده از متغیر Range که در مطالب قبلی این سری معرفی شد، قابل پیاده سازی میباشد.
IEnumerable input = new object[]{ "Apple", "Sugar", "Flour" }; IEnumerable<string> query = from string i in input select i; foreach (var item in query) { Console.WriteLine(item); }
عملگر ToArray
عملگر ToArray یک توالی ورودی را دریافت و یک توالی خروجی را به صورت آرایه تولید میکند. این عملگر باعث اجرای سریع پرس و جو میشود و رفتار پیش فرض LINQ را که اجرای با تاخیر میباشد، تحریف/بازنویسی (Override) میکند.
مثال: در این مثال یک توالی از نوع <IEnumerable<string به یک آرایه رشتهای تبدیل شده است (تبدیل لیست به آرایه).
IEnumerable<string> input = new List<string> { "Apple", "Sugar", "Flour" }; string[] array = input.ToArray();
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ToList
عملگر ToList همچون ToArray، اجرای با تاخیر را نادیده میگیرد. عملگر ToList همانطور که از نامش پیداست، توالی خروجی را بهصورت لیست مهیا میکند.
مثال:
IEnumerable<string> input = new[] { "Apple", "Sugar", "Flour" }; List<string> list = input.ToList();
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ToDictionary
این عملگر توالی ورودی را به یک دیکشنری جنریک تبدیل میکند (<Dictinary<TKey,TValue) .
سادهترین امضاء عملگر ToDictionary، یک عبارت Lambda میباشد. این عبارت Lambda نشان دهندهی یک تابع است که عنصر کلید(Key) را در دیکشنری، مشخص میکند.
مثال:
class Recipe { public int Id { get; set; } public string Name { get; set; } public int Rating { get; set; } } IEnumerable<Recipe> recipes = new[] { new Recipe { Id = 1, Name = "Apple Pie", Rating = 5 }, new Recipe { Id = 2, Name = "Cherry Pie", Rating = 2 }, new Recipe { Id = 3, Name = "Beef Pie", Rating = 3 } }; Dictionary<int, Recipe> dict = recipes.ToDictionary(x => x.Id); foreach (KeyValuePair<int, Recipe> item in dict) { Console.WriteLine($"Key={item.Key}, Recipe={item.Value}"); }
خروجی مثال بالا:
Key=1, Recipe=Apple Pie Key=2, Recipe=Cherry Pie Key=3, Recipe=Beef Pie
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ToLookup
این عملگر رفتاری شبیه به عملگر ToDictionary را دارد، اما به جای تولید خروجی از نوع دیکشنری، نمونهای از جنس ILookUp را ایجاد میکند.
در کد زیر خروجی ایجاد شده توسط lookup دستورالعملها (Recipes) را بر حسب امتیاز آنها گروه بندی کرده است. در این مثال کلید، بر حسب Byte میباشد.
مثال :
class Recipe { public int Id { get; set; } public string Name { get; set; } public byte Rating { get; set; } } IEnumerable<Recipe> recipes = new[] { new Recipe { Id = 1, Name = "Apple Pie", Rating = 5 }, new Recipe { Id = 1, Name = "Banana Pie", Rating = 5 }, new Recipe { Id = 2, Name = "Cherry Pie", Rating = 2 }, new Recipe { Id = 3, Name = "Beef Pie", Rating = 3 } }; ILookup<byte, Recipe> look = recipes.ToLookup(x => x.Rating); foreach (IGrouping<byte, Recipe> ratingGroup in look) { byte rating = ratingGroup.Key; Console.WriteLine($"Rating {rating}"); foreach (var recipe in ratingGroup) { Console.WriteLine($" - {recipe.Name}"); } }
Rating 5 - Apple Pie - Banana Pie Rating 2 - Cherry Pie Rating 3 - Beef Pie
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگرهای عناصر Element Operators
این عملگرها، یک توالی ورودی را دریافت و تنها یک عنصر از توالی ورودی و یا یک عنصر را به عنوان عنصر پیش فرض باز میگردانند. این نوع عملگرها توالی خروجی را تولید نمیکنند.
عملگر First
این عملگر اولین عنصر توالی را باز میگرداند.
مثال :
Ingredient[] ingredients = { new Ingredient {Name = "Sugar", Calories = 500}, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 150}, new Ingredient {Name = "Flour", Calories = 50}, new Ingredient {Name = "Butter", Calories = 500} }; Ingredient element = ingredients.First(); Console.WriteLine(element.Name);
Sugar
Ingredient[] ingredients = { new Ingredient {Name = "Sugar", Calories = 500}, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 150}, new Ingredient {Name = "Flour", Calories = 50}, new Ingredient {Name = "Butter", Calories = 500} }; Ingredient element = ingredients.First(x=>x.Calories==150); Console.WriteLine(element.Name);
Milk
Unhandled Exception: System.InvalidOperationException: Sequence contains no elements
Ingredient[] ingredients = { }; Ingredient element = ingredients.First();
Unhandled Exception: System.InvalidOperationException: Sequence contains no matching element
Ingredient[] ingredients = { new Ingredient {Name = "Sugar", Calories = 500}, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 150}, new Ingredient {Name = "Flour", Calories = 50}, new Ingredient {Name = "Butter", Calories = 500} }; Ingredient element = ingredients.First(x=>x.Calories==1500);
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر FirstOrDefault
عملگر FirstOrDefalt همانند عملگر First عمل میکند، اما با این تفاوت که به جای پرتاب یک استثناء در شرایط معرفی شده در عملگر First، یک مقدار پیش فرض را بر اساس نوع عناصر توالی باز میگرداند. در صورتیکه توالی از نوع عددی باشد، مقدار 0 و اگر عناصر توالی از انواع ارجاعی باشند، مقدار Null و برای مقادیر منطقی، ارزش False بهعنوان مقادیر پیش فرض باز گردانده میشوند.
Ingredient[] ingredients = { }; Ingredient element = ingredients.FirstOrDefault(); Console.WriteLine(element == null);
True
Ingredient[] ingredients = { new Ingredient {Name = "Sugar", Calories = 500}, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 150}, new Ingredient {Name = "Flour", Calories = 50}, new Ingredient {Name = "Butter", Calories = 500} }; Ingredient element = ingredients.FirstOrDefault(x=>x.Calories==1500); Console.WriteLine(element==null);
True
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Last
این عملگر آخرین عنصر توالی را باز میگرداند. همچون عملگر First، این عملگر نیز یک امضاء برای دریافت یک عبارت شرط یا پیش بینی دارد. این پیش بینی، آخرین عنصری را که شرط را تامین کند، باز میگرداند. باز هم مثل عملگر First، در صورتی که توالی هیچ عنصری نداشته باشد و یا عدم تامین شرط توسط عناصر توالی، استثنایی رخ خواهد داد.
Ingredient[] ingredients = { new Ingredient {Name = "Sugar", Calories = 500}, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 150}, new Ingredient {Name = "Flour", Calories = 50}, new Ingredient {Name = "Butter", Calories = 500} }; Ingredient element = ingredients.Last(x=>x.Calories==500); Console.WriteLine(element.Name);
Flour
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر LastOrDefault
این عملگر همچون عملگر FirstOrDefault عمل میکند. از بروز استثناء جلوگیری کرده و مقدار پیش فرض را به خروجی ارسال میکند.
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Single
عملگر Single ، تنها عنصر توالی ورودی را باز میگرداند.در صورتی که توالی ما بیش از یک عنصر داشته باشد و یا توالی هیچ عنصری نداشته باشد، یک استثناء رخ خواهد داد.
Unhandled Exception: System.InvalidOperationException: Sequence contains more than one matching element Unhandled Exception: System.InvalidOperationException: Sequence contains no matching element
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 } }; Ingredient element = ingredients.Single(); Console.WriteLine(element.Name);
Sugar
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Butter", Calories = 150}, new Ingredient {Name = "Milk", Calories = 500} }; Ingredient element = ingredients.Single(x => x.Calories == 150); Console.WriteLine(element.Name);
Butter
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر SingleOrDefault
عملگر SingleOrDefault همچون عملگر Single عمل میکند؛ اما با این تفاوت که اگر توالی هیچ عنصری نداشته باشد، مقدار پیش فرض نوع توالی، باز گردانده میشود و در صورتیکه هیچ عنصری شرط مشخص شده را تامین نکند، باز هم مقدار پیش فرض توالی، به جای رخ دادن استثناء باز گردانده میشود.
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 50} }; Ingredient element = ingredients.SingleOrDefault(x => x.Calories == 9999); Console.WriteLine(element==null);
True
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ElementAt
عملگر ElementAt عنصری را در یک جایگاه مشخص شدهی در توالی، باز میگرداند.
مثال: در کد زیر سومین عنصر توالی ورودی انتخاب میشود:
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 50} }; Ingredient element = ingredients.ElementAt(2); Console.WriteLine(element.Name);
Milk
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر ElementAtOrDefualt
عملگر ElementAtOrDefualt نیز همچون عملگر ElementAt کار میکند؛ اما در صورت وارد کردن اندیسی بزرگتر از اندیس مجاز توالی، دیگر یک استثناء رخ نخواهد داد و یک مقدار پیش فرض، بر اساس نوع عناصر توالی باز گردانده میشود.
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 50} }; Ingredient element = ingredients.ElementAtOrDefault(5); Console.WriteLine(element==null);
True
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر DefaultIfEmpty
عملگر DefaultIfEmpty یک توالی را دریافت کرده و به دو شکل عمل میکند:
1- اگر توالی شامل حداقل یک عنصر باشد، این توالی بدون هیچ تغییری به خروجی ارسال میشود.
2- اگر توالی هیچ عنصری نداشته باشد، توالی خروجی خالی نخواهد بود. در این حالت توالی خروجی تنها یک عضو دارد و آن هم مقدار پیش فرضی بر اساس نوع توالی میباشد.
مثال :
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 50} }; IEnumerable<Ingredient> query = ingredients.DefaultIfEmpty(); foreach (Ingredient item in query) { Console.WriteLine(item.Name); }
Sugar Egg Milk
کد زیر حالت دوم معرفی شدهی در تعریف DefaultIfEmpty را نشان میدهد.
Ingredient[] ingredients = { }; IEnumerable<Ingredient> query = ingredients.DefaultIfEmpty(); foreach (Ingredient item in query) { Console.WriteLine(item == null); }
True
پیاده سازی توسط عبارتهای جستجو
معادل این عملگر، کلمهی کلیدی جدیدی در عبارتهای جستجو وجود ندارد و ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
حتما تا به حال در وب سایتهای زیادی قسمت هایی را دیده اید که چیدمان عناصر آن به شکل زیر است:
این گونه چیدمان را حتما در منوی Start ویندوز 8 بارها دیدهاید! عناصر تشکیل دهندهی این شکل از چیدمان، میتوانند یک سری عکس باشند که تشکیل یک گالری عکس را دادهاند و یا یک سری div که محتوای پستهای یک وبلاگ را در خود جای دادهاند. چیزی که این شکل از چیدمان عناصر را نسبت به چیدمانهای معمول متمایز میکند این است که طول و عرض هر یک از این عناصر با یکدیگر متفاوت است و هدف از این گونه چیدمان آن است که این عناصر در فضایی که به آنها اختصاص داده شده است، به صورت بهینه قرار گیرند تا کمترین فضا هدر رود.
برای اعمال این شکل از چیدمان در دنیای وب افزونههای زیادی بر فراز کتاب خانهی jQuery تدارک دیده شده است که از جمله مطرحترین آنها میتوان به افزونه های Isotope ، Masonry و Gridster اشاره کرد.
افزونهی Isotope مزایایی را برای من در پی داشت و این افزونه را برای انجام کارهای خود، مناسب دیدم. نکتهی مهم اینجا است که هدف من بررسی Isotope نیست، چرا که اگر به وب سایت آن مراجعه کنید، با کوهی از مستندات مواجه میشوید که چگونه از آن در وب سایتهای معمولی استفاده کنید.
در این مقاله قصد من این است که نشان دهم چگونه از افزونهی Isotope در AngularJS استفاده کنیم؛ چگونه چیدمان آن را راست به چپ کنیم و چگونه آن را با محیطهای واکنش گرا (Responsive) سازگار کنیم.
فرض کنید در یک وب سایت قصد داریم اطلاعات یک سری مطلب خبری را از سرور، به فرمت JSON دریافت کرده و نمایش دهیم. در AngularJS شیوهی کار بدین صورت است که اطلاعاتی که به فرمت JSON هستند را با استفاده از directive ایی به نام ng-repeat پیمایش کرده و آنها را نمایش دهیم. حال اگر بخواهیم چیدمان مطالب را با استفاده از Isotope تغییر دهیم، میبینیم که هیچ چیزی نمایش داده نمیشود. دلیل آن بر میگردد به مراحل کامپایل کردن AngularJS و نامشخص بودن زمان اعمال چیدمان Isotope به عناصر است.
در AngularJS هنگامیکه با دستکاری DOM سر و کار پیدا میکنیم، معمولا باید به سراغ Directiveها رفت و یک Directive سفارشی برای کار با Isotope تعریف کرد تا با مکانیزمهای Angular سازگار باشد. خوشبختانه Directive Isotope برای Angular موجود میباشد. نکتهی مهم این است که این Directive برای نگارش 1 افزونهی Isotope نوشته شده است. البته با نگارش 2 هم کار میکند که من برای انجام کار خود نسخهی 1 را ترجیح دادم استفاده کنم.
نکتهی بعدی که باید رعایت شود این است که چیدمان عناصر باید از راست به چپ شوند. خوشبختانه این کار در نسخهی 1 Isotope با تغییر کوچکی در سورس Isotope و تغییر یک تابع انجام میشود. گویا نسخهی دوم امکان پیش فرضی را برای این کار دارد، اما نتوانستم آن را به خوبی پیاده سازی کنم و به همین دلیل ترجیح دادم از همان نسخهی اول استفاده کنم.
برای اینکه در هنگام جابه جا شدن عناصر، انیمیشنها نیز از راست به چپ انجام شوند، باید cssهای زیر را نیز اعمال نمود:
.isotope .isotope-item { -webkit-transition-property: right, top, -webkit-transform, opacity; -moz-transition-property: right, top, -moz-transform, opacity; -ms-transition-property: right, top, -ms-transform, opacity; -o-transition-property: right, top, -o-transform, opacity; transition-property: right, top, transform, opacity; }
Responsive بودن این عناصر مسئلهی دیگری است که باید حل گردد. امروزه اکثر فریم ورکهای مطرح css، واکنشگرا نیز هستند و برای پشتیبانی از سایزهای متفاوت صفحه نمایش، تدابیری در نظر گرفتهاند. اساس کار واکنش گرا بودن این فریم ورکها در تعیین ابعاد عناصر، بیان ابعاد به صورت درصدی است. مثلا فلان عرض div برابر 50% باشد بدین معناست که همیشه عرض این div نصف عرض عنصر والد آن باشد.
متاسفانه Isotope میانهی چندانی با این ابعاد درصدی ندارد و باید عرض عناصر به صورت دقیق و بر حسب پیکسل بیان شود. البته نسخهی جدید آن و یا حتی پلاگین هایی برای کار با ابعاد درصدی نیز تدارک دیده شده است که به شخصه به نتیجهی با کیفیتی نرسیدم.
@media (min-width: 768px) and (max-width: 980px) { .card { width: 320px; } } @media (min-width: 980px) and (max-width: 1200px) { .card { width: 260px; } } @media (min-width: 1200px) { .card { width: 340px; } }
app.directive('imageOnload', function () { return { restrict: 'A', link: function (scope, element, attrs) { element.bind('load', function () { scope.$emit('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items }); } }; });
$(window).resize(function () { $timeout(function myfunction() { $scope.$broadcast('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items },1000); });
ایجاد یک Repository در پروژه برای دستورات EF
using System; using System.Collections; using System.Linq; namespace Framework.Model { public interface IContext { T Get<T>(Func<T, bool> prediction) where T : class; IEnumerable List<T>(Func<T, bool> prediction) where T : class; void Insert<T>(T entity) where T : class; int Save(); } }
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Text; namespace Framework.Model { public class Context : IContext { private readonly DbContext _dbContext; public Context(DbContext context) { _dbContext = context; } public T Get<T>(Func<T,bool> prediction) where T : class { var dbSet = _dbContext.Set<T>(); if (dbSet!= null) return dbSet.Single(prediction); throw new Exception(); } public void Insert<T>(T entity) where T : class { var dbSet = _dbContext.Set<T>(); if (dbSet != null) { _dbContext.Entry(entity).State = EntityState.Added; } } public int Save() { return _dbContext.SaveChanges(); } IEnumerable IContext.List<T>(Func<T, bool> prediction) { var dbSet = _dbContext.Set<T>(); if (dbSet != null) return dbSet.Where(prediction).ToList(); throw new Exception(); } } }
using System.Data.Entity; using DataModel; namespace Model { public class EFContext : DbContext { public EFContext(string db): base(db) { } public DbSet<Product> Products { get; set; } } }
using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; namespace Model { public class Context : Framework.Model.Context { public Context(string db): base(new EFContext(db)) { } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Biz { public class Context : Model.Context { public Context(string db) : base(db) { } } }
using System.Web.Mvc; using Framework.Model; namespace ProductionRepository.Controllers { public class BaseController : Controller { public IContext DataContext { get; set; } public BaseController() { DataContext = new Biz.Context(System.Configuration.ConfigurationManager.ConnectionStrings["Database"].ConnectionString); } } }
using System.Web.Mvc; using DataModel; using System.Collections.Generic; namespace ProductionRepository.Controllers { public class ProductController : BaseController { public ActionResult Index() { var x = DataContext.List<Product>(s => s.Name != null); return View(x); } } }
using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Mvc; namespace TestUnit { [TestFixture] public class Test { [Test] public void IndexShouldListProduct() { var repo = new Moq.Mock<Framework.Model.IContext>(); var products = new List<DataModel.Product>(); products.Add(new DataModel.Product { Id = 1, Name = "asdasdasd" }); products.Add(new DataModel.Product { Id = 2, Name = "adaqwe" }); products.Add(new DataModel.Product { Id = 4, Name = "qewqw" }); products.Add(new DataModel.Product { Id = 5, Name = "qwe" }); repo.Setup(x => x.List<DataModel.Product>(p => p.Name != null)).Returns(products.AsEnumerable()); var controller = new ProductionRepository.Controllers.ProductController(); controller.DataContext = repo.Object; var result = controller.Index() as ViewResult; var model = result.Model as List<DataModel.Product>; Assert.AreEqual(4, model.Count); Assert.AreEqual("", result.ViewName); } } }
تنظیمات روابط یک به چند در EF Core
همان اسکریپت ابتدای مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانکهای اطلاعاتی از پیش موجود» را درنظر بگیرید. رابطهی تعریف شدهی در آن از نوع one-to-many است: یک بلاگ که میتواند چندین مطلب را داشته باشد.
اگر EF Core را وادار به تولید نگاشتهای Code First معادل آن کنیم، به این خروجیها خواهیم رسید:
الف) با استفاده از روش Fluent API
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose
using System; using System.Collections.Generic; namespace Core1RtmEmptyTest.Entities { public partial class Blog { public Blog() { Post = new HashSet<Post>(); } public int BlogId { get; set; } public string Url { get; set; } public virtual ICollection<Post> Post { get; set; } } }
using System; using System.Collections.Generic; namespace Core1RtmEmptyTest.Entities { public partial class Post { public int PostId { get; set; } public string Content { get; set; } public string Title { get; set; } public virtual Blog Blog { get; set; } public int BlogId { get; set; } } }
using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; namespace Core1RtmEmptyTest.Entities { public partial class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>(entity => { entity.Property(e => e.Url).IsRequired(); }); modelBuilder.Entity<Post>(entity => { entity.HasOne(d => d.Blog) .WithMany(p => p.Post) .HasForeignKey(d => d.BlogId); }); } public virtual DbSet<Blog> Blog { get; set; } public virtual DbSet<Post> Post { get; set; } } }
نحوهی تشخیص خودکار روابط
EF Core به صورت پیش فرض، روابط را بر اساس ارجاعات بین کلاسها تشخیص میدهد. در اینجا به خاصیت Blog نام navigation property را میدهند:
public virtual Blog Blog { get; set; }
public virtual ICollection<Post> Post { get; set; }
نحوهی تشخیص خودکار کلیدهای خارجی
اگر در یک طرف رابطهی تشخیص داده شده، خاصیتی با یکی از سه نام زیر وجود داشت:
<primary key property name> <navigation property name><primary key property name> <principal entity name><primary key property name>
برای مثال در رابطهی فوق، نام خاصیت BlogId دقیقا بر اساس همان الگوی <primary key property name> طرف دیگر رابطهاست:
public virtual Blog Blog { get; set; } public int BlogId { get; set; }
تا اینجا اگر مطلب را دنبال کرده باشید به این نتیجه خواهید رسید که دو کلاس فوق، اساسا نیازی به هیچ نوع تنظیم Fluent و یا Data annotations ایی برای برقراری ارتباط یک به چند ندارند. چون روابط بین آنها بر اساس خواص راهبری (navigation property) و همچنین الگوی <primary key property name>، به صورت خودکار قابل تشخیص و تنظیم است. به علاوه ... در هر طرف رابطه، فقط یک navigation property وجود دارد و نیازی به تنظیم دستی سر دیگر رابطه نیست.
استفاده از Fluent API برای تنظیم رابطهی One-to-Many
در تنظیمات فوق، در متد OnModelCreating، ذکر صریح این روابط را صرفا جهت از بین بردن هرگونه ابهامی مشاهده میکنید:
modelBuilder.Entity<Post>(entity => { entity.HasOne(d => d.Blog) .WithMany(p => p.Post) .HasForeignKey(d => d.BlogId); });
مرحلهی بعد، مشخص کردن سر دیگر رابطه (inverse navigation) است. اینکار توسط یکی از متدهای WithOne و یا WithMany انجام میشود.
متدهایی که اسامی فرد دارند مانند HasOne/WithOne به یک navigation property ساده اشاره میکنند.
متدهایی که اسامی جمع دارند مانند HasMany/WithMany به collection navigation properties اشاره خواهند کرد.
متد HasForeignKey نیز برای ذکر صریح کلید خارجی بکار رفتهاست.
ب) با استفاده از روش data-annotations
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose -a
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Core1RtmEmptyTest.Entities { public partial class Blog { public Blog() { Post = new HashSet<Post>(); } public int BlogId { get; set; } [Required] public string Url { get; set; } [InverseProperty("Blog")] public virtual ICollection<Post> Post { get; set; } } }
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Core1RtmEmptyTest.Entities { public partial class Post { public int PostId { get; set; } public string Content { get; set; } public string Title { get; set; } [ForeignKey("BlogId")] [InverseProperty("Post")] public virtual Blog Blog { get; set; } public int BlogId { get; set; } } }
using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; namespace Core1RtmEmptyTest.Entities { public partial class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { } public virtual DbSet<Blog> Blog { get; set; } public virtual DbSet<Post> Post { get; set; } } }
[ForeignKey("BlogId")] [InverseProperty("Post")] public virtual Blog Blog { get; set; } public int BlogId { get; set; }
در این حالت (داشتن بیش از یک خاصیت راهبری)، باید ویژگی InverseProperty را نیز به سر دوم رابطه، اعمال کرد.
[InverseProperty("Blog")] public virtual ICollection<Post> Post { get; set; }
مطالب تکمیلی
علت virtual بودن خواص راهبری تولید شده
اگر دقت کنید، EF Core کدی را که تولید کردهاست، به همراه خاصیتهایی virtual است:
public virtual Blog Blog { get; set; }
الف) پیاده سازی lazy loading (بارگذاری خودکار اعضای مرتبط (همان خواص راهبری) با اولین دسترسی به آنها)
ب) پیاده سازی change tracking
مبحث lazy loading فعلا در EF Core 1.0 پشتیبانی نمیشود. اما change tracking آن فعال است.
بنابراین اگر مشاهده کردید خواص راهبری به صورت virtual تعریف شدهاند، علت آن فعال سازی lazy loading است و اگر سایر خواص به صورت virtual تعریف شدهاند، هدف اصلی آن بهبود عملکرد سیستم change tracking است.
همچنین اگر دقت کرده باشید، نوع مجموعهها نیز ICollection ذکر شدهاست. این مورد نیز یکی دیگر از پیش فرضهای توکار EF Core است؛ در جهت تشکیل پروکسیها بر روی خواص راهبری مجموعهای (علاوه بر virtual تعریف کردن آنها). عنوان شدهاست که اگر برای مثال از List استفاده کنید (پیاده سازی اینترفیس) یا هر اینترفیس دیگری که از ICollection مشتق شدهاست، این پروکسیها تشکیل نخواهند شد.
واکشی اعضای به هم مرتبط
همانطور که عنوان شد، نگارش اول EF Core برخلاف EF 6.x از Lazy loading پشتیبانی نمیکند. البته این مساله در کل مورد مثبتی است؛ خصوصا در برنامههای وب! چون استفادهی نادرست از Lazy loading که به select n+1 نیز مشهور است، سبب رفت و برگشتهای بیشماری به بانک اطلاعاتی میشود و عموم برنامه نویسهای وب باید مدام توسط برنامههای Profiler بررسی کنند که آیا این مساله رخ دادهاست یا خیر. فعلا EF Core از این مشکل در امان است!
اما ... اگر به روش کار EF 6.x عادت کرده باشید، قطعه کد ذیل:
var firstPost = context.Post.First(); Console.WriteLine(firstPost.Blog.Url);
System.NullReferenceException Object reference not set to an instance of an object.
برای رفع این مشکل باید توسط متد Include، سبب لغو عملیات Lazy loading و واکشی صریح Blog مرتبط شویم که اصطلاحا به آن eager loading میگویند:
var firstPost = context.Post.Include(x => x.Blog).First(); Console.WriteLine(firstPost.Blog.Url);
نکتهای در مورد سطوح بارگذاری اعضای به هم مرتبط در EF Core
متد Include ایی را که تا اینجا مشاهده کردید، با EF 6.x تفاوتی ندارد. برای مثال اگر شیء Blog حاوی خواص راهبری Posts و همچنین Owner باشد، برای بارگذاری این اعضای مرتبط، میتوان همانند قبل، متدهای Include را پشت سر هم ذکر کرد:
var blogs = context.Blogs .Include(blog => blog.Posts) .Include(blog => blog.Owner) .ToList();
var blogs = context.Blogs .Include(blog => blog.Posts) .ThenInclude(post => post.Author) .ToList();
همچنین در اینجا امکان ذکر زنجیروار متدهای ThenInclude هم هست:
var blogs = context.Blogs .Include(blog => blog.Posts) .ThenInclude(post => post.Author) .ThenInclude(author => author.Photo) .ToList();
به علاوه امکان ذکر چندین ریشه و چندین زیر ریشه هم وجود دارند:
var blogs = context.Blogs .Include(blog => blog.Posts) .ThenInclude(post => post.Author) .ThenInclude(author => author.Photo) .Include(blog => blog.Owner) .ThenInclude(owner => owner.Photo) .ToList();
یک نکته: متد Include تنها زمانی درنظر گرفته خواهد شد که نوع خروجی نهایی کوئری، دقیقا از نوع موجودیتی باشد که با آن شروع به کار کردهایم. برای مثال اگر در این بین یک Select اضافه شود و فقط تنها تعدادی از خواص Blog واکشی شوند، از تمام Includeهای ذکر شده صرفنظر میشود؛ مانند کوئری ذیل:
var blogs = context.Blogs .Include(blog => blog.Posts) .Select(blog => new { Id = blog.BlogId, Url = blog.Url }) .ToList();
تنظیمات حذف آبشاری در رابطهی one-to-many
زمانیکه در رابطهی one-to-many قسمت principal (والد رابطه) و یا همان Blog در مثال جاری حذف میشود، سه اتفاق برای فرزندان آن میسر خواهند بود:
الف) Cascade : در این حالت ردیفهای فرزندان وابسته نیز حذف خواهند شد.
باید دقت داشت که حالت Cascade فقط برای موجودیتهایی اعمال میشود که توسط Context بارگذاری شده و در آن وجود دارند. اگر میخواهید سایر موجودیتهای مرتبط نیز با این روش حذف شوند، باید در سمت دیتابیس نیز تنظیماتی مانند ON DELETE CASCADE زیر نیز وجود داشته باشند:
CONSTRAINT [FK_Post_Blog_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blog] ([BlogId]) ON DELETE CASCADE
modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .OnDelete(DeleteBehavior.Cascade);
ج) Restrict: هیچ تغییری بر روی فرزندان رابطه رخ نمیدهد.
یک نکته: به صورت پیش فرض اگر رابطهی one-to-many، به Required تنظیم شود، حالت حذف آن cascade خواهد بود. در غیراینصورت برای حالتهای Optional، حالت SetNull تنظیم میگردد:
modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .IsRequired();
به علاوه باید دقت داشت، همان مباحث «تعیین اجباری بودن یا نبودن ستونها در EF Core» در قسمت قبل، در اینجا هم صادق است. برای مثال چون BlogId (کلید خارجی در کلاس Post) از نوع int است و نال پذیر نیست، بنابراین از دیدگاه EF Core یک فیلد اجباری درنظر گرفته میشود. به همین جهت است که در کدهای تولید شدهی توسط EF Core در ابتدای بحث، ذکر متد IsRequired و یا OnDelete را مشاهده نمیکنید.
بنابراین اگر میخواهید حالت SetNull را فعال کنید، باید این کلید خارجی را نیز نال پذیر و به صورت int? BlogId ذکر کنید تا optional درنظر گرفته شود.
آشنایی با Refactoring - قسمت 9
در برنامههای وب امروز نیازی به فراخوانی ثوابت که در طول حیات برنامه انگشت شمار تغیر میکنند نیست و با توجه به استفاده از فرامین و متدهای سمت کلاینت احتیاج هست تا این ثوابت بار اول لود صفحه به کلاینت پاس داده شوند.
میتوان در این گونه موارد از قابلیتهای گوناگونی استفاده کرد که در اینجا ما با استفاده از یک فیلد مخفی و json مقدار را به کلاینت پاس میدهیم و در این مثال در سمت کلاینت نیز دراپ دان را با این مقادیر پر میکنیم:
public enum PersistType { Persistable = 1, NotPersist = 2, AlwaysPersist = 3 }
لیست را باید قبل از پر کردن در فیلد مخفی به json بصورت serialize شده تبدیل کرد، برای این منظور از JavaScriptSerializer موجود در اسمبلیهای دات نت در متد زیر استفاده شده:
public static string ConvertEnumToJavascript(Type t) { if (!t.IsEnum) throw new Exception("Type must be an enumeration"); var values = System.Enum.GetValues(t); var dict = new Dictionary<int, string>(); foreach (object obj in values) { string name = System.Enum.GetName(t, obj); dict.Add(Convert.ToInt32(System.Enum.Format(t, obj, "D")), name); } return new JavaScriptSerializer().Serialize(dict); }
با توجه به اینکه در سمت کلاینت مقدار json ذخیره شده در فیلد مخفی را میتوان به صورت آبجکت برخورد کرد پس یک متد در سمت کلاینت این آبجکت را در loop قراد داده و درمتغییری در فایل جاوا اسکریپت نگهداری میکنیم:
var Enum_PersistType = null; function SetEnumTypes() { Enum_PersistType = JSON.parse($('#hfJsonEnum_PersistType').val()); }
و در هر قسمت که نیاز به مقدار enum بود با توجه به ایندکس مقدار را برای نمایش ازاین متغییر بیرون میکشیم:
function GetPersistTypeTitle_Concept(enumId) { return Enum_PersistType[enumId]; }
برای مثال در dropdown در سمت کلاینت این نوع استفاده شده و در حالتی از صفحه فقط برای نمایش عنوان آن احتیاج به دریافت آن از سمت سرور باشد میتوان از این روش کمک گرفت
و یا در سمت javascript میتوان با استفاده از jQuery مقادیر متغییر را در dropdown پر کرد.
function FillDropdown() { $("#ddlPersistType").html(""); $.each(Enum_PersistType, function (key, value) { $("#ddlPersistType").append($("<option></option>").val(key).html(value)); }); }
emit$:
دو کنترلر به نامهای FirstCtrl و SecondCtrl داریم. FirstCtrl به عنوان والد کنترلر Second است(در این مورد در این پست توضیح داده شده است).
پس فایل html نیز به صورت زیر خواهد بود:
<body ng-app> <div ng-controller="FirstCtrl"> <p>{{title}}</p> <div ng-controller="SecondCtrl"> <button ng-click="onUpdate()">Update First Ctrl Title</button> </div> </div> </body>
function ChildCtrl($scope){ $scope.onUpdate = function(){ this.$emit("Update_Title", "Good Bye"); }; }
function FirstCtrl($scope){ $scope.title= "Hello"; $scope.$on("Update_Title", function(event, message){ $scope.title= message; }); }
»پارامتر دوم سرویس on$ برابر با مقدار جدید ارسال شده توسط سرویس emit$ است.
»نام رویدادی که به عنوان پارامتر به on$ پاس داده میشود باید برابر با نام رویداد پاس داده شده به emit$ باشد.
»میتوان چندین پارامتر را با استفاده از emit$ ارسال کرد و در سرویس on$ با تعریف متغیر به تعداد پارامترها مقادیر آنها را دریافت نمود.
broadcast$
همان طور که مشاهده کردید SecondCtrl در محدوده FirstCtrl تعریف شده است. در نتیجه به راحتی با استفاده از سرویس emit$ توانستیم یک رویداد را منتشر نماییم. اما نکته مهم این است که اگر قصد داشته باشیم یک رویداد را از کنترلر والد (در این جا FirstCtrl است) منتشر نماییم به طوری که در کنترلرهای فرزند قابل دریافت باشد(حرکت رویداد بالا به پایین است)، باید از broadcast$ استفاده کنیم.
»broadcast$ فقط از نظر کاربرد با emit$ متفاوت است و در پیاده سازی کاملا مشابه هستند.
یک مثال:
function ParentCtrl($scope){ $scope.foo = "Hello"; $scope.$on("UPDATE_PARENT", function(event, message){ $scope.title= message; $scope.$broadcast("DO_BIDDING", { buttonTitle : "Taken over", onButtonClick : function(){ $scope.title= "HAHA this button no longer works!"; } }); }); } function ChildCtrl($scope){ $scope.buttonTitle = "Update Parent"; $scope.onButtonClick = function(){ this.$emit("UPDATE_PARENT", "Updated"); }; $scope.$on("DO_BIDDING", function(event, data){ for(var i in data){ $scope[i] = data[i]; } }); }
اگر حالت فرزند و والد بین کنترلرها نباشد چه؟
در این حالت باید rootScope$ را به کنترلر مورد نظر تزریق نمایید و سپس با استفاده از سرویس broadcast$ یا emit$ رویدادتان را منتشر کنید. مثال:
'use strict'; angular.module('myAppControllers', []) .controller('FirstCtrl', function ($rootScope) { $rootScope.$broadcast('UPDATE_ALL'); Or $rootScope.$emit('UPDATE_ALL'); });
از آن جا که حرکت بالا به پایین event bubbling بسیار هزینه برتر است نسبت به حرکت پایین به بالا در نتیجه سعی کنید تا جای ممکن از rootScope$.$broadcast استفاده نکنید. در این جا توضیح کاملی درباره دلایل عدم استفاده از rootScope$.$broadcast داده شده است.
هم چنین میتوانید یک مثال Live را نیز برای مقایسه بین emit$ و broadcast$ در این جا مشاهده کنید.