ضمن تشکر از آقای نصیری بخاطر معرفی روش دیگر، توجه فرمایید من تنها یک معرفی اجمالی درباره این ابزار داشتم. این ابزار امکانات دیگری نیز دارد که بطور خاص برخی از آنها به شرح زیر است:
یک نکتهی دیگر:
ممکن هست جهت پخش فایلهای mp4 ، از یک سری codec استفاده کرده باشید. مثلا: Haali's Media Splitter و FFDShow
تا زمانیکه در media player ویندوز در پاسخ به سؤال «فایلهای mp4 را هم پخش کنم یا نه؟»، گزینهی به خاطر سپاری پاسخ را تیک نزده باشید، در WPF Media Element با خطای زیر مواجه خواهید شد:
Media file download failed.
Exception from HRESULT: 0xC00D0FEA
ممکن هست جهت پخش فایلهای mp4 ، از یک سری codec استفاده کرده باشید. مثلا: Haali's Media Splitter و FFDShow
تا زمانیکه در media player ویندوز در پاسخ به سؤال «فایلهای mp4 را هم پخش کنم یا نه؟»، گزینهی به خاطر سپاری پاسخ را تیک نزده باشید، در WPF Media Element با خطای زیر مواجه خواهید شد:
Media file download failed.
Exception from HRESULT: 0xC00D0FEA
پس از معرفی ویژگیهای لازم، در ادامه با نحوهی تبدیل این ویژگیها به معادل نگاشت آنها در automapper خواهم پرداخت.
متد زیر هستهی اصلی عملیات است و کلیهی نگاشتهای لازم را انجام میدهد. این متد وظیفهی تبدیل نگاشتها را دارد. نگاشتهایی که با Attributes مشخص شدهاند:
ورودی این متد اسملبی مربوط به ویوومدل میباشد (برای زمانیکه ویوومدلها در اسمبلی دیگری باشند).
در سطر اول، اقدام به رجیستر کردن کلیهی مبدلهای سراسری میکنیم. در این سطر مبدل تاریخ به کوچی خورشیدی مورد استفاده قرار گرفته است. سپس در اسمبلی داده شده، کلیه نوعهایی که ویژگی MapFromAttribute را دارند، یافته و جدا میکنیم. در حلقهی foreach ابتدا نگاشت نوع مبدأ و مقصد را انجام میدهیم. خروجی این متد از نوع IMappingExpression است. گر چه این اینترفیس برای تغییر بسته است، ولی قابل توسعه میباشد و عملیات را توسط متدهای الحاقی انجام میدهیم(اصل OCP).
اگر به نحوهی نامگذاری متدهای الحاقی تعریف شده دقت کرده باشید، تنها کلمهی Do به ابتدای نام ویژگیها اضافه شده است.
متد الحاقی DoMapFormMemberAttribute
هر IMappingExpression دارای امکاناتی برای نگهداری و انجام فعالیت بر روی یک نگاشت میباشد. در کوئری ابتدای متد، کلیهی پروپرتیهایی را که دارای ویژگی MapForMemeberAttribute میباشند، یافته و جدا میکنیم. این پروپرتیها از نظر معادل اسمی در نوع مبدأ و مقصد متفاوت هستند. سپس در حلقه، کار اتصال پروپرتی مبدأ و مقصد صورت میگیرد.
متد الحاقی DoIgnoreMapAttribute
این متد کلیهی پروپرتیهایی را که دارای ویژگی IgnoreMapAttribute باشند، از گردونهی نگاشت automapper خارج میکند. به عنوان مثال پروپرتی Password در ویوومدل مربوط به تغییر گذرواژه را نظر بگیرید. این پروپرتی نباید مقدار معادلی در شیء EF داشته باشد. از طرفی هم باید در ویوو وجودداشته باشد. با استفاده از این ویژگی هیچ نگاشتی انجام نمیشود و میتوان تضمین کرد که گذرواژه به ویوومدل و ویوو راه پیدا نمیکند.
متد الحاقی DoUseValueResolverAttribute
به شیوهی قبل، ابتدا نوع هایی را که دارای ویژگی UseValueResolverAttribute باشند، یافته و جدا میکنیم. سپس در حلقه، کار نگاشت متناظر در automapper انجام میگیرد. لازم به ذکر است که متد opt.ResolveUsing یک شیء با کارآیی (can do) اینترفیس IValueResolver را به عنوان آرگومان میگیرد.
متد الحاقی DoIgnoreAllNonExisting
این متد برحسب پرچم تعیین شده در هنگام بکارگیری ویژگی MapFromAttribute رفتار میکند. به این صورت که اگر موقع تعریف، مقدار IgnoreAllNonExistingProperty را صحیح اعلام کنیم، تمام پروپرتیهای مقصد را که معادل اسمی در مبدأ نداشته باشند و همچنین هیچگونه تنظیمی جهت مشخص سازی تکلیف نگاشت آنها صورت نگرفته باشد، از گردونهی نگاشت Automapper خارج میکند.
توضیح تکمیلی: پس از تنظیم کلیهی نگاشتها در automapper جهت اطمینان از صحت تنظیمات، فراخوانی متد AutoMapper.Mapper.AssertConfigurationIsValid الزامی است. یکی از عواملی که باعث شکست این متد میشود، وجود پروپرتیهایی در نوع مقصد است، بطوریکه معادل اسمی در نوع مبدأ نداشته باشند و یا تنظیمی جهت مشخص سازی نگاشت آن انجام نشده باشد (پروپرتی که قابل نگاشت نباشد). در حقیقت این شکست بسیار مفید است. به این صورت که اگر این شکست صورت نگیرد در حین نگاشت مقادیر، باید از null یا مقدار default بدون اطلاع برنامه نویس برای مقداردهی پروپرتی استفاده کند و این یک حالت نامعلوم شیء است. اگر میخواهید این پروپرتیها مقدار پیشفرضی بگیرند و همچنین باعث شکست عملیات هم نشوند، باید بطور صریح این موضوع را اعلام کنید. این اعلام یا باید به همین روش صورت بگیرد یا باید از ویژگی IgnorMapAttribute استفاده شود. تنها تفاوت این دو، نحوهی اعمال تنظیم میباشد. IgnorMapAttribute باید روی تک تک پروپرتیهای مدنظر قرار گیرد، ولی در روش اول تنها کافیست که مقدار true تنظیم گردد. بهنظر استفاده از IgnoreMapAttribute باعث طولانی شدن کدها میشود؛ اما توصیه میشود که از همین شیوه استفاده کنید.
تا اینجا کدهای مورد نیاز نوشته شدند. در ادامه به ارائهی یک مثال برای نگاشت اشیاء در Automapper توسط Attributeها میپردازم.
مدل سادهی زیر را در نظر بگیرید:
با ویوومدل متناظر ذیل:
در تنظیم ویژگی MapFromAttribute ابتدا نوع مبدأ (Student) را مشخص کردیم و بعد صراحتاً گفتیم که از نگاشت پروپرتیهای بلاتکلیف صرف نظر کند و همچنین پرچم انتقال Data Annotationهای EF به ویوومدل را هم برافراشتیم. توسط MapForMember پروپرتی FirstName را به پروپرتی Name در مبدأ تنظیم کردیم و LastName را به Family. همچنین Email را بصورت صریح از نگاشت شدن منع کردیم. پروپرتی BookCounts تعداد کتابها را محاسبه میکند و TotalBookPrice قیمت کلیهی کتابها را. برای این موارد از تأمین کنندهی داده (Value Resolver) استفاده کردیم. این تأمین کنندهها میتوانند اینچنین پیاده سازی شوند:
نحوهی پیکربندی و مشاهدهی نتایج را در یک برنامهی تحت کنسول پیاده سازی کردم. متد Main آن میتواند اینچنین باشد:
ابتدا اسمبلی مربوط به ویوومدلها را مشخص میکنیم. سپس این اسمبلی را جهت تبدیل ویژگیها به نگاشتهای معتبر automapper به متد Initialize ارسال میکنیم. تنها بکار بردن همین دوسطر برای اعمال تنظیمها مورد نیاز میباشد. بعد از اجرای موفق متد Initialize، نگاشتهای اشیاء آماده هستند.
نمونهی خروجی:
دریافت کدها + مثال
متد زیر هستهی اصلی عملیات است و کلیهی نگاشتهای لازم را انجام میدهد. این متد وظیفهی تبدیل نگاشتها را دارد. نگاشتهایی که با Attributes مشخص شدهاند:
public static void Initialize(Assembly assembly) { //register global convertors. AutoMapper.Mapper.CreateMap<DateTime, string>().ConvertUsing<DateTimeToPersianDateTimeConverter>(); var typesToMap = from t in assembly.GetTypes() let attr = t.GetCustomAttribute<MapFromAttribute>() where attr != null select new {SourceType = attr.SourceType, Destination = t, Attribute = attr}; foreach (var map in typesToMap) { AutoMapper.Mapper.CreateMap(map.SourceType, map.Destination) .DoMapForMemberAttribute() // for different property names in source and destination .DoIgnoreMapAttribute()// ignore specified properties .DoUseValueResolverAttribute()// set value resolvers .DoIgnoreAllNonExisting()// its have to be the latest. ; } //endeach AutoMapper.Mapper.AssertConfigurationIsValid(); }
در سطر اول، اقدام به رجیستر کردن کلیهی مبدلهای سراسری میکنیم. در این سطر مبدل تاریخ به کوچی خورشیدی مورد استفاده قرار گرفته است. سپس در اسمبلی داده شده، کلیه نوعهایی که ویژگی MapFromAttribute را دارند، یافته و جدا میکنیم. در حلقهی foreach ابتدا نگاشت نوع مبدأ و مقصد را انجام میدهیم. خروجی این متد از نوع IMappingExpression است. گر چه این اینترفیس برای تغییر بسته است، ولی قابل توسعه میباشد و عملیات را توسط متدهای الحاقی انجام میدهیم(اصل OCP).
اگر به نحوهی نامگذاری متدهای الحاقی تعریف شده دقت کرده باشید، تنها کلمهی Do به ابتدای نام ویژگیها اضافه شده است.
متد الحاقی DoMapFormMemberAttribute
public static IMappingExpression DoMapForMemberAttribute(this IMappingExpression expression) { var ok = from p in expression.TypeMap.DestinationType.GetProperties() let attr = p.GetCustomAttribute<MapForMemberAttribute>() where attr != null select new {AttributeValue = attr, PropertyName = p.Name}; foreach (var property in ok) { expression.ForMember(property.PropertyName, opt => opt.MapFrom(property.AttributeValue.MemberToMap)); } return expression; }
متد الحاقی DoIgnoreMapAttribute
public static IMappingExpression DoIgnoreAttribute(this IMappingExpression expression) { foreach (var property in expression.TypeMap.DestinationType.GetProperties() .Where(x => x.GetCustomAttribute<IgnoreMapAttribute>() != null)) { expression.ForMember(property.Name, opt => opt.Ignore()); } return expression; }
متد الحاقی DoUseValueResolverAttribute
public static IMappingExpression DoUseValueResolverAttribute(this IMappingExpression expression) { var ok = from p in expression.TypeMap.DestinationType.GetProperties() let attr = p.GetCustomAttribute<UseValueResolverAttribute>() where attr != null select new {AttributeValue = attr, PropertyName = p.Name}; foreach (var property in ok) { expression.ForMember(property.PropertyName, opt => opt.ResolveUsing(property.AttributeValue.ValueResolver)); } return expression; }
متد الحاقی DoIgnoreAllNonExisting
public static IMappingExpression DoIgnoreAllNonExisting(this IMappingExpression expression) { var attr = expression.TypeMap.DestinationType.GetCustomAttribute<MapFromAttribute>(); if (attr?.IgnoreAllNonExistingProperty == false)//instead of if(attr == null || attr.IgnoreAllNonExistingProperty == false) return expression; foreach (var property in expression.TypeMap.GetUnmappedPropertyNames()) { expression.ForMember(property, opt => opt.Ignore()); } return expression; }
توضیح تکمیلی: پس از تنظیم کلیهی نگاشتها در automapper جهت اطمینان از صحت تنظیمات، فراخوانی متد AutoMapper.Mapper.AssertConfigurationIsValid الزامی است. یکی از عواملی که باعث شکست این متد میشود، وجود پروپرتیهایی در نوع مقصد است، بطوریکه معادل اسمی در نوع مبدأ نداشته باشند و یا تنظیمی جهت مشخص سازی نگاشت آن انجام نشده باشد (پروپرتی که قابل نگاشت نباشد). در حقیقت این شکست بسیار مفید است. به این صورت که اگر این شکست صورت نگیرد در حین نگاشت مقادیر، باید از null یا مقدار default بدون اطلاع برنامه نویس برای مقداردهی پروپرتی استفاده کند و این یک حالت نامعلوم شیء است. اگر میخواهید این پروپرتیها مقدار پیشفرضی بگیرند و همچنین باعث شکست عملیات هم نشوند، باید بطور صریح این موضوع را اعلام کنید. این اعلام یا باید به همین روش صورت بگیرد یا باید از ویژگی IgnorMapAttribute استفاده شود. تنها تفاوت این دو، نحوهی اعمال تنظیم میباشد. IgnorMapAttribute باید روی تک تک پروپرتیهای مدنظر قرار گیرد، ولی در روش اول تنها کافیست که مقدار true تنظیم گردد. بهنظر استفاده از IgnoreMapAttribute باعث طولانی شدن کدها میشود؛ اما توصیه میشود که از همین شیوه استفاده کنید.
تا اینجا کدهای مورد نیاز نوشته شدند. در ادامه به ارائهی یک مثال برای نگاشت اشیاء در Automapper توسط Attributeها میپردازم.
مدل سادهی زیر را در نظر بگیرید:
public class Student { public virtual int Id { set; get; } public virtual string Name { set; get; } public virtual string Family { set; get; } public virtual string Email { set; get; } public virtual DateTime RegisterDateTime { set; get; } public virtual ICollection<Book> Books { set; get; } } public class Book { public virtual int Id { set; get; } public virtual string Name { set; get; } public virtual DateTime BorrowDateTime { set; get; } public virtual DateTime ExpiredDateTime { set; get; } public virtual decimal Price { set; get; } [ForeignKey("StudentIdFk")] public virtual Student Student { set; get; } public virtual int StudentIdFk { set; get; } }
[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; }
[IgnoreMap] 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 TotalBookPrice { set; get; } };
public class BookCountValueResolver : ValueResolver<Student, int> { protected override int ResolveCore(Student source) => source.Books.Count; }; public class BookPriceValueResolver : ValueResolver<Student, decimal> { protected override decimal ResolveCore(Student source) => source.Books.Sum(b => b.Price); };
static void Main(string[] args) { var assemblyToLoad = Assembly.GetAssembly(typeof (AdminStudentViewModel));//get assembly global::AttributesForAutomapper.Configuration.Initialize(assemblyToLoad);//init automaper IList<Student> lst; using (var context = new MySampleContext()) { lst = context.Students.Include(x => x.Books).ToList(); } foreach (var student in lst) { WriteLine( $"[{student.Id}]*\n{student.Name} {student.Family}.\nmailto:{student.Email}.\nRegistered at'{student.RegisterDateTime}'"); foreach (var book in student.Books) WriteLine($"\tBook name:{book.Name}, Book price:{book.Price}"); } var lstViewModel = AutoMapper.Mapper.Map<IList<Student>, IList<AdminStudentViewModel>>(lst); foreach (var adminStudentViewModel in lstViewModel) { WriteLine( $"[{adminStudentViewModel.Id}]*\n\t{adminStudentViewModel.FirstName} {adminStudentViewModel.LastName}.\n\t" + $"mailto:{adminStudentViewModel.Email}.\n\tRegistered at'{adminStudentViewModel.RegisterDateTimePersian}'\n\t" + $"Book Counts: {adminStudentViewModel.BookCounts} with total price of {adminStudentViewModel.TotalBookPrice}"); } WriteLine("Press any key to exit..."); ReadKey(); }
نمونهی خروجی:
[1]* Morteza Raeisi. mailto:MrRaeisi@outlook.com. Registered at'23/08/1392 19:11:43' // I'm using Windows 10 with Persian calendar as default, On other OS or calendar settings, this value is different. Book name:AutoMapper Attr, Book price:1000.00 Book name:Second Book, Book price:2500.00 Book name:Hungry Book, Book price:2500.00 ... [1]* Morteza Raeisi. //MapForMemebers mailto:. // IgnoreMap Registered at'1392/08/23 19:11' // Convert using Book Counts: 3 with total price of 6000.00 // Value resolvers ...
ORM های NHibernate و Entity framework روشهای متفاوتی را برای به روز رسانی کلید خارجی با حداقل رفت و برگشت به دیتابیس ارائه میدهند که در ادامه معرفی خواهند شد.
صورت مساله:
فرض کنید میخواهیم برنامهای را بنویسیم که ریز پرداختهای روزانهی ما را ثبت کند. برای اینکار حداقل به یک جدول گروههای اقلام خریداری شده، یک جدول حسابهای تامین کنندهی مخارج، یک جدول فروشندهها و نهایتا یک جدول صورتحسابهای پرداختی بر اساس جداول ذکر شده نیاز خواهد بود.
الف) بررسی مدل برنامه
در اینجا جهت تعریف ویژگیها یا Attributes تعریف شده در این کلاسها از NHibernate validator استفاده شده (+). مزیت اینکار هم علاوه بر اعتبارسنجی سمت کلاینت (پیش از تبادل اطلاعات با بانک اطلاعاتی)، تولید جداولی با همین مشخصات است. برای مثال Fluent NHibernate بر اساس ویژگی Length تعریف شده با طول حداکثر 120 ، یک فیلد nvarchar با همین طول را ایجاد میکند.
public class Account
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام باید بین 3 و 120 کاراکتر باشد")]
public virtual string Name { get; set; }
}
public class Category
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 130, Message = "طول نام باید بین 3 و 130 کاراکتر باشد")]
public virtual string Name { get; set; }
}
public class Payee
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام باید بین 3 و 120 کاراکتر باشد")]
public virtual string Name { get; set; }
}
public class Bill
{
public virtual int Id { get; set; }
[NotNull]
public virtual Account Account { get; set; }
[NotNull]
public virtual Category Category { get; set; }
[NotNull]
public virtual Payee Payee { get; set; }
[NotNull]
public virtual decimal Amount { set; get; }
[NotNull]
public virtual DateTime BillDate { set; get; }
[NotNullNotEmpty]
[Length(Min = 1, Max = 500, Message = "طول توضیحات باید بین 1 و 500 کاراکتر باشد")]
public virtual string Description { get; set; }
}
ب) ساختار جداول متناظر (تولید شده به صورت خودکار توسط Fluent NHibernate در اینجا)
در مورد نحوهی استفاده از ویژگی AutoMapping و همچنین تولید خودکار ساختار بانک اطلاعاتی از روی جداول در NHibernate قبلا توضیح داده شده است. البته بدیهی است که ترکیب مقالهی Validation و آشنایی با AutoMapping در اینجا جهت اعمال ویژگیها باید بکار گرفته شود که در همان مقالهی Validation مفصل توضیح داده شده است.
نکتهی مهم database schema تولیدی، کلیدهای خارجی (foreign key) تعریف شده بر روی جدول Bills است (همان AccountId، CategoryId و PayeeId تعریف شده) که به primary key جداول متناظر اشاره میکند.
create table Accounts (
AccountId INT IDENTITY NOT NULL,
Name NVARCHAR(120) not null,
primary key (AccountId)
)
create table Bills (
BillId INT IDENTITY NOT NULL,
Amount DECIMAL(19,5) not null,
BillDate DATETIME not null,
Description NVARCHAR(500) not null,
AccountId INT not null,
CategoryId INT not null,
PayeeId INT not null,
primary key (BillId)
)
create table Categories (
CategoryId INT IDENTITY NOT NULL,
Name NVARCHAR(130) not null,
primary key (CategoryId)
)
create table Payees (
PayeeId INT IDENTITY NOT NULL,
Name NVARCHAR(120) not null,
primary key (PayeeId)
)
alter table Bills
add constraint fk_Account_Bill
foreign key (AccountId)
references Accounts
alter table Bills
add constraint fk_Category_Bill
foreign key (CategoryId)
references Categories
alter table Bills
add constraint fk_Payee_Bill
foreign key (PayeeId)
references Payees
ج) صفحهی ثبت صورتحسابها
صفحات ثبت گروههای اقلام، حسابها و فروشندهها، نکتهی خاصی ندارند. چون این جداول وابستگی خاصی به جایی نداشته و به سادگی اطلاعات آنها را میتوان ثبت یا به روز کرد.
صفحهی مشکل در این مثال، همان صفحهی ثبت صورتحسابها است که از سه کلید خارجی به سه جدول دیگر تشکیل شده است.
عموما برای طراحی این نوع صفحات، کلیدهای خارجی را با drop down list نمایش میدهند و اگر در جهت سهولت کار کاربر قدم برداشته شود، باید از یک Auto complete drop down list استفاده کرد تا کاربر برنامه جهت یافتن آیتمهای از پیش تعریف شده کمتر سختی بکشد.
اگر از Silverlight یا WPF استفاده شود، امکان بایند یک لیست کامل از اشیاء با تمام خواص مرتبط به آنها وجود دارد (هر رکورد نمایش داده شده در دراپ داون لیست، دقیقا معادل است با یک شیء متناظر با کلاسهای تعریف شده است). اگر از ASP.NET استفاده شود (یعنی یک محیط بدون حالت که پس از نمایش یک صفحه دیگر خبری از لیست اشیاء بایند شده وجود نخواهد داشت و همگی توسط وب سرور جهت صرفه جویی در منابع تخریب شدهاند)، بهتر است datatextfield را با فیلد نام و datavaluefield را با فیلد Id مقدار دهی کرد تا کاربر نهایی، نام را جهت ثبت اطلاعات مشاهده کند و برنامه از Id موجود در لیست جهت ثبت کلیدهای خارجی استفاده نماید.
و نکتهی اصلی هم همینجا است که چگونه؟! چون ما زمانیکه با یک ORM سر و کار داریم، برای ثبت یک رکورد در جدول Bills باید یک وهله از کلاس Bill را ایجاد کرده و خواص آنرا مقدار دهی کنیم. اگر به تعریف کلاس Bill مراجعه کنید، سه خاصیت آن از نوع سه کلاس مجزا تعریف شده است. به به عبارتی با داشتن فقط یک id از رکوردهای این کلاسها باید بتوان سه وهلهی متناظر آنها را از بانک اطلاعاتی خواند و سپس به این خواص انتساب داد:
var newBill = new Bill
{
Account = accountRepository.GetByKey(1),
Amount = 1,
BillDate = DateTime.Now,
Category = categoryRepository.GetByKey(1),
Description = "testestest...",
Payee = payeeRepository.GetByKey(1)
};
- یکبار برای دریافت رکورد متناظر با گروهها بر اساس کلید اصلی آن (که از دراپ داون لیست مربوطه دریافت میشود)
- یکبار برای دریافت رکورد متناظر با فروشندهها بر اساس کلید اصلی آن (که از دراپ داون لیست مربوطه دریافت میشود)
- یکبار برای دریافت رکورد متناظر با حسابها بر اساس کلید اصلی آن (که از دراپ داون لیست مربوطه دریافت میشود)
- یکبار هم ثبت نهایی اطلاعات در بانک اطلاعاتی
متد GetByKey فوق همان متد session.Get استاندارد NHibernate است (چون به primary key ها از طریق drop down list دسترسی داریم، به سادگی میتوان بر اساس متد Get استاندارد ذکر شده عمل کرد).
SQL نهایی تولیدی هم به صورت واضحی این مشکل را نمایش میدهد (4 بار رفت و برگشت؛ سه بار select یکبار هم insert نهایی):
SELECT account0_.AccountId as AccountId0_0_, account0_.Name as Name0_0_
FROM Accounts account0_ WHERE account0_.AccountId=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT category0_.CategoryId as CategoryId2_0_, category0_.Name as Name2_0_
FROM Categories category0_ WHERE category0_.CategoryId=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT payee0_.PayeeId as PayeeId3_0_, payee0_.Name as Name3_0_
FROM Payees payee0_ WHERE payee0_.PayeeId=@p0;@p0 = 1 [Type: Int32 (0)]
INSERT INTO Bills (Amount, BillDate, Description, AccountId, CategoryId, PayeeId)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
select SCOPE_IDENTITY();
@p0 = 1 [Type: Decimal (0)],
@p1 = 2010/12/27 11:48:33 ق.ظ [Type: DateTime (0)],
@p2 = 'testestest...' [Type: String (500)],
@p3 = 1 [Type: Int32 (0)],
@p4 = 1 [Type: Int32 (0)],
@p5 = 1 [Type: Int32 (0)]
کسانی که قبلا با رویههای ذخیره شده کار کرده باشند (stored procedures) احتمالا الان خواهند گفت؛ ما که گفتیم این روش کند است! سربار زیادی دارد! فقط کافی است یک SP بنویسید و کل عملیات را با یک رفت و برگشت انجام دهید.
اما در ORMs نیز برای انجام این مورد در طی یک حرکت یک ضرب راه حلهایی وجود دارد که در ادامه بحث خواهد شد:
د) پیاده سازی با NHibernate
برای حل این مشکل در NHibernate با داشتن primary key (برای مثال از طریق datavaluefield ذکر شده)، بجای session.Get از session.Load استفاده کنید.
session.Get یعنی همین الان برو به بانک اطلاعاتی مراجعه کن و رکورد متناظر با کلید اصلی ذکر شده را بازگشت بده و یک شیء از آن را ایجاد کن (حالتهای دیگر دسترسی به اطلاعات مانند استفاده از LINQ یا Criteria API یا هر روش مشابه دیگری نیز در اینجا به همین معنا خواهد بود).
session.Load یعنی فعلا دست نگه دار! مگر در جدول نهایی نگاشت شده، اصلا چیزی به نام شیء مثلا گروه وجود دارد؟ مگر این مورد واقعا یک فیلد عددی در جدول Bills بیشتر نیست؟ ما هم که الان این عدد را داریم (به کمک عناصر دراپ داون لیست)، پس لطفا در پشت صحنه یک پروکسی برای ایجاد شیء مورد نظر ایجاد کن (uninitialized proxy to the entity) و سپس عملیات مرتبط را در حین تشکیل SQL نهایی بر اساس این عدد موجود انجام بده. یعنی نیازی به رفت و برگشت به بانک اطلاعاتی نیست. در این حالت اگر SQL نهایی را بررسی کنیم فقط یک سطر زیر خواهد بود (سه select ذکر شده حذف خواهند شد):
INSERT INTO Bills (Amount, BillDate, Description, AccountId, CategoryId, PayeeId)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
select SCOPE_IDENTITY();
@p0 = 1 [Type: Decimal (0)],
@p1 = 2010/12/27 11:58:22 ق.ظ [Type: DateTime (0)],
@p2 = 'testestest...' [Type: String (500)],
@p3 = 1 [Type: Int32 (0)],
@p4 = 1 [Type: Int32 (0)],
@p5 = 1 [Type: Int32 (0)]
ه) پیاده سازی با Entity framework
Entity framework زمانیکه بانک اطلاعاتی فوق را (به روش database first) به کلاسهای متناظر تبدیل/نگاشت میکند، حاصل نهایی مثلا در مورد کلاس Bill به صورت خلاصه به شکل زیر خواهد بود:
public partial class Bill : EntityObject
{
public global::System.Int32 BillId {set;get;}
public global::System.Decimal Amount {set;get;}
public global::System.DateTime BillDate {set;get;}
public global::System.String Description {set;get;}
public global::System.Int32 AccountId {set;get;}
public global::System.Int32 CategoryId {set;get;}
public global::System.Int32 PayeeId {set;get;}
public Account Account {set;get;}
public Category Category {set;get;}
}
نظرات اشتراکها
رایگان شدن بیش از ۷۰۰۰ دوره سایت Pluralsight
برنامه را کمی تغییر دادم تا خودش فایلها را هم یکی یکی دریافت کند؛ آهسته و پیوسته، به همراه ایجاد پوشهها، به ازای هر ماژول دوره و نامگذاری صحیح فایلهای ویدیوهای دریافتی: PluralsightLinks-V2.7z
نظرات اشتراکها
دریافت خروجی کامل NET Tips. با فرمت EPub
جهت اطلاع: خروجی CHM هم اضافه شد.
یک نکته: فایلهای CHM دریافتی از طریق مرورگرها، به صورت پیشفرض قابل نمایش نیستند. باید بر روی آنها کلیک راست کنید و سپس در برگهی خواص فایل، بر روی دکمهی unblock آن فایل کلیک کنید تا قابل استفاده شود.
اشتراکها
مجموعه ابزارهای رایگان و آنلاین
اشتراکها
برنامه دیکمپایل کدهای جاوا
اشتراکها