اشتراکها
نگاشت اشیاء امری مفید و لذت بخش است. ولی بخاطر تنظیمات خاص آن و افزایش کدها، همیشه کمی دردسر ساز بوده است. استفاده از کلاس Profile راه کار مناسبی است؛ اما در این حالت کلاس مقصد (ViewModel) از تنظیمات نگاشتها بی اطلاع میماند و فقط حاوی داده خواهد بود. برای ادغام کلاس و تنظیمات نگاشت در اینجا راهکاری ارائه گردید که در ادامه و با الگو گیری از همین ایده، اقدام به ارائهی روشی جدید میکنم که با استفاده از Attributeها تنظیمات نگاشت اشیاء را در AutoMapper انجام میدهد.
در نهایت میخواهیم نگاشتها را اینچنین تنظیم کنیم:
این سبک تنظیم کردن نگاشتهای اشیاء به نظر بهتر از روشهای دیگر است؛ چون کلاسهای ویوومدل را معنادار کرده و همچنین برای برنامه نویسان EF و ASP.NET MVC استفادهی از ویژگیها، یک شیوهی کاری معمول به حساب میآید.
به تعریف و توضیح صفتهای (ویژگیها یا Attributes) مورد نیاز میپردازم:
صفت MapFromAttribute
این صفت روی کلاسها مینشیند و توسط آرگومان sourceType آن، نوع مبدأ را برای automapper مشخص میکند. در واقع همه چیز از اینجا شروع میشود. همچنین آرگومان ignoreAllNonExistingProperty مشخص میکند کلیهی صفاتی که در مقصد هستند ولی معادل اسمی در مبدأ ندارند، بصورت خودکار رد (Ignore) شده و از آنها صرف نظر شود تا از شکست متد AutoMapper.Mapper.AssertConfigurationIsValid جلوگیری کند (پرداخته شده در اینجا). آرگومان alsoCopyMetadata پیاده سازی نمیشود؛ ولی میتواند پرچمی باشد تا اجازه دهد Data Annotations از مدلهای ef به ViewModel انتقال یابند.
صفت IgnoreMapAttribute
از این صفت برای رد کردن خصیصهای در نگاشتها استفاده میکنیم. لازم به ذکر است که صفتی مشابه در Automapper.IgnoreAttribute وجود دارد که میتواند به جای این صفت مورد استفاده قرار گیرد. «نگارنده جهت همخوانی با سایر صفات، اقدام به استفادهی از این صفت میکند»
صفت MapForMemberAttribute
اگر نام خصیصهها در مبدأ و مقصد یکی نباشند، از این صفت برای همگام سازی این دو استفاده میکنیم.
صفت UseValueResolverAttribute
استفاده از ValueResolverها در اینجا ذکر شده است. از این صفت برای تنظیم این مقدار برای یک خصیصه استفاده میشود. برای مثال فیلد FullName را در مقصد درنظر بگیرد که از دو فیلد Name و Family در مبدأ تشکیل میشود.
تا اینجا صفات پیش نیاز کار فراهم شدند. حال باید این صفتها را به نگاشت متناسبی در automapper تبدیل کنیم.
دریافت کدها
ادامه دارد...
در نهایت میخواهیم نگاشتها را اینچنین تنظیم کنیم:
[MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)] public class AdminStudentViewModel { // [IgnoreMap] public int Id { set; get; } [MapForMember("Name")] public string FirstName { set; get; } [MapForMember("Family")] public string LastName { set; get; } public string Email { set; get; } [MapForMember("RegisterDateTime")] public string RegisterDateTimePersian { set; get; } [UseValueResolver(typeof (BookCountValueResolver))] public int BookCounts { set; get; } [UseValueResolver(typeof (BookPriceValueResolver))] public decimal BookPrice { set; get; } };
به تعریف و توضیح صفتهای (ویژگیها یا Attributes) مورد نیاز میپردازم:
صفت MapFromAttribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class MapFromAttribute : Attribute { public Type SourceType { get; private set; } public bool IgnoreAllNonExistingProperty { get; private set; } public bool AlsoCopyMetadata { get; private set; } //Go to: https://www.dntips.ir/courses/topic/16/cb36bc2e-4263-431e-86a5-236322cb5576 public MapFromAttribute(Type sourceType, bool ignoreAllNonExistingProperty = false, bool alsoCopyMetadata = false) { SourceType = sourceType; IgnoreAllNonExistingProperty = ignoreAllNonExistingProperty; AlsoCopyMetadata = alsoCopyMetadata; } };
صفت IgnoreMapAttribute
[AttributeUsage(AttributeTargets.Property)] public class IgnoreMapAttribute : Attribute {};
صفت MapForMemberAttribute
[AttributeUsage(AttributeTargets.Property)] public class MapForMemberAttribute : Attribute { public string MemberToMap { get; private set; } public MapForMemberAttribute(string memberToMap) { MemberToMap = memberToMap; } };
صفت UseValueResolverAttribute
[AttributeUsage(AttributeTargets.Property)] public class UseValueResolverAttribute : Attribute { public IValueResolver ValueResolver { get; private set; } public UseValueResolverAttribute(Type valueResolver) { ValueResolver = valueResolver.GetConstructors()[0].Invoke(new object[] {}) as IValueResolver; } };
تا اینجا صفات پیش نیاز کار فراهم شدند. حال باید این صفتها را به نگاشت متناسبی در automapper تبدیل کنیم.
دریافت کدها
ادامه دارد...
عنوان میشود که HTML over Web socket آیندهی توسعهی برنامههای وب است و این آینده هم اکنون توسط Blazor Server در دسترس است. در این مدل توسعه، ابتدا یک اتصال SignalR برقرار شده و سپس تمام تعاملات بین سرور و کلاینت، از طریق همین اتصال که عموما web socket است، مدیریت میشود. به همین جهت در ادامه قصد داریم یک پروژهی Blazor Server را تکمیل کنیم. پس از آن یک پروژهی Blazor WASM را نیز بررسی خواهیم کرد. بنابراین هر دو مدل توسعهی برنامههای Blazor را پوشش خواهیم داد. برای این منظور در ابتدا مبانی Blazor را بررسی میکنیم که در هر دو مدل یکی است.
تعریف مدل برنامه
در همان پروژهی خالی Blazor Server که در قسمت دوم با دستور dotnet new blazorserver ایجاد کردیم، پوشهی Models را افزوده و کلاس BlazorRoom را در آن تعریف میکنیم:
سپس برای اینکه مدام نیاز به تعریف فضای نام آن در فایلهای مختلف razor. برنامه نباشد، به فایل Imports.razor_ مراجعه کرده و سطر زیر را به انتهای آن اضافه میکنیم:
برنامه را نیز توسط دستور dotnet watch run اجرا میکنیم.
Data binding یک طرفه
در ادامه به فایل Pages\Index.razor مراجعه کرده و منهای سطر اول مسیریابی آن، مابقی محتوای آنرا حذف میکنیم. در اینجا میخواهیم مقادیر نمونهای از شیء BlazorRoom را نمایش دهیم. به همین جهت این شیء را در قسمت code@ فایل razor جاری (همانند نکات قسمت قبل)، ایجاد میکنیم:
در اینجا در ابتدا شیء Room را در قسمت قطعه کد فایل razor جاری ایجاد کرده و سپس اطلاعات آنرا با استفاده از زبان Razor نمایش دادهایم.
به این روش نمایش اطلاعات، one-way data-binding نیز گفته میشود. اما چطور میتوان یک طرفه بودن آنرا متوجه شد؟ برای این منظور یک text-box را نیز در ذیل تعاریف فوق، به صورت زیر اضافه میکنیم که مقدارش را از Room.Price دریافت میکند:
اکنون اگر این مقدار را تغییر دهیم، عدد جدید قیمت اتاق، به خاصیت Room.Price منعکس نمیشود و تغییری نمیکند:
Data binding دو طرفه
اکنون میخواهیم اگر مقدار ورودی Room.Price توسط text-box فوق تغییر کرد، نتیجهی نهایی، به خاصیت متناظر با آن نیز اعمال شود و تغییر کند. برای این منظور فقط کافی است ویژگی value را به bind-value@ تغییر دهیم:
ویژگی bind-value@ سبب برقراری data-binding دو طرفه میشود. یعنی در ابتدا مقدار اولیهی خاصیت Room.Price را نمایش میدهد. در ادامهی اگر کاربر، مقدار این text-box را تغییر داد، نتیجهی نهایی را به خاصیت Room.Price نیز اعمال میکند و همچنین این تغییر، سبب به روز رسانی UI نیز میشود؛ یعنی در جائیکه پیشتر مقدار اولیهی Room.Price را نمایش داده بودیم، اکنون مقدار جدید آن نمایش داده خواهد شد:
البته اگر برنامه را اجرا کنیم، با تغییر مقدار text-box، بلافاصله تغییری را مشاهده نخواهیم کرد. برای اعمال تغییرات نیاز خواهد بود تا در جائی خارج از text-box کلیک و focus را به المانی دیگر منتقل کنیم. اگر میخواهیم همراه با تایپ اطلاعات درون text-box، رابط کاربری نیز به روز شود، میتوان bind-value را به یک رخداد خاص، مانند oninput متصل کرد. حالت پیشفرض آن onchange است:
اکنون اگر برنامه را اجرا کرده و درون text-box اطلاعاتی را وارد کنیم، بلافاصله UI نیز به روز رسانی خواهد شد.
لیست کامل رخدادها را در اینجا میتوانید مشاهده کنید. برای مثال برای یک المان input، دو رخداد onchange و oninput قابل تعریف هستند.
یک نکته: در حین کار با bind-value@، نیازی نیست مقدار آن با @ شروع شود. یعنی ذکر "bind-value="Room.Price@ نیز کافی است.
تمرین 1 - خاصیت IsActive یک اتاق را به یک checkbox متصل کرده و همچنین وضعیت جاری آنرا نیز در یک برچسب نمایش دهید.
در اینجا میخواهیم مقدار خاصیت Room.IsActive را توسط یک اتصال دو طرفه، به یک checkbox متصل کنیم:
با استفاده از bind-value@، وضعیت جاری خاصیت Room.IsActive را به یک checkbox متصل کردهایم. همچنین در ادامه توسط یک عبارت شرطی، این وضعیت را نمایش دادهایم.
بار اولی که برنامه نمایش داده میشود، هر چند مقدار IsActive بر اساس مقدار دهی آن در شیء Room، مساوی true است، اما chekbox، علامت نخورده باقی میماند. برای رفع این مشکل نیاز است ویژگی checked این المان را نیز به صورت زیر مقدار دهی کرد:
در این حالت اگر اتاقی فعال باشد، مقدار ویژگی checked، به checked و در غیراینصورت به null تنظیم میشود. به این ترتیب مشکل عدم نمایش checkbox انتخاب شده در بار اول نمایش کامپوننت جاری، برطرف میشود.
اتصال خواص مدلها به dropdownها
اکنون میخواهیم مدل این مثال را کمی توسعه داده و خواص تو در تویی را به آن اضافه کنیم:
برای مثال یک اتاق میتواند ویژگیهایی مانند مساحت، تعداد نفرات مجاز و غیره را داشته باشد. هدف از ویژگی جدید RoomProps، تعیین لیست این نوع موارد است.
پس از این تعاریف، فیلد Room را به صورت زیر به روز رسانی میکنیم تا تعدادی از خواص اتاق را به همراه داشته باشد:
در ادامه میخواهیم این خواص را در یک dropdown نمایش دهیم. همچنین با انتخاب یک خاصیت از دراپداون، مقدار خاصیت انتخابی را در یک برچسب نیز به صورت پویا نمایش خواهیم داد:
همانطور که مشاهده میکنید، انجام یک چنین کاری با Blazor بسیار سادهاست و نیازی به استفاده از جاوا اسکریپت و یا جیکوئری ندارد.
در اینجا یک فیلد را در قطعه کد برنامه تعریف کرده و به المان select متصل کردهایم. هرگاه آیتمی در این دراپ داون انتخاب شود، این فیلد، مقدار آن آیتم انتخابی را خواهد داشت. در ادامه توسط یک حلقهی foreach، تمام خواص یک اتاق را دریافت کرده و به صورت optionsهای یک select استاندارد، نمایش میدهیم. در آخر نیز مقدار SelectedRoomPropValue را نمایش دادهایم که این مقدار به صورت پویا تغییر میکند:
تعریف لیستی از اتاقها
عموما در یک برنامهی واقعی، با یک تک اتاق کار نمیکنیم. به همین جهت در ادامه لیستی از اتاقها را تعریف و مقدار دهی اولیه خواهیم کرد:
در ابتدا فیلد Rooms تعریف شده که لیستی از BlazorRoomها است. در ادامه بجای مقدار دهی مستقیم آن در همان سطح قطعه کد، آنرا در یک متد life-cycle کامپوننت جاری به نام OnInitialized که مخصوص این نوع مقدار دهیهای اولیه است، مقدار دهی کردهایم.
نمایش لیست قابل ویرایش اتاقها
اکنون میخواهیم به عنوان تمرین 2، لیست جزئیات اتاقهای تعریف شده را نمایش دهیم؛ با این شرط که نام و قیمت هر اتاق، قابل ویرایش باشد. همچنین خواص تعریف شده نیز به صورت ستونهایی مجزا، نمایش داده شوند. برای مثال اگر دو خاصیت در اینجا تعریف شده، 2 ستون اضافهتر نیز برای نمایش آنها وجود داشته باشد. به علاوه از آنجائیکه میخواهیم اتصال دوطرفه را نیز آزمایش کنیم، نام و قیمت هر اتاق را نیز در پایین جدول، مجددا به صورت برچسبهایی نمایش خواهیم داد.
برای رسیدن به تصویر فوق میتوان به صورت زیر عمل کرد:
در اینجا یک حلقهی تو در تو را مشاهده میکنید. حلقهی بیرونی، ردیفهای جدول را که شامل نام و قیمت هر اتاق است، به صورت input-boxهای متصل به خواص متناظر با آنها نمایش میدهد. سپس برای اینکه بتوانیم خواص هر ردیف را نیز نمایش دهیم، حلقهی دومی را بر روی room.RoomProps تشکیل دادهایم.
هدف از foreach پس از جدول، نمایش تغییرات انجام شدهی در input-boxها است. برای مثال اگر نام یک ردیف را تغییر دادیم، چون یک اتصال دو طرفه برقرار است، خاصیت متناظر با آن به روز رسانی شده و بلافاصله در برچسبهای ذیل جدول، منعکس میشود.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-04.zip
تعریف مدل برنامه
در همان پروژهی خالی Blazor Server که در قسمت دوم با دستور dotnet new blazorserver ایجاد کردیم، پوشهی Models را افزوده و کلاس BlazorRoom را در آن تعریف میکنیم:
namespace BlazorServerSample.Models { public class BlazorRoom { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public bool IsActive { set; get; } } }
@using BlazorServerSample.Models
Data binding یک طرفه
در ادامه به فایل Pages\Index.razor مراجعه کرده و منهای سطر اول مسیریابی آن، مابقی محتوای آنرا حذف میکنیم. در اینجا میخواهیم مقادیر نمونهای از شیء BlazorRoom را نمایش دهیم. به همین جهت این شیء را در قسمت code@ فایل razor جاری (همانند نکات قسمت قبل)، ایجاد میکنیم:
@page "/" <h2 class="bg-light border p-2"> First Room </h2> Room: @Room.Name <br/> Price: @Room.Price @code { BlazorRoom Room = new BlazorRoom { Id = 1, Name = "Room 1", IsActive = true, Price = 499 }; }
به این روش نمایش اطلاعات، one-way data-binding نیز گفته میشود. اما چطور میتوان یک طرفه بودن آنرا متوجه شد؟ برای این منظور یک text-box را نیز در ذیل تعاریف فوق، به صورت زیر اضافه میکنیم که مقدارش را از Room.Price دریافت میکند:
<input type="number" value="@Room.Price" />
Data binding دو طرفه
اکنون میخواهیم اگر مقدار ورودی Room.Price توسط text-box فوق تغییر کرد، نتیجهی نهایی، به خاصیت متناظر با آن نیز اعمال شود و تغییر کند. برای این منظور فقط کافی است ویژگی value را به bind-value@ تغییر دهیم:
<input type="number" @bind-value="@Room.Price" />
البته اگر برنامه را اجرا کنیم، با تغییر مقدار text-box، بلافاصله تغییری را مشاهده نخواهیم کرد. برای اعمال تغییرات نیاز خواهد بود تا در جائی خارج از text-box کلیک و focus را به المانی دیگر منتقل کنیم. اگر میخواهیم همراه با تایپ اطلاعات درون text-box، رابط کاربری نیز به روز شود، میتوان bind-value را به یک رخداد خاص، مانند oninput متصل کرد. حالت پیشفرض آن onchange است:
<input type="number" @bind-value="@Room.Price" @bind-value:event="oninput" />
لیست کامل رخدادها را در اینجا میتوانید مشاهده کنید. برای مثال برای یک المان input، دو رخداد onchange و oninput قابل تعریف هستند.
یک نکته: در حین کار با bind-value@، نیازی نیست مقدار آن با @ شروع شود. یعنی ذکر "bind-value="Room.Price@ نیز کافی است.
تمرین 1 - خاصیت IsActive یک اتاق را به یک checkbox متصل کرده و همچنین وضعیت جاری آنرا نیز در یک برچسب نمایش دهید.
در اینجا میخواهیم مقدار خاصیت Room.IsActive را توسط یک اتصال دو طرفه، به یک checkbox متصل کنیم:
<input type="checkbox" @bind-value="Room.IsActive" /> <br/> This room is @(Room.IsActive? "Active" : "Inactive").
بار اولی که برنامه نمایش داده میشود، هر چند مقدار IsActive بر اساس مقدار دهی آن در شیء Room، مساوی true است، اما chekbox، علامت نخورده باقی میماند. برای رفع این مشکل نیاز است ویژگی checked این المان را نیز به صورت زیر مقدار دهی کرد:
<input type="checkbox" @bind-value="Room.IsActive" checked="@(Room.IsActive? "cheked" : null)" />
اتصال خواص مدلها به dropdownها
اکنون میخواهیم مدل این مثال را کمی توسعه داده و خواص تو در تویی را به آن اضافه کنیم:
using System.Collections.Generic; namespace BlazorServerSample.Models { public class BlazorRoom { // ... public List<BlazorRoomProp> RoomProps { set; get; } } public class BlazorRoomProp { public int Id { set; get; } public string Name { set; get; } public string Value { set; get; } } }
پس از این تعاریف، فیلد Room را به صورت زیر به روز رسانی میکنیم تا تعدادی از خواص اتاق را به همراه داشته باشد:
@code { BlazorRoom Room = new BlazorRoom { Id = 1, Name = "Room 1", IsActive = true, Price = 499, RoomProps = new List<BlazorRoomProp> { new BlazorRoomProp { Id = 1, Name = "Sq Ft", Value = "100" }, new BlazorRoomProp { Id = 2, Name = "Occupancy", Value = "3" } } }; }
<select @bind="SelectedRoomPropValue"> @foreach (var prop in Room.RoomProps) { <option value="@prop.Value">@prop.Name</option> } </select> <span>The value of the selected room prop is: @SelectedRoomPropValue</span> @code { string SelectedRoomPropValue = ""; // ...
در اینجا یک فیلد را در قطعه کد برنامه تعریف کرده و به المان select متصل کردهایم. هرگاه آیتمی در این دراپ داون انتخاب شود، این فیلد، مقدار آن آیتم انتخابی را خواهد داشت. در ادامه توسط یک حلقهی foreach، تمام خواص یک اتاق را دریافت کرده و به صورت optionsهای یک select استاندارد، نمایش میدهیم. در آخر نیز مقدار SelectedRoomPropValue را نمایش دادهایم که این مقدار به صورت پویا تغییر میکند:
تعریف لیستی از اتاقها
عموما در یک برنامهی واقعی، با یک تک اتاق کار نمیکنیم. به همین جهت در ادامه لیستی از اتاقها را تعریف و مقدار دهی اولیه خواهیم کرد:
@code { string SelectedRoomPropValue = ""; List<BlazorRoom> Rooms = new List<BlazorRoom>(); protected override void OnInitialized() { base.OnInitialized(); Rooms.Add(new BlazorRoom { Id = 1, Name = "Room 1", IsActive = true, Price = 499, RoomProps = new List<BlazorRoomProp> { new BlazorRoomProp { Id = 1, Name = "Sq Ft", Value = "100" }, new BlazorRoomProp { Id = 2, Name = "Occupancy", Value = "3" } } }); Rooms.Add(new BlazorRoom { Id = 2, Name = "Room 2", IsActive = true, Price = 399, RoomProps = new List<BlazorRoomProp> { new BlazorRoomProp { Id = 1, Name = "Sq Ft", Value = "250" }, new BlazorRoomProp { Id = 2, Name = "Occupancy", Value = "4" } } }); } }
نمایش لیست قابل ویرایش اتاقها
اکنون میخواهیم به عنوان تمرین 2، لیست جزئیات اتاقهای تعریف شده را نمایش دهیم؛ با این شرط که نام و قیمت هر اتاق، قابل ویرایش باشد. همچنین خواص تعریف شده نیز به صورت ستونهایی مجزا، نمایش داده شوند. برای مثال اگر دو خاصیت در اینجا تعریف شده، 2 ستون اضافهتر نیز برای نمایش آنها وجود داشته باشد. به علاوه از آنجائیکه میخواهیم اتصال دوطرفه را نیز آزمایش کنیم، نام و قیمت هر اتاق را نیز در پایین جدول، مجددا به صورت برچسبهایی نمایش خواهیم داد.
برای رسیدن به تصویر فوق میتوان به صورت زیر عمل کرد:
<div class="border p-2 mt-3"> <h2 class="text-info">Rooms List</h2> <table class="table table-dark"> @foreach(var room in Rooms) { <tr> <td> <input type="text" @bind-value="room.Name" @bind-value:event="oninput"/> </td> <td> <input type="text" @bind-value="room.Price" @bind-value:event="oninput"/> </td> @foreach (var roomProp in room.RoomProps) { <td> @roomProp.Name, @roomProp.Value </td> } </tr> } </table> @foreach(var room in Rooms) { <p>@room.Name's price is @room.Price.</p> } </div>
هدف از foreach پس از جدول، نمایش تغییرات انجام شدهی در input-boxها است. برای مثال اگر نام یک ردیف را تغییر دادیم، چون یک اتصال دو طرفه برقرار است، خاصیت متناظر با آن به روز رسانی شده و بلافاصله در برچسبهای ذیل جدول، منعکس میشود.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-04.zip
با سلام
بنده مدل زیر را دارم که مربوط به صفحاتی هستند که والد هم دارند.
و با دستور زیر میخواهم از آن کوئری بگیرم:
دستور فوق به خوبی کار میکنه. ولی وقتی با که دستوراتی که توسط mini-profiler لاگ شده را میبینیم که اخطار duplicate reader را میدهد.
برای هر page موجود دستور زیر را صادر میکند
میخواستم ببینم کاری میشه کرد تا سربار این کوئری را کمتر کرد؟
در ضمن اگر بخواهم viewmodel را طوری تعریف کنم تا فیلدهای اضافی مانند createddate و user و... که در هنگام نمایش منوی آبشاری به آنها نیازی ندارم چه کار باید کرد؟ چون من هر کاری کردم نتونستم parent را برای viewmodel به خوبی تعریف کنم.
ممنون
بنده مدل زیر را دارم که مربوط به صفحاتی هستند که والد هم دارند.
public class Page { public virtual int Id { get; set; } public virtual string Title { get; set; } public virtual DateTime? CreatedDate { get; set; } public virtual DateTime? ModifiedDate { get; set; } public virtual string Body { get; set; } public virtual string Keyword { get; set; } public virtual string Description { get; set; } public virtual string Status { get; set; } public virtual bool? CommentStatus { get; set; } public virtual int? Order { get; set; } public virtual User User { get; set; } public virtual User EditedByUser { get; set; } public virtual ICollection<Comment> Comments { get; set; } public virtual int? ParentId { get; set; } public virtual Page Parent { get; set; } public virtual ICollection<Page> Children { get; set; } }
و با دستور زیر میخواهم از آن کوئری بگیرم:
this._pages.ToList().Where(page => page.Parent == null).ToList();
دستور فوق به خوبی کار میکنه. ولی وقتی با که دستوراتی که توسط mini-profiler لاگ شده را میبینیم که اخطار duplicate reader را میدهد.
برای هر page موجود دستور زیر را صادر میکند
SELECT [Extent1].[Id] AS [Id], [Extent1].[Title] AS [Title], [Extent1].[CreatedDate] AS [CreatedDate], [Extent1].[ModifiedDate] AS [ModifiedDate], [Extent1].[Body] AS [Body], [Extent1].[Keyword] AS [Keyword], [Extent1].[Description] AS [Description], [Extent1].[Status] AS [Status], [Extent1].[CommentStatus] AS [CommentStatus], [Extent1].[Order] AS [Order], [Extent1].[ParentId] AS [ParentId], [Extent2].[Id] AS [Id1], [Extent2].[Title] AS [Title1], [Extent2].[CreatedDate] AS [CreatedDate1], [Extent2].[ModifiedDate] AS [ModifiedDate1], [Extent2].[Body] AS [Body1], [Extent2].[Keyword] AS [Keyword1], [Extent2].[Description] AS [Description1], [Extent2].[Status] AS [Status1], [Extent2].[CommentStatus] AS [CommentStatus1], [Extent2].[Order] AS [Order1], [Extent2].[ParentId] AS [ParentId1], [Extent2].[User_Id] AS [User_Id], [Extent2].[EditedByUser_Id] AS [EditedByUser_Id], [Extent1].[User_Id] AS [User_Id1], [Extent1].[EditedByUser_Id] AS [EditedByUser_Id1] FROM [dbo].[Pages] AS [Extent1] LEFT OUTER JOIN [dbo].[Pages] AS [Extent2] ON [Extent1].[ParentId] = [Extent2].[Id
میخواستم ببینم کاری میشه کرد تا سربار این کوئری را کمتر کرد؟
در ضمن اگر بخواهم viewmodel را طوری تعریف کنم تا فیلدهای اضافی مانند createddate و user و... که در هنگام نمایش منوی آبشاری به آنها نیازی ندارم چه کار باید کرد؟ چون من هر کاری کردم نتونستم parent را برای viewmodel به خوبی تعریف کنم.
ممنون
تعریف: Constant فیلدی است که مقدار آن در زمان کامپایل (Compile time) مشخص میشود و این مقدار هیچگاه نمیتواند تغییر کند (ثابت است). از کلمه کلیدی (Keyword) ، const برای تعریف یک constant استفاده میشود.
تعاریف اولیه : Constant Field : فیلد ثابتی که مستقیما در یک Class و یا Struct تعریف میشود.
Constant Local : ثابتی که در بلاکهای برنامه (بدنه یک تابع ، حلقه تکرار و ...) تعریف میشود.
همهی انواع درون ساخت (Built in) در زبان #C مانند (انواع عددی، بولین، کاراکتر، رشته و نوعهای شمارشی) و اشارهگرهای تهی (null reference) میتوانند بصورت constant تعریف شوند. باید توجه داشت که عبارت تعریف و مقدار دهی یک constant (ثابت) باید بصورتی باشد که در زمان کامپایل کاملا قابل ارزیابی باشد.
جدول مقایسهای بین Const و ReadOnly
Constant | ReadOnly |
میتواند به Fieldها و همچنین localها اعمال شود. | تنها به Field ها اعمال میشود. |
مقدار دهی اولیه آن الزامی است. | مقدار دهی اولیه میتواند هنگام تعریف و یا در درون سازنده انجام شود (در هیچ متد دیگری امکان پذیر نیست). |
تخصیص حافظه انجام نمیشود و مقدار آن در کدهای IL گنجانده میشود (توضیح در ادامه مطلب). | تخصیص حافظه بصورت داینامیک انجام میشود و میتوانیم در زمان اجرا مقدار آن را بدست آوریم. |
ثابتها در #C بصورت پیش فرض از نوع static هستند. بدین معنا که از طریق نام کلاس قابل دسترسی هستند. | تنها از طریق وهله سازی از یک کلاس قابل دسترسی هستند. |
نوعهای درون ساز (built in) و Null Reference ها را میتوان بصورت const تعریف کرد. Boolean,Char, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal , string. | مشابه Constant ها |
مقدار آن در طول عمر یک برنامه ثابت است. | مقدار آن میتواند در هنگام فراخوانی سازنده برای وهلههای مختلف متفاوت باشد. |
فیلدهای const را نمیتوان بصورت پارامترهای out و ref استفاده کرد. | فیلدهای ReadOnly را میتوان بصورت پارامترهای ref و out در درون سازنده استفاده کرد. |
نحوه تعریف یک constant :
همانطور که در تصویر مشاهده میکنید در کنار نماد انتخابی برای constها یک قفل کوچک (نشان از غیرقابل تغییر بودن) قرار گرفته است .
مثالی از تعریف و رفتار Constantها در #C :
const int field_constant = 10; //constant field static void Main(string[] args) { const int x = 10, y = 15; //constant local :correct const int z = x + y; //constant local : correct; const int a = x + GetVariableValue();//Error } public static int GetVariableValue() { const int localx = 10; return 10; }
در خطوط اول و دوم ارزش متغیرهای x,y,z بدرستی محاسبه و ارزیابی شدهاست. اما در خط سوم تخصیص مقدار برای ثابت a به زمان اجرای برنامه موکول شده است. در نتیجه با بروز خطا مواجه میشویم .
فیلدهای فقط خواندنی ReadOnly
در #C فقط Fieldها را میتوان بصورت ReadOnly تعریف کرد. این فیلدها یا در زمان تعریف و یا از طریق سازنده مقدار دهی میشوند.
بررسی تفاوت readonly و const در سطح IL
برای مشاهده کدهای سطح میانی (IL Code) از ابزار خط فرمان Developer Command ویژوال استدیو 2017 و همچنین برنامه ILdasm استفاده شده است. همانطور که در جدول مقایسهای بیان شد، برای constant field ها تخصیص حافظهای صورت نمیگیرد و مقادیر مستقیما در کدهای IL گنجانده میشود.
مثال:
class Program { public const int numberOfDays = 7; public readonly double piValue = 3.14; static void Main(string[] args) { } }
اگر فایل Exe کد فوق را توسط نرم افزار IL Dasm مشاهده کنید، خواهید دید که مقدار ذخیره شده در numberOfDays در کد IL گنجانده شده است :
ولی مقدار ذخیره شده در piValue در زمان اجرا قابل دسترسی میباشد.
مشکل Versioning فیلدهای const
public const int numberOfDays = 7; public readonly double piValue = 3.14;
کد برنامه اصلی که ارجاعی به اسمبلی جانبی دارد:
static void Main(string[] args) { var readEx = new MyLib.TestClass(); var readConstValue = MyLib.TestClass.numberOfDays; var readReadOnlyValue = readEx.piValue; }
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 17 (0x11) .maxstack 1 .locals init ([0] class [MyLib]MyLib.TestClass readEx, [1] int32 readConstValue, [2] float64 readOnlyValue) IL_0000: nop IL_0001: newobj instance void [MyLib]MyLib.TestClass::.ctor() IL_0006: stloc.0 //readEx IL_0007: ldc.i4.7 //ارزش ذخیره شده در کد IL_0008: stloc.1 //readConstValue IL_0009: ldloc.0 //readEg IL_000a: ldfld float64 [MyLib]MyLib.TestClass::piValue IL_000f: stloc.2 //readReadOnlyValue IL_0010: ret } // end of method Program::Main
اگر در کتابخانه جانبی ارزش فیلد const را تغییر دهید و آن را مجدد کامپایل کنید، تا زمانیکه اسمبلی برنامه اصلی را کامپایل نکردهاید، همان ارزش قبلی در برنامه نمایش داده میشود.
برای غلبه بر این مشکل از فیلدهای Static ReadOnly استفاده میکنیم.
مثال:
public class ReadonlyStatic { public static readonly string x = "Hi"; public static readonly string y; public ReadonlyStatic() { //y = "Hello"; This is wrong } static ReadonlyStatic() { y = "Hello"; } }
اولین مشکلی که با استفاده از فیلدهای Static ReadOnly حل میشود، مشکل Versioning فیلدهای Const است. بدین ترتیب دیگر نیازی به کامپایل مجدد برنامه مصرف کننده نیست .
نکته بعدی که در کد فوق نشان داده شدهاست، فیلدهای static readOnly در زمان تعریف و یا تنها از طریق سازندهی static میتوانند مقدار دهی شوند.
مقایسه ReadOnly و Static :
ReadOnly | Static |
هم در زمان تعریف و هم از طریق سازنده میتوان آن را مقدار دهی کرد. | در زمان تعریف و تنها از طریق سازنده static میتوان آن را مقدار دهی کرد. |
مقدار بر اساس مقادیری که در سازندهها تعیین میشود متفاوت است. | مقادیر بعد از مقدار دهی اولیه تغییر نمیکنند. |
چه زمانی از Const و چه زمانی از ReadOnly استفاده کنیم :
- زمانی باید از Const استفاده کرد که مطمئن هستیم ارزش ذخیره شده در آن در طول عمر یک برنامه تغییر نمیکند. بطور مثال ذخیره تعداد روز هفته در یک فیلد از نوع Constant. اگر شک داریم که ممکن است این ارزش تغییر کند، میتوانیم از حالت static readOnly برای غلبه بر مشکل Versioning استفاده کنیم.
- از آنجائیکه مقادیر constant در کدهای IL گنجانده میشوند، برای رسیدن به کارآیی بهتر، مقادیری را که در طول عمر یک برنامه تغییر نمیکنند، به صورت const تعریف میکنیم.
- هر زمان تصمیم داشتیم Constant هایی به ازای هر وهله از کلاس داشته باشیم از ReadOnly استفاده میکنیم.
با توجه به مطالب مطرح شده در متن فوق و نحوه استفاده از ViewModel در ASP.NET MVC و همچنین توصیههایی که در رابطه با آدرسدهی صحیح WebApiها وجود دارد (استفاده از اسم جمع، استفاده از اسم به جای فعل و ...)، در رابطه با آدرسدهی صحیح برای تامین اطلاعات مورد نیاز Viewها (در حالت ویرایش یا افزودن)، در سمت کلاینت که استفاده کننده آن میتواند یک کامپوننت Angular یا هر نوع دیگری باشد آیا دوستان نظر و Best Practice دارند؟
با توجه به اینکه حالت فوق، احتمالاً دو متد از چند متد مورد استفاده میباشد، آیا دوستان درباره متدهای GetAdd , GetEdit و همچنین آدرسدهی صحیح این نوع متدها که قرار است از سمت کلاینت فراخوانی شود نظری دارند؟
به طور مثال برای ویرایش و افزودن یک محصول به صورت زیر عمل میکنیم:
//ViewModels public class CustomListItem { public int Id { get; set; } public string Text { get; set; } } public class ProductAddGetViewModel { public IEnumerable<CustomListItem> Categories { get; set; } public IEnumerable<CustomListItem> Groups { get; set; } } public class ProductAddViewModel { public string Name { get; set; } public bool IsActive { get; set; } public int CategoryId { get; set; } public int GroupId { get; set; } } public class ProductEditGetViewModel { public int Id { get; set; } public string Name { get; set; } public IEnumerable<CustomListItem> Categories { get; set; } public IEnumerable<CustomListItem> Groups { get; set; } } public class ProductEditViewModel { public int Id { get; set; } public string Name { get; set; } public int CategoryId { get; set; } public int GroupId { get; set; } }
و
// ProductsController - ApiControler // GET: api/products/views/add [HttpGet("views/add")] public async Task<IActionResult> GetAdd() { ProductAddGetViewModel model = await _productService.GetAddModelAsync(); return Ok(model) } // POST: api/products [HttpPost] public async Task<IActionResult> Add(ProductAddViewModel model) { ... } // GET: api/products/5/views/edit [HttpGet("{id}/views/edit")] public async Task<IActionResult> GetEdit(int id) { ProductEditGetViewModel model = await _productService.GetEditModelAsync(id); return Ok(model) } // PUT: api/products/5 [HttpPut("{id}")] public async Task<IActionResult> Edit(int id, ProductEditViewModel model) { ... }
پ.ن: درباره نامگذاری بهتر ViewModelها هم اگر نظری هست ممنون میشم بیان شود.
برای کار با React، نیاز است با ES6 آشنایی داشته باشید که در این سایت، یک سری کامل بررسی مقدمات آنرا پیشتر مرور کردهایم. علاوه بر توصیهی مطالعهی این سری (اینکار الزامی است)، در این قسمت خلاصهی بسیار سریع و کاربردی آنرا که بیشتر در برنامههای مبتنی بر React مورد استفاده قرار میگیرند، با هم مرور خواهیم کرد. در قسمتهای بعدی، اهمیت ذکر این خلاصه بیشتر مشخص میشود.
برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید React را ایجاد میکنیم.
سپس تمام کدهای داخل index.js را نیز حذف میکنیم. اکنون تمام کدهای خالص جاوا اسکریپتی خود را داخل این فایل خواهیم نوشت.
به علاوه چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود میتوانید مشاهده کنید (فشردن دکمهی F12).
var، let و const
در اکثر زبانهای برنامه نویسی، متغیرها در محدودهی دید قطعه کدی که تعریف شدهاند (scope)، قابل دسترسی هستند. برای نمونه محتوای فایل index.js پروژه را به صورت زیر تغییر داده و با فرض اجرای دستور npm start، خروجی آنرا میتوان در کنسول مرورگر مشاهده کرد.
در این مثال متغیر i، مخصوص قطعه کد حلقه، تعریف شدهاست. بنابراین به ظاهر نباید خارج از این حلقه نیز قابل دسترسی باشد. اما خروجی آن به صورت زیر است:
در آخرین پیمایش حلقه، i مساوی 5 شده و از حلقه خارج میشود. اما چون در اینجا برای تعریف متغیر از واژهی کلیدی var استفاده شدهاست، محدودهی دید آن به کل تابعی که در آن تعریف شدهاست، بسط پیدا میکند. به همین جهت در این خروجی، عدد 5 را نیز مشاهده میکند که حاصل دسترسی به i، خارج از حلقهاست.
برای یک دست سازی این رفتار با سایر زبانهای برنامه نویسی، در ES6، واژهی کلیدی جدیدی به نام let تعریف شدهاست که میدان دید متغیر را به قطعه کدی که در آن تعریف شدهاست، محدود میکند. اکنون اگر در حلقهی فوق بجای var از let استفاده شود، یک چنین خطایی در مرورگر ظاهر خواهد شد که عنوان میکند، i استفاده شدهی در خارج از حلقه، تعریف نشدهاست.
علاوه بر let، واژهی کلیدی جدید const نیز به ES6 اضافه شدهاست که از آن برای تعریف ثوابت استفاده میشود. constها نیز همانند let، میدان دید محدود شدهای به قطعه کد تعریف شدهی در آن دارند؛ اما قابلیت انتساب مجدد را ندارند:
اگر یک چنین قطعه کدی را اجرا کنیم، خطای x is const را در مرورگر میتوان مشاهده کرد.
به صورت خلاصه از این پس واژهی کلیدی var را فراموش کنید. همیشه با const جهت تعریف متغیرها شروع کنید. اگر به خطا برخوردید و نیاز به انتساب مجدد وجود داشت، آنرا به let تغییر دهید. بنابراین استفاده از const همیشه نسبت به let ارجحیت دارد.
اشیاء در جاوا اسکریپت
اشیاء در جاوا اسکریپت به صورت مجموعهای از key/valueها تعریف میشوند:
در اینجا امکان تعریف یک تابع نیز وجود دارد که چون درون یک شیء قرار میگیرد، اینبار «متد» نامیده میشود. همچنین در ES6 میتوان این متدها را به صورت معمولی، مانند متد talk نیز تعریف کرد که به آنها concise method میگویند. بنابراین نحوهی تعریف فوق را به نحو زیر نیز میتوان خلاصه کرد:
پس از تعریف این شیء، روش دسترسی به اجزای آن به صورت زیر است:
به دو مورد اول، روش dot notation میگویند که از همان ابتدا دقیقا مشخص است کدامیک از خواص و متدهای شیء تعریف شده، مورد استفاده قرار میگیرند.
مورد آخر همان روش استفاده از key/valueها است که اساس اشیاء جاوا اسکریپتی را تشکیل میدهد. البته از این روش فقط زمانی استفاده کنید که قرار است یکسری کار پویا صورت گیرند (مقدار key به صورت متغیر دریافت شود) و از ابتدا مشخص نیست که کدام خاصیت یا متد قرار است تعریف و استفاده شود:
واژهی کلیدی this در جاوا اسکریپت
از واژهی کلیدی this، در قسمتهای بعدی زیاد استفاده خواهیم کرد. به همین جهت نیاز است تفاوتهای اساسی آنرا با سایر زبانهای برنامه نویسی بررسی کنیم.
همان شیء person را که پیشتر تعریف کردیم درنظر بگیرید. در متد walk آن، مقدار this را لاگ میکنیم:
خروجی این قطعه، به صورت زیر است:
شیء this در جاوا اسکریپت، همانند سایر زبانهای برنامه نویسی مانند سیشارپ و یا جاوا رفتار نمیکند. در سایر زبانهای نامبرده شده، this همواره ارجاعی را به وهلهای از شیء جاری، باز میگرداند؛ دقیقا همانند تصویری که در بالا مشاهده میکنید. در اینجا نیز this جاوا اسکریپتی لاگ شده، ارجاعی را به وهلهی جاری شیء person، بازگشت دادهاست. اما مشکل اینجا است که this در جاوا اسکریپت، همیشه به این صورت رفتار نمیکند!
برای نمونه در ادامه یک ثابت را به نام walk تعریف کرده و آنرا به person.walk مقدار دهی میکنیم:
دقت داشته باشید که در اینجا از () استفاده نشدهاست (متد walk اجرا نشدهاست). یعنی صرفا «ارجاعی» از متد walk شیء person را به ثابت walk نسبت دادهایم. بنابراین اکنون ثابت walk نیز یک function است که حاصل console.log آن به صورت زیر است:
سؤال: اکنون اگر این function را با فراخوانی ()walk اجرا کنیم، چه خروجی را میتوان مشاهده کرد؟
اینبار this لاگ شده، به شیء person اشاره نمیکند و شیء استاندارد window مرورگر را بازگشت دادهاست!
اگر یک function به صورت متدی از یک شیء فراخوانی شود، مقدار this همواره اشارهگری به وهلهای از آن شیء خواهد بود. اما اگر این تابع به صورت متکی به خود و به صورت یک function و نه متد یک شیء، فراخوانی شود، اینبار this، شیء سراسری جاوا اسکریپت یا همان شیء window را بازگشت میدهد.
یک نکته: اگر strict mode جاوا اسکریپت را در پروژهی جاری فعال کنیم، بجای شیء window، مقدار undefined را در خروجی فوق شاهد خواهیم بود.
اتصال مجدد this به شیء اصلی در جاوا اسکریپت
تا اینجا دریافتیم که اگر یک function را به صورت متکی به خود و نه جزئی از یک شیء فراخوانی کنیم، شیء this در این حالت به شیء window سراسری مرورگر اشاره میکند و اگر strict mode فعال باشد، فقط undefined را بازگشت میهد. اکنون میخواهیم بررسی کنیم که چگونه میتوان این مشکل را برطرف کرد؛ یعنی صرفنظر از نحوهی فراخوانی متدها یا تابعها، this همواره ارجاعی را به شیء person بازگشت دهد.
در جاوا اسکریپت، تابعها نیز شیء هستند. برای مثال person.walk نوشته شده نیز یک شیء است. برای اثبات سادهی آن فقط یک دات را پس از person.walk قرار دهید:
همانطور که مشاهده میکنید، شیء person.walk مانند تمام اشیاء دیگر جاوا اسکریپت، به همراه متد bind نیز هست. کار آن، انقیاد یک تابع، به یک شیء است. یعنی هرچیزی را که به عنوان آرگومان آن، به آن ارسال کنیم، به عنوان مقدار شیء this درنظر میگیرد:
در اینجا متد bind، یک وهلهی جدید از person.walk را بازگشت میدهد که در آن شیء person را به عنوان شیء this، تنظیم کردهاست. به همین جهت اینبار فراخوانی walk2 که به شیء person متصل شدهاست، به this صحیحی بجای window سراسری اشاره میکند. از این روش در برنامههای مبتنی بر React زیاد استفاده میشود.
Arrow functions
تابع زیر را درنظر بگیرید که به یک ثابت انتساب داده شدهاست:
در ES6، روش سادهتر و تمیزتری برای این نوع تعاریف، ذیل ویژگی جدید Arrow functions اضافه شدهاست. برای تبدیل قطعه کد فوق به یک arrow function، ابتدا واژهی کلیدی function را حذف میکنیم. سپس بین پارامتر تابع و {}، یک علامت <= (که به آن fat arrow هم میگویند!) قرار میدهیم:
اگر مانند اینجا تنها یک تک پارامتر وجود داشته باشد، میتوان پرانتزهای ذکر شده را نیز حذف کرد:
و اگر این متد پارامتری نداشت، از () استفاده میشود.
در ادامه اگر بدنهی این تابع، فقط حاوی یک return بود، میتوان آنرا به صورت زیر نیز خلاصه کرد (در اینجا {} به همراه واژهی کلیدی return حذف میشوند):
این یک سطر ساده شده، دقیقا معادل اولین const square ای است که نوشتیم. نحوهی فراخوانی آن نیز مانند قبل است.
اکنون مثال مفید دیگری را در مورد Arrow functions بررسی میکنیم که بیشتر شبیه به عبارات LINQ در #C است:
در اینجا آرایهای از اشیاء job را مشاهده میکنید که مورد آخر آن، فعال نیست. اکنون میخواهیم لیست کارهای فعال را گزارشگیری کنیم:
متد filter در جاوا اسکریپت، بر روی تک تک عناصر آرایهی jobs حرکت میکند. سپس هر job را به پارامتر متد ارسالی آن که predicate نام دارد، جهت دریافت یک خروجی true و یا false، ارائه میدهد. اگر خروجی این متد true باشد، آن job انتخاب خواهد شد و در لیست نهایی گزارش، ظاهر میشود.
در ادامه میتوان این تابع را توسط arrow functions به صورت سادهتر زیر نیز نوشت:
ابتدا واژهی کلیدی function را حذف میکنیم. سپس چون یک تک پارامتر را دریافت میکند، نیازی به ذکر پرانتزهای آن نیز نیست. در ادامه چون یک تک return را داریم، { return } را با یک <= جایگزین خواهیم کرد. در اینجا نیازی به ذکر سمیکالن انتهای return هم نیست. نوشتن یک چنین کدی تمیزتر و خواندن آن، سادهتر است.
ارتباط بین arrow functions و شیء this
نکتهی مهمی را که باید در مورد arrow functions دانست این است که شیء this را rebind نمیکنند (rebind: مقدار دهی مجدد؛ ریست کردن).
در مثال زیر، ابتدا شیء user با متد talk که در آن شیء this، لاگ شده، ایجاد شده و سپس این متد فراخوانی گردیدهاست:
همانطور که انتظار میرود، this ای که در اینجا لاگ میشود، دقیقا ارجاعی را به وهلهی جاری شیء user دارد.
اکنون اگر متد لاگ کردن را داخل یک تایمر قرار دهیم چه اتفاقی رخ میدهد؟
متد setTimeout، متدی را که به عنوان آرگومان اول آن دریافت کرده، پس از 1 ثانیه اجرا میکند.
در این حالت خروجی console.log، مجددا همان شیء سراسری window مرورگر است و دیگر به وهلهی جاری شیء user اشاره نمیکند. علت اینجا است که پارامتر اول متد setTimeout که یک callback function نام دارد، جزئی از هیچ شیءای نیست. بنابراین دیگر مانند فراخوانی متد ()user.talk در مثال قبلی کار نمیکند؛ چون متکی به خود است. هر زمان که یک متد متکی به خود غیر وابستهی به یک شیء را اجرا کنیم، به صورت پیشفرض this آن، به شیء window مرورگر اشاره میکند.
سؤال: چگونه میتوان درون یک callback function متکی به خود، به this همان شیء user جاری دسترسی یافت؟
یک روش حل این مساله، ذخیره this شیء user در یک متغیر و سپس ارسال آن به متد متکی به خود setTimeout است:
این روشی است که سالها است وجود دارد؛ با ارائهی arrow functions، دیگر نیازی به اینکار نیست و میتوان از روش زیر استفاده کرد:
در اینجا callback function را تبدیل به یک arrow function کردهایم و چون arrow functions شیء this را rebind نمیکنند، یعنی شیء this را به ارث میبرند. بنابراین console.log مثال فوق، دقیقا به this شیء user اشاره میکند و دوباره آنرا مقدار دهی مجدد نمیکند و از همان نمونهی موجود قطعه کد تعریف شده، استفاده خواهد کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-02.zip
در قسمت بعد نیز بررسی پیشنیازهای جاوا اسکریپتی شروع به کار با React را ادامه خواهیم داد.
برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید React را ایجاد میکنیم.
> create-react-app sample-02 > cd sample-02 > npm start
به علاوه چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود میتوانید مشاهده کنید (فشردن دکمهی F12).
var، let و const
در اکثر زبانهای برنامه نویسی، متغیرها در محدودهی دید قطعه کدی که تعریف شدهاند (scope)، قابل دسترسی هستند. برای نمونه محتوای فایل index.js پروژه را به صورت زیر تغییر داده و با فرض اجرای دستور npm start، خروجی آنرا میتوان در کنسول مرورگر مشاهده کرد.
function sayHello() { for (var i = 0; i < 5; i++) { console.log(i); } console.log(i); } sayHello();
در آخرین پیمایش حلقه، i مساوی 5 شده و از حلقه خارج میشود. اما چون در اینجا برای تعریف متغیر از واژهی کلیدی var استفاده شدهاست، محدودهی دید آن به کل تابعی که در آن تعریف شدهاست، بسط پیدا میکند. به همین جهت در این خروجی، عدد 5 را نیز مشاهده میکند که حاصل دسترسی به i، خارج از حلقهاست.
برای یک دست سازی این رفتار با سایر زبانهای برنامه نویسی، در ES6، واژهی کلیدی جدیدی به نام let تعریف شدهاست که میدان دید متغیر را به قطعه کدی که در آن تعریف شدهاست، محدود میکند. اکنون اگر در حلقهی فوق بجای var از let استفاده شود، یک چنین خطایی در مرورگر ظاهر خواهد شد که عنوان میکند، i استفاده شدهی در خارج از حلقه، تعریف نشدهاست.
./src/index.js Line 14:15: 'i' is not defined no-undef Search for the keywords to learn more about each error.
علاوه بر let، واژهی کلیدی جدید const نیز به ES6 اضافه شدهاست که از آن برای تعریف ثوابت استفاده میشود. constها نیز همانند let، میدان دید محدود شدهای به قطعه کد تعریف شدهی در آن دارند؛ اما قابلیت انتساب مجدد را ندارند:
const x = 1; x = 2; // Attempting to override 'x' which is a constant.
به صورت خلاصه از این پس واژهی کلیدی var را فراموش کنید. همیشه با const جهت تعریف متغیرها شروع کنید. اگر به خطا برخوردید و نیاز به انتساب مجدد وجود داشت، آنرا به let تغییر دهید. بنابراین استفاده از const همیشه نسبت به let ارجحیت دارد.
اشیاء در جاوا اسکریپت
اشیاء در جاوا اسکریپت به صورت مجموعهای از key/valueها تعریف میشوند:
const person = { name: "User 1", walk: function() {}, // method talk() {} // concise method };
const person = { name: "User 1", walk() {}, talk() {} };
person.talk(); person.name = "User 3"; person["name"] = "User 2";
مورد آخر همان روش استفاده از key/valueها است که اساس اشیاء جاوا اسکریپتی را تشکیل میدهد. البته از این روش فقط زمانی استفاده کنید که قرار است یکسری کار پویا صورت گیرند (مقدار key به صورت متغیر دریافت شود) و از ابتدا مشخص نیست که کدام خاصیت یا متد قرار است تعریف و استفاده شود:
const targetMember = "name"; person[targetMember] = "User 2";
واژهی کلیدی this در جاوا اسکریپت
از واژهی کلیدی this، در قسمتهای بعدی زیاد استفاده خواهیم کرد. به همین جهت نیاز است تفاوتهای اساسی آنرا با سایر زبانهای برنامه نویسی بررسی کنیم.
همان شیء person را که پیشتر تعریف کردیم درنظر بگیرید. در متد walk آن، مقدار this را لاگ میکنیم:
const person = { name: "User 1", walk() { console.log(this); }, talk() {} }; person.walk();
شیء this در جاوا اسکریپت، همانند سایر زبانهای برنامه نویسی مانند سیشارپ و یا جاوا رفتار نمیکند. در سایر زبانهای نامبرده شده، this همواره ارجاعی را به وهلهای از شیء جاری، باز میگرداند؛ دقیقا همانند تصویری که در بالا مشاهده میکنید. در اینجا نیز this جاوا اسکریپتی لاگ شده، ارجاعی را به وهلهی جاری شیء person، بازگشت دادهاست. اما مشکل اینجا است که this در جاوا اسکریپت، همیشه به این صورت رفتار نمیکند!
برای نمونه در ادامه یک ثابت را به نام walk تعریف کرده و آنرا به person.walk مقدار دهی میکنیم:
const walk = person.walk; console.log(walk);
سؤال: اکنون اگر این function را با فراخوانی ()walk اجرا کنیم، چه خروجی را میتوان مشاهده کرد؟
اینبار this لاگ شده، به شیء person اشاره نمیکند و شیء استاندارد window مرورگر را بازگشت دادهاست!
اگر یک function به صورت متدی از یک شیء فراخوانی شود، مقدار this همواره اشارهگری به وهلهای از آن شیء خواهد بود. اما اگر این تابع به صورت متکی به خود و به صورت یک function و نه متد یک شیء، فراخوانی شود، اینبار this، شیء سراسری جاوا اسکریپت یا همان شیء window را بازگشت میدهد.
یک نکته: اگر strict mode جاوا اسکریپت را در پروژهی جاری فعال کنیم، بجای شیء window، مقدار undefined را در خروجی فوق شاهد خواهیم بود.
اتصال مجدد this به شیء اصلی در جاوا اسکریپت
تا اینجا دریافتیم که اگر یک function را به صورت متکی به خود و نه جزئی از یک شیء فراخوانی کنیم، شیء this در این حالت به شیء window سراسری مرورگر اشاره میکند و اگر strict mode فعال باشد، فقط undefined را بازگشت میهد. اکنون میخواهیم بررسی کنیم که چگونه میتوان این مشکل را برطرف کرد؛ یعنی صرفنظر از نحوهی فراخوانی متدها یا تابعها، this همواره ارجاعی را به شیء person بازگشت دهد.
در جاوا اسکریپت، تابعها نیز شیء هستند. برای مثال person.walk نوشته شده نیز یک شیء است. برای اثبات سادهی آن فقط یک دات را پس از person.walk قرار دهید:
همانطور که مشاهده میکنید، شیء person.walk مانند تمام اشیاء دیگر جاوا اسکریپت، به همراه متد bind نیز هست. کار آن، انقیاد یک تابع، به یک شیء است. یعنی هرچیزی را که به عنوان آرگومان آن، به آن ارسال کنیم، به عنوان مقدار شیء this درنظر میگیرد:
const walk2 = person.walk.bind(person); console.log(walk2); walk2();
Arrow functions
تابع زیر را درنظر بگیرید که به یک ثابت انتساب داده شدهاست:
const square = function(number) { return number * number; };
const square2 = (number) => { return number * number; };
const square2 = number => { return number * number; };
در ادامه اگر بدنهی این تابع، فقط حاوی یک return بود، میتوان آنرا به صورت زیر نیز خلاصه کرد (در اینجا {} به همراه واژهی کلیدی return حذف میشوند):
const square3 = number => number * number; console.log(square3(5));
اکنون مثال مفید دیگری را در مورد Arrow functions بررسی میکنیم که بیشتر شبیه به عبارات LINQ در #C است:
const jobs = [ { id: 1, isActive: true }, { id: 2, isActive: true }, { id: 3, isActive: true }, { id: 4, isActive: true }, { id: 5, isActive: false } ];
var activeJobs = jobs.filter(function(job) { return job.isActive; });
در ادامه میتوان این تابع را توسط arrow functions به صورت سادهتر زیر نیز نوشت:
var activeJobs2 = jobs.filter(job => job.isActive);
ارتباط بین arrow functions و شیء this
نکتهی مهمی را که باید در مورد arrow functions دانست این است که شیء this را rebind نمیکنند (rebind: مقدار دهی مجدد؛ ریست کردن).
در مثال زیر، ابتدا شیء user با متد talk که در آن شیء this، لاگ شده، ایجاد شده و سپس این متد فراخوانی گردیدهاست:
const user = { name: "User 1", talk() { console.log(this); } }; user.talk();
اکنون اگر متد لاگ کردن را داخل یک تایمر قرار دهیم چه اتفاقی رخ میدهد؟
const user = { name: "User 1", talk() { setTimeout(function() { console.log(this); }, 1000); } }; user.talk();
در این حالت خروجی console.log، مجددا همان شیء سراسری window مرورگر است و دیگر به وهلهی جاری شیء user اشاره نمیکند. علت اینجا است که پارامتر اول متد setTimeout که یک callback function نام دارد، جزئی از هیچ شیءای نیست. بنابراین دیگر مانند فراخوانی متد ()user.talk در مثال قبلی کار نمیکند؛ چون متکی به خود است. هر زمان که یک متد متکی به خود غیر وابستهی به یک شیء را اجرا کنیم، به صورت پیشفرض this آن، به شیء window مرورگر اشاره میکند.
سؤال: چگونه میتوان درون یک callback function متکی به خود، به this همان شیء user جاری دسترسی یافت؟
یک روش حل این مساله، ذخیره this شیء user در یک متغیر و سپس ارسال آن به متد متکی به خود setTimeout است:
const user2 = { name: "User 2", talk() { var self = this; setTimeout(function() { console.log(self); }, 1000); } }; user2.talk();
const user3 = { name: "User 3", talk() { setTimeout(() => console.log(this), 1000); } }; user3.talk();
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-02.zip
در قسمت بعد نیز بررسی پیشنیازهای جاوا اسکریپتی شروع به کار با React را ادامه خواهیم داد.
نظرات مطالب
EF Code First #7
به ازای هر FK یک Collection مجزا باید داشته باشید و همچنین از Fluent API هم باید استفاده کنید:
public class User { public int UserId { get; set;} public string Name { get; set; } public virtual ICollection<Comment> HomeCommentes { get; set; } public virtual ICollection<Comment> AwayCommentes { get; set; } } public class Comment { public int CommentId { get; set; } public int HomeUserId { get; set; } public int GuestUserId { get; set; } public virtual User HomeUser { get; set; } public virtual User GuestUser { get; set; } } public class Context : DbContext { ... protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Comment>() .HasRequired(m => m.HomeUser) .WithMany(t => t.HomeCommentes) .HasForeignKey(m => m.HomeUserId) .WillCascadeOnDelete(false); modelBuilder.Entity<Comment>() .HasRequired(m => m.GuestUser) .WithMany(t => t.AwayCommentes) .HasForeignKey(m => m.GuestUserId) .WillCascadeOnDelete(false); } }
نظرات مطالب
EF Code First #1
سلام مهندس نصیری، چرا این کد توی EF5 خطای کلید خارجی میده؟
کدش از کتاب Code First که معرفی کردین استفاده کردم اما کد خودتون خطا نداره
کد کانتکست
و در تابع Main برنامه Console این نوشته شده اما خطا میده و ثبت نمیشه
کدهای دیگه تست کردم مشکلی نداشت اما این مورد ؟
با profiler چک کردم خطای عدم توانایی در تبدیل نوع datetime2 به datetime میده
کدش از کتاب Code First که معرفی کردین استفاده کردم اما کد خودتون خطا نداره
using System; using System.Collections.Generic; namespace ChapterOneProject { public class Patient { public Patient() { Visits = new List<Visit>(); } public int Id { get; set; } public string Name { get; set; } public DateTime BirthDate { get; set; } //[ForeignKey("AnimalTypeId")] public AnimalType AnimalType { get; set; } //public int AnimalTypeId { get; set; } public DateTime FirstVisit { get; set; } public List<Visit> Visits { get; set; } } public class Visit { [Key] public int Id { get; set; } public DateTime Date { get; set; } public String ReasonForVisit { get; set; } public String Outcome { get; set; } public Decimal Weight { get; set; } //[ForeignKey("PatientId")] //public virtual Patient Patient { get; set; } public int PatientId { get; set; } } public class AnimalType { public int Id { get; set; } public string TypeName { get; set; } } }
کد کانتکست
public class VetContext : DbContext { public DbSet<Patient> Patients { get; set; } public DbSet<Visit> Visits { get; set; } //public DbSet<AnimalType> AnimalTypes { get; set; } }
var dog = new AnimalType { TypeName = "Dog" }; var visit = new List<Visit> { new Visit { Date = new DateTime(2011, 9, 1), Outcome = "Test", ReasonForVisit = "Test", Weight = 32, } }; var patient = new Patient { Name = "Sampson", BirthDate = new DateTime(2008, 1, 28), AnimalType = dog, Visits = visit, }; using (var context = new VetContext()) { context.Patients.Add(patient); context.SaveChanges(); }
کدهای دیگه تست کردم مشکلی نداشت اما این مورد ؟
با profiler چک کردم خطای عدم توانایی در تبدیل نوع datetime2 به datetime میده
نکته تکمیلی :
ایجاد منوهای چند سطحی با استفاده از ViewComponent تا N سطح
کلاس Entity :
public class Navbar { public int Id { get; set; } public string Title { get; set; } public int? ParentId { get; set; } public virtual Navbar Parent { get; set; } public bool IsActive { get; set; } public bool HasChiled { get; set; } public bool IsMegaMenu { get; set; } public PageGroup PageGroup { get; set; } public string Url { get; set; } public bool OpenNewPage { get; set; } public virtual ICollection<Navbar> Children { get; set; } }
public class TopNavbar : ViewComponent { private readonly DbSet<Navbar> _navbars; private readonly AppDbContext _dbContext; public TopNavbar(AppDbContext dbContext) { _dbContext = dbContext; _navbars = _dbContext.Set<Navbar>(); } public async Task<IViewComponentResult> InvokeAsync() { var navbars = await _navbars.Include(p=>p.Parent).Include(x=>x.Children).OrderBy(x=>x.ParentId).ToListAsync(); return View(viewName: "~/Views/Shared/Components/NavbarViewComponent/_Menu.cshtml", navbars); } }
<ul class="menu"> <li> <a href="Index_demo6.html"><i class="menu_icon_wrapper fal fa-home-lg-alt"></i>صفحه اصلی</a> </li> @await Component.InvokeAsync("TopNavbar"); </ul>
Menu.cshtml_ :
@using TR.Context.Entities @using Microsoft.AspNetCore.Html @model IEnumerable<TR.Context.Entities.Navbar> @foreach (var menu in Model.Where(x => x.Parent == null)) { <li class="@(menu.HasChiled ? "has_sub narrow" : "")"> <a href="#">@menu.Title</a> @if (menu.HasChiled) { <div class="second"> <div class="inner"> <ul> @foreach (var menuChild in menu.Children) { <partial name="~/Views/Shared/Components/NavbarViewComponent/_SubMenu.cshtml" model="menuChild" /> } </ul> </div> </div> } </li> }
پارشیال ویو SubMenu.cshtml_
@model TR_.Context.Entities.Navbar <li class="@(Model.HasChiled ? "sub":"")"> <a href="#"> @if (Model.Children.Any()) {<i class="q_menu_arrow fal fa-angle-left"></i>} @Model.Title </a> @if (Model.Children.Any()) { <ul> @foreach (var menuChild in Model.Children) { <partial name="~/Views/Shared/Components/NavbarViewComponent/_SubMenu.cshtml" model="menuChild" /> } </ul> } </li>