In my earlier post Getting Started: Xamarin Forms with .NET Standard I covered how to create a new Xamarin Forms project which uses a .NET Standard 1.4 library to share the views between iOS, Android and UWP.
بازخوردهای دوره
لغو Lazy Loading در حین کار با AutoMapper و Entity Framework
سلام؛ ما در پروژه هامون معمولا کار Object Mapping رو توی لایه Repository انجام نمیدیم و در لایه Service کار تبدیل Model به ViewModel رو انجام میدیم ..
var jobViewModel = Mapper.Map<Job>(jobViewModel);
به نظرتون استفاده مستقیم از متد ProjectTo در AutoMapper در لایه ای مثل Repository کار درستیه ؟
مطالب دورهها
ساخت یک Mini ORM با AutoMapper
Mini ORMها برخلاف ORMهای کاملی مانند Entity framework یا NHibernate، کوئریهای LINQ را تبدیل به SQL نمیکنند. در اینجا کار با SQL نویسی مستقیم شروع میشود و مهمترین کار این کتابخانهها، نگاشت نتیجهی دریافتی از بانک اطلاعاتی به اشیاء دات نتی هستند. خوب ... AutoMapper هم دقیقا همین کار را انجام میدهد! بنابراین در ادامه قصد داریم یک Mini ORM را به کمک AutoMapper طراحی کنیم.
کلاس پایه AdoMapper
در اینجا کلاس پایه Mini ORM طراحی شده را ملاحظه میکنید. برای نمونه قسمت GetRecords آن مانند مباحث استاندارد ADO.NET است. فقط کار خواندن و همچنین نگاشت رکوردهای دریافت شده از بانک اطلاعاتی به شیءایی از نوع T توسط AutoMapper انجام خواهد شد.
نحوهی استفاده از کلاس پایه AdoMapper
در کدهای ذیل نحوهی ارث بری از کلاس پایه AdoMapper و سپس استفاده از متدهای آنرا ملاحظه میکنید:
در این مثال نحوهی تعریف کوئریهای پارامتری نیز در متد GetById به نحو متداولی مشخص شدهاست. کار نگاشت حاصل این کوئریها به اشیاء دات نتی را AutoMapper انجام خواهد داد. نحوهی کار نیز، نگاشت فیلد f1 به خاصیت f1 است (هم نامها به هم نگاشت میشوند).
تعریف پروفایل مخصوص AutoMapper
ORMهای تمام عیار، کار نگاشت فیلدهای بانک اطلاعاتی را به خواص اشیاء دات نتی، به صورت خودکار انجام میدهند. در اینجا همانند روشهای متداول کار با AutoMapper نیاز است این نگاشت را به صورت دستی یکبار تعریف کرد:
و سپس در ابتدای برنامه آنرا به AutoMapper معرفی نمود:
سفارشی سازی نگاشتهای AutoMapper
فرض کنید کلاس Advertisement زیر، معادل است با جدول Advertisements بانک اطلاعاتی؛ با این تفاوت که در کلاس تعریف شده، خاصیت TitleWithOtherName تطابقی با هیچکدام از فیلدهای بانک اطلاعاتی ندارد. بنابراین اطلاعاتی نیز به آن نگاشت نخواهد شد.
برای رفع این مشکل میتوان حین تعریف پروفایل مخصوص Advertisement، آنرا سفارشی سازی نیز نمود:
در اینجا پس از تعریف نگاشت مخصوص کار با IDataRecordها، عنوان شدهاست که هر زمانیکه به خاصیت TitleWithOtherName رسیدی، مقدارش را از فیلد Title دریافت و جایگزین کن.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
کلاس پایه AdoMapper
public abstract class AdoMapper<T> where T : class { private readonly SqlConnection _connection; protected AdoMapper(string connectionString) { _connection = new SqlConnection(connectionString); } protected virtual IEnumerable<T> ExecuteCommand(SqlCommand command) { command.Connection = _connection; command.CommandType = CommandType.StoredProcedure; _connection.Open(); try { var reader = command.ExecuteReader(); try { return Mapper.Map<IDataReader, IEnumerable<T>>(reader); } finally { reader.Close(); } } finally { _connection.Close(); } } protected virtual T GetRecord(SqlCommand command) { command.Connection = _connection; _connection.Open(); try { var reader = command.ExecuteReader(); try { reader.Read(); return Mapper.Map<IDataReader, T>(reader); } finally { reader.Close(); } } finally { _connection.Close(); } } protected virtual IEnumerable<T> GetRecords(SqlCommand command) { command.Connection = _connection; _connection.Open(); try { var reader = command.ExecuteReader(); try { return Mapper.Map<IDataReader, IEnumerable<T>>(reader); } finally { reader.Close(); } } finally { _connection.Close(); } } }
نحوهی استفاده از کلاس پایه AdoMapper
در کدهای ذیل نحوهی ارث بری از کلاس پایه AdoMapper و سپس استفاده از متدهای آنرا ملاحظه میکنید:
public class UsersService : AdoMapper<User>, IUsersService { public UsersService(string connectionString) : base(connectionString) { } public IEnumerable<User> GetAll() { using (var command = new SqlCommand("SELECT * FROM Users")) { return GetRecords(command); } } public User GetById(int id) { using (var command = new SqlCommand("SELECT * FROM Users WHERE Id = @id")) { command.Parameters.Add(new SqlParameter("id", id)); return GetRecord(command); } } }
تعریف پروفایل مخصوص AutoMapper
ORMهای تمام عیار، کار نگاشت فیلدهای بانک اطلاعاتی را به خواص اشیاء دات نتی، به صورت خودکار انجام میدهند. در اینجا همانند روشهای متداول کار با AutoMapper نیاز است این نگاشت را به صورت دستی یکبار تعریف کرد:
public class UsersProfile : Profile { protected override void Configure() { this.CreateMap<IDataRecord, User>(); } public override string ProfileName { get { return this.GetType().Name; } } }
Mapper.Initialize(cfg => // In Application_Start() { cfg.AddProfile<UsersProfile>(); });
سفارشی سازی نگاشتهای AutoMapper
فرض کنید کلاس Advertisement زیر، معادل است با جدول Advertisements بانک اطلاعاتی؛ با این تفاوت که در کلاس تعریف شده، خاصیت TitleWithOtherName تطابقی با هیچکدام از فیلدهای بانک اطلاعاتی ندارد. بنابراین اطلاعاتی نیز به آن نگاشت نخواهد شد.
public class Advertisement { public int Id { set; get; } public string Title { get; set; } public string Description { get; set; } public int UserId { get; set; } public string TitleWithOtherName { get; set; } }
public class AdvertisementsProfile : Profile { protected override void Configure() { this.CreateMap<IDataRecord, Advertisement>() .ForMember(dest => dest.TitleWithOtherName, options => options.MapFrom(src => src.GetString(src.GetOrdinal("Title")))); } public override string ProfileName { get { return this.GetType().Name; } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
نگاشت اشیاء امری مفید و لذت بخش است. ولی بخاطر تنظیمات خاص آن و افزایش کدها، همیشه کمی دردسر ساز بوده است. استفاده از کلاس 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 تبدیل کنیم.
دریافت کدها
ادامه دارد...
اشتراکها
Git for Windows 2.19.0 منتشر شد
New Features
Comes with Git v2.19.0.
There are now fast, built-in versions of git stash and git rebase, available as experimental options.
The included OpenSSH client now enables modern ciphers.
The gitweb component was removed because it is highly unlikely to be used on Windows.
The git archimport tool (which was probably used by exactly 0 users) is no longer included in Git for Windows.