نظرات مطالب
بازخوردهای دوره
تبدیلگر تاریخ شمسی برای AutoMapper
ممنونم. دقیقا مشکل همین بود( LINQ to Objects ) . به این صورت اعمال کردم و خروجی به درستی نمایش داده شد :
return _mappingEngine.Map <List<UserViewModel>> (users.Skip(skipRecords) .Take(recordsPerPage) .Future().ToList());
پیشنیاز: «تنظیمات ارث بری کلاسها در EF Code first»
در مطلب پیشنیاز فوق، تنظیمات روابط ارث بری را تا EF 6.x، میتوانید مطالعه کنید. در EF Core 1.0 RTM، فقط رابطهی TPH که در آن تمام کلاسهای سلسه مراتب ارث بری، به یک جدول در بانک اطلاعاتی نگاشت میشوند، پشتیبانی میشود. سایر روشهای ارث بری که در EF 6.x وجود دارند، مانند TPT و TPC، قرار است به نگارشهای پس از 1.0 RTM آن اضافه شوند:
- لیست مواردی که قرار است به نگارشهای بعدی اضافه شوند
- پیگیری وضعیت پیاده سازی TPT
- پیگیری وضعیت پیاده سازی TPC
طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط
مرسوم است که یک کلاس ویژه را به نام BaseEntity، به شکل زیر تعریف کنند؛ که اهدف آن حداقل سه مورد ذیل است:
الف) کاهش ذکر فیلدهای تکراری در سایر کلاسهای دومین برنامه، مانند فیلد Id
ب) نشانه گذاری موجودیتهای برنامه، جهت یافتن سریع آنها توسط Reflection (برای مثال افزودن خودکار موجودیتها به Context برنامه با یافتن تمام کلاسهایی که از نوع BaseEntity هستند)
ج) مقدار دهی خودکار یک سری از فیلدهای ویژه، مانند زمان افزوده شدن رکورد و آخرین زمان ویرایش شدن رکورد و امثال آن
و پس از آن هر موجودیت برنامه به این شکل خلاصه شده و نشانه گذاری میشود:
حالت پیش فرض ارث بریها در EF Core، همان حالت TPH است که در ادامه توضیح داده خواهد شد. اما هدف ما در اینجا تنظیم هیچکدام از حالتهای ارث بری نیست. هدف صرفا کاهش تعداد فیلدهای تکراری ذکر شدهی در کلاسهای دومین برنامه است. بنابراین جهت لغو تنظیمات ارث بری EF Core، نیاز است یک چنین تنظیمی را انجام داد:
با فراخوانی متد Ignore بر روی کلاس پایهی تهیه شده، این کلاس دیگر وارد تنظیمات روابط EF Core نمیشود و در جداول نهایی، فیلدهای آن به صورت معمول در کنار سایر فیلدهای جداول مشتق شدهی از آنها قرار میگیرند.
مشکل! اگر بر روی کلاس پایهی تعریف شده تنظیماتی را اعمال کنید (هر نوع تنظیمی را)، با توجه به فراخوانی متد Ignore، این تنظیمات نیز ندید گرفته خواهند شد.
اگر علاقمند بودید تا این تنظیمات را به تمام کلاسهای مشتق شدهی از BaseEntity به صورت خودکار اعمال کنید، روش کار به صورت ذیل است:
کاری که در اینجا انجام شده، تنظیم خاصیت DateAdded کلاس پایه، به حالت ValueGeneratedOnAdd و تنظیم خاصیت DateUpdated کلاس پایه به حالت ValueGeneratedOnAddOrUpdate با مقدار پیش فرض getdate است. این مفاهیم را در مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکسها» پیشتر بررسی کردیم.
خلاصهی آن نیز به این صورت است:
الف) نیازی نیست تا در حین ثبت اطلاعات موجودیتهای خود، فیلدهای DateAdded و یا DateUpdated را مقدار دهی کنید.
ب) فیلد DateAdded فقط در زمان اولین بار ثبت در بانک اطلاعاتی، به صورت خودکار توسط متد getdate مقدار دهی میشود.
ج) فیلد DateUpdated در هر بار فراخوانی متد SaveChanges (یعنی در هر دو حالت ثبت و یا به روز رسانی) به صورت خودکار توسط متد getdate مقدار دهی میشود.
تذکر! بدیهی است متد getdate، یک متد بومی سمت SQL Server است و این روش خاص تعیین مقدار پیش فرض فیلدها، فقط با SQL Server کار میکند. همچنین این getdate، به معنای دریافت تاریخ و ساعت سروری است که SQL Server بر روی آن نصب شدهاست و نه سروری که برنامهی وب شما در آن قرار دارد و برنامه کوچکترین دخالتی را در مقدار دهی این مقادیر نخواهد داشت.
در قسمتهای بعدی که مباحث Tracking را بررسی خواهیم کرد، روش دیگری را برای طراحی کلاسهای پایه و مقدار دهی خواص ویژهی آنها مطرح میکنیم که مستقل است از نوع بانک اطلاعاتی مورد استفاده.
بررسی تنظیمات رابطهی Table per Hierarchy یا TPH
رابطهی TPH یا تشکیل یک جدول بانک اطلاعاتی، به ازای تمام کلاسهای دخیل در سلسه مراتب ارث بری تعریف شده، بسیار شبیه است به حالت BaseEntity فوق که در آن نیز ارث بری تعریف شده، در نهایت منجر به تشکیل یک جدول، در سمت بانک اطلاعاتی میگردد. با این تفاوت که در حالت TPH، فیلد جدیدی نیز به نام Discriminator، به تعریف نهایی جدول ایجاد شده، اضافه میشود. از فیلد Discriminator جهت درج نام کلاسهای متناظر با هر رکورد، استفاده شده است. به این ترتیب EF در حین کار با اشیاء، دقیقا میداند که چگونه باید خواص متناظر با کلاسهای مختلف را مقدار دهی کند و نوع ردیف درج شدهی در بانک اطلاعاتی چیست؟
باید دقت داشت که تنظیمات TPH، شیوه برخورد پیش فرض EF Core با ارث بری کلاسها است و نیاز به هیچگونه تنظیم اضافهتری را ندارد. اما اگر علاقمند بودید تا نام فیلد خودکار Discriminator و مقادیری را که در آن درج میشوند، سفارشی سازی کنید، روش کار صرفا توسط Fluent API میسر است و به صورت زیر میباشد:
در اینجا نام فیلد Discriminator، به blog_type، مقدار نوع متناظر با کلاس Blog، به blog_base و مقدار نوع متناظر با کلاس RssBlog، به blog_rss تنظیم شدهاست.
اگر این تنظیمات سفارشی صورت نگیرند، از نامهای پیش فرض نوعها برای مقدار دهی ستون Discriminator، مانند تصویر ذیل استفاده خواهد شد:
برای کوئری نوشتن در این حالت میتوان از متد الحاقی OfType جهت فیلتر کردن اطلاعات بر اساس کلاسی خاص، کمک گرفت:
در مطلب پیشنیاز فوق، تنظیمات روابط ارث بری را تا EF 6.x، میتوانید مطالعه کنید. در EF Core 1.0 RTM، فقط رابطهی TPH که در آن تمام کلاسهای سلسه مراتب ارث بری، به یک جدول در بانک اطلاعاتی نگاشت میشوند، پشتیبانی میشود. سایر روشهای ارث بری که در EF 6.x وجود دارند، مانند TPT و TPC، قرار است به نگارشهای پس از 1.0 RTM آن اضافه شوند:
- لیست مواردی که قرار است به نگارشهای بعدی اضافه شوند
- پیگیری وضعیت پیاده سازی TPT
- پیگیری وضعیت پیاده سازی TPC
طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط
مرسوم است که یک کلاس ویژه را به نام BaseEntity، به شکل زیر تعریف کنند؛ که اهدف آن حداقل سه مورد ذیل است:
الف) کاهش ذکر فیلدهای تکراری در سایر کلاسهای دومین برنامه، مانند فیلد Id
ب) نشانه گذاری موجودیتهای برنامه، جهت یافتن سریع آنها توسط Reflection (برای مثال افزودن خودکار موجودیتها به Context برنامه با یافتن تمام کلاسهایی که از نوع BaseEntity هستند)
ج) مقدار دهی خودکار یک سری از فیلدهای ویژه، مانند زمان افزوده شدن رکورد و آخرین زمان ویرایش شدن رکورد و امثال آن
public class BaseEntity { public int Id { set; get; } public DateTime? DateAdded { set; get; } public DateTime? DateUpdated { set; get; } }
public class Person : BaseEntity { public string FirstName { get; set; } public string LastName { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>();
مشکل! اگر بر روی کلاس پایهی تعریف شده تنظیماتی را اعمال کنید (هر نوع تنظیمی را)، با توجه به فراخوانی متد Ignore، این تنظیمات نیز ندید گرفته خواهند شد.
اگر علاقمند بودید تا این تنظیمات را به تمام کلاسهای مشتق شدهی از BaseEntity به صورت خودکار اعمال کنید، روش کار به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>(); foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var dateAddedProperty = entityType.FindProperty("DateAdded"); dateAddedProperty.ValueGenerated = ValueGenerated.OnAdd; dateAddedProperty.SqlServer().DefaultValueSql = "getdate()"; var dateUpdatedProperty = entityType.FindProperty("DateUpdated"); dateUpdatedProperty.ValueGenerated = ValueGenerated.OnAddOrUpdate; dateUpdatedProperty.SqlServer().ComputedColumnSql = "getdate()"; }
خلاصهی آن نیز به این صورت است:
الف) نیازی نیست تا در حین ثبت اطلاعات موجودیتهای خود، فیلدهای DateAdded و یا DateUpdated را مقدار دهی کنید.
ب) فیلد DateAdded فقط در زمان اولین بار ثبت در بانک اطلاعاتی، به صورت خودکار توسط متد getdate مقدار دهی میشود.
ج) فیلد DateUpdated در هر بار فراخوانی متد SaveChanges (یعنی در هر دو حالت ثبت و یا به روز رسانی) به صورت خودکار توسط متد getdate مقدار دهی میشود.
تذکر! بدیهی است متد getdate، یک متد بومی سمت SQL Server است و این روش خاص تعیین مقدار پیش فرض فیلدها، فقط با SQL Server کار میکند. همچنین این getdate، به معنای دریافت تاریخ و ساعت سروری است که SQL Server بر روی آن نصب شدهاست و نه سروری که برنامهی وب شما در آن قرار دارد و برنامه کوچکترین دخالتی را در مقدار دهی این مقادیر نخواهد داشت.
در قسمتهای بعدی که مباحث Tracking را بررسی خواهیم کرد، روش دیگری را برای طراحی کلاسهای پایه و مقدار دهی خواص ویژهی آنها مطرح میکنیم که مستقل است از نوع بانک اطلاعاتی مورد استفاده.
بررسی تنظیمات رابطهی Table per Hierarchy یا TPH
رابطهی TPH یا تشکیل یک جدول بانک اطلاعاتی، به ازای تمام کلاسهای دخیل در سلسه مراتب ارث بری تعریف شده، بسیار شبیه است به حالت BaseEntity فوق که در آن نیز ارث بری تعریف شده، در نهایت منجر به تشکیل یک جدول، در سمت بانک اطلاعاتی میگردد. با این تفاوت که در حالت TPH، فیلد جدیدی نیز به نام Discriminator، به تعریف نهایی جدول ایجاد شده، اضافه میشود. از فیلد Discriminator جهت درج نام کلاسهای متناظر با هر رکورد، استفاده شده است. به این ترتیب EF در حین کار با اشیاء، دقیقا میداند که چگونه باید خواص متناظر با کلاسهای مختلف را مقدار دهی کند و نوع ردیف درج شدهی در بانک اطلاعاتی چیست؟
باید دقت داشت که تنظیمات TPH، شیوه برخورد پیش فرض EF Core با ارث بری کلاسها است و نیاز به هیچگونه تنظیم اضافهتری را ندارد. اما اگر علاقمند بودید تا نام فیلد خودکار Discriminator و مقادیری را که در آن درج میشوند، سفارشی سازی کنید، روش کار صرفا توسط Fluent API میسر است و به صورت زیر میباشد:
public class Blog { public int BlogId { get; set; } public string Url { get; set; } } public class RssBlog : Blog { public string RssUrl { get; set; } } class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasDiscriminator<string>("blog_type") .HasValue<Blog>("blog_base") .HasValue<RssBlog>("blog_rss"); } }
اگر این تنظیمات سفارشی صورت نگیرند، از نامهای پیش فرض نوعها برای مقدار دهی ستون Discriminator، مانند تصویر ذیل استفاده خواهد شد:
برای کوئری نوشتن در این حالت میتوان از متد الحاقی OfType جهت فیلتر کردن اطلاعات بر اساس کلاسی خاص، کمک گرفت:
var blog1 = db.Blogs.OfType<RssBlog>().FirstOrDefault(x => x.RssUrl == "………");
اگر مطلب "ذخیره سازی SQL تولیدی در NH3" را دنبال کرده باشید که یک مثال عملی از "NHibernate 3.0 و عدم وابستگی مستقیم به Log4Net" بود، خروجی حاصل از آن به صورت زیر است:
---+ 12/29/2010 05:35:59.75 +---
SQL ...
---+ 12/29/2010 05:35:59.75 +---
SQL ...
خوشبختانه در دات نت فریم ورک میتوان با بررسی Stack trace ، رد کاملی را از فراخوانهای متدها یافت:
StackTrace stackTrace = new StackTrace();
StackFrame stackFrame = stackTrace.GetFrame(1);
MethodBase methodBase = stackFrame.GetMethod();
Type callingType=methodBase.DeclaringType;
بر این اساس سورس مثال قبل را جهت درج اطلاعات فراخوانهای متدها تکمیل کردهام، که از این آدرس قابل دریافت است.
اکنون اگر از این ماژول جدید استفاده کنیم، خروجی نمونهی آن به صورت زیر خواهد بود:
---+ 01/02/2011 02:19:24.98 +---
-- Void ASP.feedback_aspx.ProcessRequest(System.Web.HttpContext) [File=App_Web_4nvdip40.5.cs, Line=0]
--- Void Prog.Web.UserCtrls.FeedbacksList.Page_Load(System.Object, System.EventArgs) [File=FeedbacksList.ascx.cs, Line=23]
---- Void Prog.Web.UserCtrls.FeedbacksList.BindTo() [File=FeedbacksList.ascx.cs, Line=43]
----- System.Collections.Generic.IList`1[Feedback] Prog.GetAllUserFeedbacks(Int32) [File=FeedbackWebRepository.cs, Line=66]
SELECT ...
@p0 = 3 [Type: Int32 (0)]
اینطوری حداقل میتوان دریافت که SQL تولیدی دقیقا به کجا بر میگردد و چه متدی سبب صدور آن شده است.
ملاحظات:
این ماژول تنها در صورت وجود فایل pdb معتبر کنار اسمبلیهای شما این خروجی مفصل را تولید خواهد کرد. در غیر اینصورت از آن صرفنظر میکند. (برای مثال نام فایل سورس فراخوان، شمارهی سطر فراخوان، حتی محل قرارگیری آن فایل بر روی کامپیوتر شما در فایلهای pdb ثبت میگردند؛ به همین جهت توصیه اکید حین ارائهی نهایی برنامه، حذف این نوع فایلها است)
این خطای خود EF هست (^). به این معنا که در LINQ to Entities مجاز نیستید در حین projection، از کلاسهایی که به جداول بانک اطلاعاتی نگاشت شدهاند استفاده کنید. از یک ViewModel یا یک DTO استفاده کنید تا مشکل برطرف شود. اطلاعات بیشتر
نظرات مطالب
EF Code First #12
- منطق تجاری مورد نظر رو باید به صورت یک متد با حد و مرز مشخص، کپسوله و داخل این متد از Skip و Take و سایر امکانات LINQ استفاده کنید.
- ضمنا نباید لیست تهیه کنید و بعد روی آن Take انجام دهید؛ چون کارآیی پایینی داره:
«تفاوت بین IQueryable و IEnumerable در حین کار با ORMs»
- ضمنا نباید لیست تهیه کنید و بعد روی آن Take انجام دهید؛ چون کارآیی پایینی داره:
«تفاوت بین IQueryable و IEnumerable در حین کار با ORMs»
نظرات مطالب
EF Code First #8
مدل مشکلی نداره (تست شده). اطلاعات بیشتر: «استثنای Sequence contains no elements در حین استفاده از LINQ»
ضمن اینکه مطلب فوق در اینجا کامل شده:
«مباحث تکمیلی مدلهای خود ارجاع دهنده در EF Code first»
ضمن اینکه مطلب فوق در اینجا کامل شده:
«مباحث تکمیلی مدلهای خود ارجاع دهنده در EF Code first»
نظرات مطالب
EF Code First #13
باید از Dynamic LINQ استفاده کنید.