به نام آن که جان را فکرت آموخت / چراغ دل به نور جان برافروخت
با سلام و تشکر از مطالعه مقالات بنده در کانال ویرگول ، در این مقاله امکانات جدید EF Core بررسی می شود. .ایده این سری مقاله های آموزشی از این موضوع سرچشمه می گیرد که بخشی از خوانندگان وجود دارد که به محتوای نوشتاری آنلاین بهتر پاسخ می دهند و ترجیج می دهند مهارت های جدید را به سرعت از طریق خواندن افزایش دهند.این سری اموزش ها با ارایه پکیج آموزش های کاربردی در خصوص C# asp.net core آغاز می شود که انتظار می رود با واکنش مثبت کاربران همراه شود.
توجه: این مقاله به مرور زمان، ویرایش و یا تکمیل میشود!
تقاضا: در صورتی که با مشکل تایپی، دستوری و یا مفهومی در این مقاله برخورد کردید، از شما دوست عزیز و گرامی، صمیمانه تقاضا میکنم که اینجانب را مطلع کرده، تا نسبت به تصحیح و یا تکمیل آن، در اسرع وقت، اقدام نمایم.
با کمال تشکر
جواد جهانگیری
شماره تلفن همراه: 09149431772
نشانی پست الکترونیکی: javad.jahangiri.niopdc@gmail.com
فیلمهای آموزشی در آپارات:جواد جهانگیری (CTO) - آپارات
فیلم آموزشی در یوتویب: javad jahangiri - YouTube
نسخه مقاله: ۱.۱ - تاریخ بروزرسانی: 1400/09/23
در این مقاله به شما توضیح میدهم که Entity Framework چیست، چه مزایایی دارد، یک مرور کلی انجام میدهم و سپس پیش نیازهای زیر مطالعه می شود:
و در ادامه مقاله امکانات جدید EF Core مطالعه می شود:
منبع اموزشی برای این مقاله سایت مایکروسافت می باشد
در مورد Entity framework core می توان گفت که یک data access API می باشد و در حقیقت یک نوع object relational mapper (ORM) می باشد که می توان از ان بصورت کراس پلتفورم در C# Asp.net Core استفاده کرد
در مورد EF Core انواع دیتابیس ها را پشتیبانی می کند
برنامه نویسی Asynchronous با استفاده ی بهینه از منابع سرور، کارایی برنامه را بالا می برد.
تعداد thread هایی که در دسترس یک سرور وب قرار دارد محدود است و در مواقعی که بارگذاری سنگین در حال اجرا است، تمامی thread ها بکار گرفته می شوند. در صورت به وجود آمدن چنین شرایطی، سرویس دهنده تا زمانی که تمامی thread ها آزاد نشده اند، قادر به پردازش درخواست ها نخواهد بود. اما در خصوص synchronous code، بسیاری از thread ها با اینکه هیچ کار یا عملیات خاصی را انجام نمی دهند، باز مشغول بوده و قابل دسترس نمی باشند. دلیلش این است که thread ها منتظر هستند که عملیات ورودی/خروجی به اتمام برسد. در رابطه با asynchronous code، هنگامی که فرایندی منتظر اتمام عملیات ورودی/خروجی می باشد، thread آن آزاد شده تا برای پردازش دیگر درخواست ها توسط سرور مورد استفاده قرار گیرد. در نتیجه، asynchronous code امکان و زمینه ی استفاده ی بهینه از منابع سرور را مهیا ساخته و همچنین سرور را قادر می سازد تا بدون هیچ گونه تاخیر ترافیک بیشتری را مدیریت کند.در نسخه های قدیمی تر .NET، کدنویسی و تست آن بسیار پیچیده، مستعد خطا بوده و همچنین خطایابی (debug) آن بسیار دشوار می باشد. در ویرایش C# Asp.net Core کدنویسی، تست و اشکال زدایی آن به مراتب آسان تر می باشد، از این رو پیشنهاد می کنیم تا حد امکان کدهای ناهمزمان (asynchronous) بنویسید. اگرچه نوشتن کدهای ناهمزمان باعث ورود مقداری سربار (overhead) می شود، در مواقع کم ترافیک، افت کارایی بسیار ناچیز بوده و مشکل بزرگی رخ نمی دهد. این در حالی است که در شرایطی که ترافیک بالا است، افزایش بالقوه ی کارایی چشمگیر خواهد بود.
به کد زیر دقت کنید
public async Task<actionresult> Index() { var departments = db.Departments.Include(d => d.Administrator); return View(await departments.ToListAsync()); }
چهار تغییر به کد بالا اعمال شده که به query امکان می دهد به صورت ناهمزمان (Async) اجرا گردد:
1. متد مورد نظر با کلیدواژه ی asynch علامت گذاری شده که به مترجم یا کامپایلر می فهماند که باید callback هایی را برای بخش های بدنه ی متد ایجاد کرده و شی Task< actionresult>که برگردانده می شود را به صورت خودکار ایجاد کند.
2. نوع (type) بازگشتی از ActionResult به <Task< actionresult تبدیل شده است. نوع <Task< t نشانگر عملیات یا کارهای در حال انجام با نتیجه ی type T می باشد.
کلیدواژه ی await به فراخوانی web service اعمال شده است. کامپایلر با دیدن این کلیدواژه، در پشت پرده متد مورد نظر را به دو بخش تقسیم می کند. اولین بخش آن متد با عملیاتی که به صورت ناهمزمان راه اندازی (آغاز) شده پایان می یابد و اما دومین بخش آن در یک متد callback قرار داده می شود که پس از اتمام عملیات، فراخوانی می شود.
3. نسخه ی ناهمزمان (asynchronous) متد الحاقی (extension method) به نام ToList صدا زده شده است.
چرا دستور departments.ToList اصلاح شده اما دستور departments = db.Departments مورد تغییر قرار نگرفته است؟ باید گفت دلیلش این است که تنها آن دستوراتی که باعث می شوند query یا command به پایگاه داده ارسال شود، به صورت ناهمزمان اجرا می شوند. دستور departments = db.Departments یک query تنظیم می کند، اما query تا زمانی که متد ToList فراخوانی نشده، اجرا نمی شود. بنابراین تنها متد ToList به صورت ناهمزمان اجرا می شود.
به کدزیر دقت کنید:
public async Task<actionresult> Create(Department department) { if (ModelState.IsValid) { db.Departments.Add(department); await db.SaveChangesAsync(); return RedirectToAction("Index"); }
در متدهای Create، HttpPost Edit و DeleteConfirmed، این فراخوانی متد SaveChanges است که سبب می شود یک دستور اجرا شود، نه دستورهایی نظیر db.Departments.Add(department) که تنها منجر به اصلاح موجودیت ها در حافظه می شوند.
در قسمت قبلی مقاله با نحوه ی بروز رسانی داده ها آشنا شدید. آموزش حاضر به شما می آموزد، زمانی که چند کاربر سعی بر بروز آوری entity یکسان می کنند، چگونه تداخلات (conflict) ناشی از آن را مدیریت کنید.
آن صفحاتی که با موجودیت Department کار می کنند را گونه ای ویرایش کنید که بتوانند خطاهای همروندی (concurrency error) را در صورت رخداد آن ها، مدیریت کنند. تصاویر زیر صفحات Index و Delete را به ضمیمه ی خطاهایی که با وقوع تداخل همروندی نمایش داده می شود را نشان می دهد.
زمانی رخ می دهد که کاربری داده های یک entity را برای ویرایش نمایش می دهد و در این میان کاربری دیگر سعی می کند همان داده ها را، قبل از اینکه تغییرات اعمال شده توسط کاربر اول در پایگاه داده نوشته شود، بروز رسانی کند. چنانچه شما شناسایی چنین تداخلاتی را فعال سازی نکنید، آن تغییراتی که توسط آخرین کاربر اعمال می شود، تمامی بروز رسانی دیگر کاربران را بازنویسی می کند. در بسیاری از برنامه ها، این ریسک پذیرفتنی است، یعنی برنامه هایی که کاربران آن محدود است و تعداد اندکی بروز رسانی در آن صورت می گیرد یا بازنویسی برخی تغییرات در آن مشکل بزرگی را ایجاد نمی کند، هزینه ی برنامه نویسی برای همروندی نسبت به مزایایی که به دنبال دارد بیشتر است. در این صورت، نیازی به تنظیم و پیکربندی برنامه برای مدیریت تداخلات همزمانی نیست.
یکی از روش های نوشتن برنامه ای که قادر باشد از از دست رفت داده در سناریوهای همروندی جلوگیری کند، اعمال قفل های پایگاه داده است. به این امر کنترل همروندی بدبینانه (pessimistic concurrency) می گویند. در این روش به وسیله قفل گذاری جلوی هرگونه همروندی گرفته میشود. تا زمانی که یک دستور درحال اجراست جلوی اجرای دستورهایی که ممکن است مانع اجرای صحیح دستور نخست شوند گرفته میشود. برای مثال، پیش از اینکه سطری را از پایگاه داده بخوانیم، یک قفل دسترسی فقط خواندنی (read-only) یا بروز رسانی درخواست می کنیم. )قفل امتیاز دستیابی به یک واحد داده است که توسط سیستم قفل گذاری به یک تراکنش داده میشود و یا از او پس گرفته میشود.( اگر یک سطر را برای بروز رسانی قفل کنید، هیچ کاربر دیگری قادر به قفل گذاری بر روی آن سطر برای بروز رسانی یا فقط-خواندن نخواهد بود زیرا در آن صورت تنها یک کپی از داده ها در حال ویرایش دریافت خواهد کرد. اگر یک سطر را برای سطح دسترسی فقط خواندن قفل کنید، در آن صورت دیگر کاربران می توانند آن را برای دسترسی فقط-خواندن قفل کنند اما این اجازه را در مورد بروز رسانی نخواهد داشت.
اعمال قفل ها و مدیریت آن ها معایبی را نیز به همراه دارد که از جمله می توان به پیچیدگی برنامه نویسی آن اشاره کرد. این کار همچنین لازمه ی منابع مدیریتی بسیار سنگین است. بعلاوه در صورت بالا رفتن تعداد کاربران برنامه ممکن است مشکلاتی در زمینه کارایی به وجود آورده و افت آن را به همراه داشته باشد. بنا به دلایل ذکر شده، تمامی سیستم های مدیریتی پایگاه داده از همروندی بدبینانه پشتیبانی نمی کنند.
گزینه ی مقابل همروندی بدبینانه، همروندی خوش بینانه (optimistic concurrency) می باشد. در همروندی خوش بینانه به تداخلات همزمانی اجازه ی رخ دادن داده می شود و در صورت روی دادن آن ها، واکنش مناسب صورت می گیرد. به عنوان مثال، کاربری به نام john صفحه ی Departments Edit را اجرا کرده و مقدار Budget را برای فیلد English از $350,000.00 به $0.00 تغییر می دهد.
قبل از اینکه john دکمه ی Save را کلیک کند، کاربر دیگری به نام jane همان صفحه را اجرا کرده و فیلد Start Date را از 9/1/2007 به 8/8/2013 تغییر می دهد.
ابتدا John دکمه ی Save را کلیک کرده، تغییرات خود را با بازگشت مرورگر به صفحه ی Index مشاهده می کند، سپس jane دکمه ی Save را کلیک می کند. اینکه بعد چه اتفاقی رخ می دهد، بسته به نحوه ی مدیریت تداخلات همروندی توسط شما دارد.
1. می توانید حساب اینکه کاربر کدام property را اصلاح کرده نگه دارید و بر اساس آن فقط ستون های مربوطه را در پایگاه داده بروز رسانی نمایید. در مثالی که ذکر شد، هیچ داده ای از دست نمی رود زیرا property های مختلف توسط دو کاربر متفاوت بروز رسانی شده. دفعه ی بعدی که کاربری English department را پیمایش می کند، تغییرات اعمال شده توسط هر دو کاربر را مشاهده خواهد کرد: start date (تاریخ شروع) با مقدار 8/8/2013 و budget ای (بودجه) با مقدار $0.00.
این روش بروز رسانی می تواند تعداد رخدادهای تداخل همروندی را که ممکن است منجر به از دست رفت داده شود، کاهش دهد. اما در صورت اعمال تغییرات متقابل به یک property از یک موجودیت، دیگر قادر به جلوگیری از از دست رفت اطلاعات نخواهد بود. اینکه EF به این روش عمل کند، بستگی به نحوه ی پیاده سازی شما از Update code دارد. این کار در یک برنامه ی تحت وب معمولا امکان پذیر و کاربردی نیست، زیرا در آن صورت لازم است برای اینکه علاوه بر مقادیر جدید حساب تمامی مقادیر property های اولیه یک موجودیت را داشته باشیم، مقادیر زیادی از state ها را حفظ و نگه داری کنیم. حفظ و نگهداری مقدار زیادی از state ها می تواند اثر سوء بر کارایی برنامه داشته باشد، زیرا این کار لازمه ی اشغال منابع سرور بوده یا اطلاعات مربوط به آن را می بایست در خود صفحه ی وب (برای مثال در فیلدهای پنهان) و یا یک cookie گنجاند.
2. می توانید اجازه دهید تغییرات jane تغییرات اعمال شده توسط john را بازنویسی کند. حال دفعه ی بعدی که کاربری English department را پیمایش می کند، تاریخ 8/8/2013 و مقدار بازگردانده شده ی $350,000.00 را مشاهده می کند. این سناریو، client wins یا last in wins خوانده می شود (بدین معنا که مقادیر ارائه شده توسط client بر مقادیر موجود در انبار داده یا data store اولویت دارد). همان طور که بخش مقدمه ی این آموزش تشریح شد، اگر هیچ کدنویسی برای مدیریت همروندی انجام ندهید، این اتفاق خود به صورت پیش فرض رخ می دهد.
3. می توان کاری کرد تغییرات اعمال شده توسط jane در پایگاه داده بروز رسانی نشود. به طور معمول یک پیغام خطا برای کاربر (jane) نمایش می دهیم، وضعیت جاری داده ها را به اطلاع وی می رسانیم، سپس به کاربر مذکور اجازه می دهیم در صورت تمایل (اگر می خواهد همواره اصلاحاتی را ایجاد کند) تغییرات خود را مجددا اعمال نماید. این سناریو تحت عنوان store wins شناخته می شود (بدین معنا که مقادیر انبار داده یا data store بر مقادیر ارائه شده توسط client اولویت دارد). در آموزش حاضر، این روش را پیاده خواهیم کرد. در این روش هیچ تغییری بازنویسی نمی شود، مگر اینکه کاربر قبل آن مطلع شده باشد.
می توان تداخلات همزمانی را با مدیریت خطاهای OptimisticConcurrencyException که توسط EF صادر می شود، برطرف ساخت. برای این که EF تشخیص دهد چه زمانی بایستی خطاهای مربوطه را صادر کند، ابتدا لازم است آن تداخلات را شناسایی کند. بنابراین، می بایست پایگاه داده و data model را به درستی پیکربندی نمود. گزینه هایی که برای فعال سازی conflict detection (تشخیص تداخل) در دست دارید به شرح زیر می باشند:
1. در جدول پایگاه داده، یک tracking column (ستون ردیابی) ایجاد می کنیم. این ستون را برای رهگیری و تعیین زمان اصلاح سطر مورد نظر بکار می بریم. سپس می توانیم EF را طوری تنظیم کنیم که آن ستون را در عبارت Where دستورهای Update و Delete اس کیو ال قرار دهد.
نوع داده ی ستون مزبور معمولا rowversion می باشد. مقدار rowversion یک sequential number (عدد ترتیبی) است که با هر بروز رسانی سطر مورد نظر، آن عدد افزایش می یابد. در دستور Update یا Delete، عبارت Where مقدار اصلی tracking column (نسخه ی اصلی و اولیه ی سطر مورد نظر) را نگه می دارد. چنانچه سطری که در دست بروزرسانی است توسط کاربر دیگر تغییر داده شود، در آن صورت مقدار موجود در ستون rowversion با مقدار اصلی یا اولیه ی آن متفاوت خواهد بود و از این رو دستور Update یا Delete نمی تواند (بخاطر عبارت Where) سطر مورد نظر را برای آپدیت پیدا کند. هنگامی که EF پی ببرد که هیچ سطری یا رکوردی توسط دستور Update یا Delete بروز آوری نشده (بدین معنا که تعداد سطرهای ویرایش شده برابر با صفر باشد)، در آن صورت EF آن را یک تداخل همروندی درنظر گرفته و رفتار خود را بر اساس آن تنظیم می کند.
2. EF را گونه ای تنظیم کنید که مقادیر اولیه ی تمامی ستون های موجود در جدول را داخل عبارت Where دستورات Update و Delete قرار دهد.
همان طور که در روش اول تشریح شد، چنانچه از زمانی که سطر برای اولین بار خوانده شد، چیزی در آن تغییر داده شده باشد، در آن صورت عبارت Where هیچ سطری برای بروز رسانی باز نمی گرداند. EF این رخداد را به عنوان یک تداخل همروندی (concurrency conflict) تفسیر می کند. برای آن دسته از جداول پایگاه داده که دربردارنده ی ستون های متعددی هستند، این روش ممکن است باعث ایجاد عبارت های بسیار طولانی Where شود و همچنین شما را مجاب کند مقادیر زیادی از state ها را حفظ و نگداری کنید. پیش تر نیز ذکر شد که حفظ مقادیر زیادی از state ها می تواند کارایی برنامه را تحت تاثیر قرار دهد. بنا به دلایل مذکور، استفاده از این روش توصیه نمی شود.
اگر می خواهید این روش را برای کنترل همروندی پیاده کنید، در آن صورت بایستی تمامی خاصیت های غیر کلید اصلی (non-primary-key) را در موجودیتی که می خواهید concurrency آن را ردیابی کنید، با افزودن خصیصه ی ConcurrencyCheck به آن ها علامت گذاری کنید. این تغییر به EF امکان می دهد تمامی ستون ها را در عبارت WHERE دستورهای UPDATE اضافه (include) کند.
در ادامه ی این آموزش، یک tracking property rowversion به موجودیت Department اضافه خواهیم کرد، یک controller به همره view هایی ایجاد کرده و درنهایت همه چیز را بررسی کرده و از عملکرد صحیح آن ها اطمینان حاصل می کنیم.
فایل Models\Department.cs را باز کرده و یک tracking property به نام RowVersion اضافه کنید:
public class Department { public int DepartmentID { get; set; } [StringLength(50, MinimumLength = 3)] public string Name { get; set; } [DataType(DataType.Currency)] [Column(TypeName = "money")] public decimal Budget { get; set; } [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] [Display(Name = "Start Date")] public DateTime StartDate { get; set; } [Display(Name = "Administrator")] public int? InstructorID { get; set; } [Timestamp] public byte[] RowVersion { get; set; } public virtual Instructor Administrator { get; set; } public virtual ICollection<course> Courses { get; set; } }
خصیصه ی Timestamp مشخص می کند که این ستون باید در عبارت Where دستورهای Update و Delete ارسالی به پایگاه داده گنجانده شود. خصیصه ی ذکر شده از آنجایی Timestamp خوانده می شود که نسخه های قبلی SQL Server از نوع داده ی timestamp، قبل از اینکه rowversion جایگزین آن شود، استفاده می کردند. معادل rowversion در .NET یک byte array (آرایه ای از نوع byte) می باشد.
در صورت تمایل به استفاده از fluent API، می توانید از متد IsConcurrencyToken برای مشخص کردن tracking property مورد نظر استفاده کنید:
modelBuilder.Entity<department>() .Property(p => p.RowVersion).IsConcurrencyToken();
افزودن یک خاصیت باعث تغییر database model شد، از این باید یک migration دیگر اجرا کنید. در پنجره ی PMC، دستورات زیر را وارد نمایید
Add-Migration RowVersion Update-Database
برخی از DBA ها (مدیران پایگاه داده) ترجیح می دهند از stored procedure ها (رویه های ذخیره شده) برای دسترسی به پایگاه داده استفاده کنند. در ویرایش های پیشین EF می توانستید داده های مورد نیاز را با بکارگیری stored procedure ها بازیابی کنید. این کار به وسیله ی اجرای یک query خام صورت می گرفت. اما این امکان وجود نداشت که به EF COREدستور داد با استفاده از stored procedure عملیات بروز رسانی را انجام دهد. در نسخه ی نوین EF Core ، به راحتی می توان Code First گونه ای پیکربندی کرد که از stored procedure ها استفاده کند.
به مثال زیر توجه کنید :
در داخل متد OnModelCreating در داخل کلاس ApplicationDbContext خود کافی است کدی به شکل زیر اضافه شود:
modelBuilder.Entity<department>().MapToStoredProcedures();
این کد به EF Core دستور می دهد با استفاده از stored procedure ها، عملیات درج، بروز رسانی و حذف را بر روی موجودیت Department انجام دهد.
2. در Package Manage Console، دستور زیر را وارد کنید:
add-migration DepartmentSP
فایل Migrations\_DepartmentSP.cs را باز کرده تا کد موجود در متد Up را مشاهده کنید. این متد stored procedure های Insert، Update و Delete را ایجاد می کند:
public override void Up() { CreateStoredProcedure( "dbo.Department_Insert", p => new { Name = p.String(maxLength: 50), Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"), StartDate = p.DateTime(), InstructorID = p.Int(), }, body: @"INSERT [dbo].[Department]([Name], [Budget], [StartDate], [InstructorID]) VALUES (@Name, @Budget, @StartDate, @InstructorID) DECLARE @DepartmentID int SELECT @DepartmentID = [DepartmentID] FROM [dbo].[Department] WHERE @@ROWCOUNT > 0 AND [DepartmentID] = scope_identity() SELECT t0.[DepartmentID] FROM [dbo].[Department] AS t0 WHERE @@ROWCOUNT > 0 AND t0.[DepartmentID] = @DepartmentID" ); CreateStoredProcedure( "dbo.Department_Update", p => new { DepartmentID = p.Int(), Name = p.String(maxLength: 50),
Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"), StartDate = p.DateTime(), InstructorID = p.Int(), }, body: @"UPDATE [dbo].[Department] SET [Name] = @Name, [Budget] = @Budget, [StartDate] = @StartDate, [InstructorID] @InstructorID WHERE ([DepartmentID] = @DepartmentID)" ); CreateStoredProcedure( "dbo.Department_Delete", p => new { DepartmentID = p.Int(), }, body: @"DELETE [dbo].[Department] WHERE ([DepartmentID] = @DepartmentID)" ); }
در Package Manage Console، دستور زیر را وارد نمایید:
update-database
4. برنامه را در debug mode اجرا کرده، تب Departments را باز کنید، سپس Create New را کلیک نمایید.
5. اطلاعات لازم برای یک department جدید را وارد کرده و Create را کلیک نمایید.
6. در محیط Visual Studio، اگر به گزارشات (log) در پنجره ی Output توجه کنید، می بینید که یک stored procedure باعث درج یک سطر Department شده است.
Code First اسم های پیش فرض برای stored procedure ها ایجاد می کند. در صورت استفاده از یک پایگاه داده ی از پیش موجود، ممکن است لازم باشد اسم stored procedure ها را سفارشی تنظیم کنید تا بتوانید از آن stored procedure هایی که قبلا در پایگاه داده تعریف شده اند، استفاده نمایید.
اگر بخواهید کارهایی را که stored procedure های ایجاد شده قادر به انجام آن ها هستند، تنظیم نمایید، در آن صورت بایستی کد ارائه شده توسط scaffolding برای متد Up migrations را که آن stored procedure را ایجاد می کند، ویرایش کنید. در آن صورت تمام تغییرات ایجاد شده توسط شما، هر زمانی که آن migration اجرا می شود منعکس شده و همچنین هنگامی که migrations به صورت خودکار در production پس از نصب (deployment) اجرا می شود، به production database اعمال می گردد.
اگر می خواهید Stored procedure موجود را که قبلا در یک migration ایجاد شده، تغییر دهید، در آن صورت می بایست با استفاده از دستور Add-Migration یک migration خالی ایجاد کرده، سپس کدی بنویسید که متد AlterStoredProcedure را فراخوانی کند.
یک پروژه از نوع C# Asp.net Core Web App از نوع model-view-Controller ایجاد می کنیم :
پکیج های زیر را به پروژه اضافه می کنیم
Install-Package Microsoft.EntityFrameworkCore -Version 5.0.12 Install-Package Microsoft.EntityFrameworkCore.Design -Version 5.0.12 Install-Package Microsoft.EntityFrameworkCore.tools -Version 5.0.12 Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.12 Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore -version 5.0.12 Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore -version 5.0.12
در نهایت به پروژه به شرح ذیل پکیج های ذیل را اضافه می کنیم
یک پوشه به پروژه بنام Models اضافه می کنیم و یک کلاس بنام Customer به پروژه اضافه می کنیم
using System.ComponentModel.DataAnnotations; namespace NewFeaturesEFCore.Models { public class Customer { public int Id { get; set; } [Required] public string Name { get; set; } [Required] public string Email { get; set; } public int PhoneNumber { get; set; } public byte[] RowVersion { get; set; } } }
در Ef Core می تواند به طور خودکار بفهمد که کلید اصلی ما Id خواهد بود و از آنجایی که یک Int است به طور پیش فرض آن را به صورت خودکار افزایش می دهد.
سپس به data annotation که به هر فیلد اضافه کردهایم نگاه میکند و اطلاعات بیشتری در مورد نحوه راهاندازی پایگاه داده به دست میآورد، به عنوان مثال، ما یک Required annotation روی نام و ایمیل داریم، Ef core به طور خودکار گزینه not null را در پایگاه داده اختصاص میدهد.
اکنون اجازه میدهیم ApplicationDbContext خود را ایجاد کنیم که بخش اصلی ادغام EF Core در برنامه ما است.
اجازه دهید یک پوشه جدید در ریشه برنامه خود ایجاد کنیم و یک کلاس به نام ApplicationDbContext به آن اضافه کنیم.
ما از کلاس DbContext که جزء اصلی در Ef Core است، ارث می بریم
یک پوشه بنام Data پروژه اضافه می کنیم و یک کلاس بنام ApplicationDbContext به پروژه اضافه می کنیم
using Microsoft.EntityFrameworkCore; using NewFeaturesEFCore.Models; namespace NewFeaturesEFCore.Data { public class ApplicationDbContext: DbContext { // a Db set is where we tell entity framework where to map a class (entity) to a table public DbSet<Customer> Customers { get; set; } // This is the run time configuration of public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } // ModelBuilder is the fluent mapping protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Customer>() .Property(a => a.RowVersion) .IsRowVersion(); // Cuncurrency property using fluent mapping base.OnModelCreating(modelBuilder); } } }
کانکشن استرینگ مربوط به دیتابیس SQLSERVER را به فایل appsettings.json پروژه اضافه می کنیم
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=.;Database=NewFeaturesEFCore;Trusted_Connection=True;MultipleActiveResultSets=True" } }
حالا می بایستی در کلاس Startup میان افزار EFCore را Inject می کنیم
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddRazorPages(); }
سپس مایگریشن را ایجاد می کنیم و دیتابیس را بروزرسانی می کنیم
Add-Migration "Initial Migration" Update-Database
دقت شود به جدول مشتریان یک فیلد بنام RowVersion اضافه شده است که برای مفاهیم همزمانی استفاده می شود
اجازه دهید اکنون کدی را بنویسیم تا تمام داده های Customer را از پایگاه داده دریافت کنیم، در داخل HomeController خود می توانیم موارد زیر را به Action Index اضافه کنیم.
private readonly ILogger<HomeController> _logger; private readonly ApplicationDbContext _context; public HomeController(ILogger<HomeController> logger, ApplicationDbContext context) { _logger = logger; _context = context; } public async Task<IActionResult> Index() { var customers = await _context.Customers .Where(x => x.Name.Contains("a")) .OrderBy(o => o.Name) .ToListAsync(); return View(customers); }
اکنون که مروری اجمالی بر Ef core و ویژگیهای آن ارائه کردهایم، اجازه دهید در مورد برخی از تغییرات جدیدی که در Ef Core 5 آمده است صحبت کنیم.
نمای اشکالزدایی هنگام اجرای دیباگر، میتوانیم هنگام نگهداشتن ماوس روی پرس و جو، گزینه debugView را ببینیم.
بنابراین میتوانیم ببینیم که فیلد Query یک نمایش SQL از کوئری LINQ ما است که میتوانیم مستقیماً از آنجا کپی کرده و آن را در یک اSQL management studio اجرا کنیم تا خروجی کوئری را آزمایش کنیم و هر مشکلی را که ممکن است با آن مواجه شویم اشکالزدایی کنیم.
نکته:
دقت شود برای اینکه بتوانیم از این حالت استفاده کینم برای دیباگ می بایستی Query را از حالت await خارج کنیم و ToListAsync رادر دو مرحله انجام دهیم
یکی دیگر از مواردی که میتوانیم Query خود را دریافت کنیم، استفاده از متد افزونه در پرس و جو است
.ToQueryString()
var sqlStr = customers.ToQueryString(); Console.WriteLine(sqlStr);
آخرین گزینه فعال کردن گزارشهای عمومی در سراسر برنامه ما است، در کلاس راهاندازیمان، جایی که ApplicationDbContext را در DbContextOptions اضافه کردهایم و میتوانیم لاگ گیری به سیستم را از آنجا فعال کنیم.
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")) .LogTo(Console.WriteLine, LogLevel.Information)); services.AddControllersWithViews(); }
نکته :
حتما کدها رو به حالت قبل بصورت await و ToListAsync تک مرحله ای برگشت دهید در غیراینصورت در حین اجرای درخواست رو دیتابیس خطا خواهید گرفت
در EFCORE پیشرفت های جدیدی در روابط ManyToMany ایجاد شد است روابط M2mM فرض کنید ما یک جدول از مشتریان و یک جدول برای گروه ها داریم رابطه Many-To-Many یعنی یک کاربر می تواند به گروه های مختلف تعلق داشته باشد و یک گروه می تواند چندین کاربر داشته باشد.
به روشی که در یک پایگاه داده انجام می شود، یک جدول در وسط بین 2 جدولی که روابط Many-to-Many دارند ایجاد می کنیم و آن جدول را Join Table می نامیم که در مورد ما Join Table جدول CustomerGroup است. و هر ردیف داخل آن جدول 1 مشتری را به 1 گروه مرتبط می کند
برای درک بهتر موضوع ابتدا این روابط را به روش قدیمی در EF Core پیاده سازی می کینم و سپس با امکانات جدیدی که به EFCore اضافه شده است با روش جدید پیاده سازی را انجام می دهیم در حال حاضر ما یک جدول مشتری داریم که اجازه بدهدید 2 جدول دیگر را به پروژه اضافه کنیم و سپس روابط ManyToMany را ایجاد کنیم
در پوشه Models اجازه می دهد تا 2 کلاس Group و CustomerGroup اضافه کنیم
using System.Collections.Generic; namespace NewFeatureEFCore.Models { public class Group { public int Id { get; set; } public string Name { get; set; } public ICollection<CustomerGroup> Groups { get; set; } } }
namespace NewFeatureEFCore.Models { public class CustomerGroup { public int Id { get; set; } public Customer Customer { get; set; } public Group Group { get; set; } } }
و ما باید کلاس مشتری را به موارد زیر به روز کنیم
public ICollection<CustomerGroup> Groups {get;set;}
مرحله بعدی به روز رسانی ApplicationDbContext است
public DbSet<Group> Groups { get; set; } public DbSet<CustomerGroup> CustomerGroups { get; set; }
اجازه می دهد تا اسکریپت های migration خود را ایجاد کنیم تا پایگاه داده ما به روز شود
Add-Migration "Added M2M relationships" Update-Database
حالا ببینیم چگونه میتوانیم این کد را با EF Core 5 بهروزرسانی کنیم، اولین کاری که میخواهیم انجام دهیم این است که جدول CustomerGroup را حذف کنیم و سپس باید برخی بهروزرسانیها را در مدل مشتری و گروه انجام دهیم.
For the customer Model
For the Group Model
تعییرات بر روی دیتابیس اعمال می کنیم
Add-Migration "Added NewFeature_M2M relationships" Update-Database
در اینجا می بینیم که JoinTable را حذف کرده ایم و کار را به EF Core واگذار کرده ایم تا JoinTable را برای ما ایجاد و مدیریت کند. به روز رسانی دیگری که باید انجام دهیم، درخواست LINQ است، زیرا ما دیگر جدول CustomerGroup نداریم، پرس و جو بسیار ساده تر و خواندنی تر به نظر می رسد.
var customersElec = _context.Customers .Where(x => x.Groups.Any(g => g.Name == "Electronics"));
پس چگونه این جادو اتفاق افتاد، EF Core در پسزمینه مدلهایی را که ما داریم تجزیه و تحلیل کرد و JoinTable را برای ما ایجاد کرد و کنترل آن را در دست گرفت.
Inheritance Mapping
ارث بری در دانت بر اساس از کلاس هاس base class, sub class است و میخواهیم آنها را به یک پایگاه داده رابطهای نگاشت کنیم. به ما اجازه میدهد مدلهای دیگری را اضافه کنیم تا ببینیم قبل از Net 5 چگونه کار میکردیم و سپس میتوانیم نحوه پیادهسازی آن را با ویژگیهای جدید ببینیم. در Net 5 داخل پوشه Models ما اجازه می دهد 2 مدل جدید اضافه کنیم.
namespace NewFeatureEFCore.Models { public class VipCustomer:Customer { public string VipTeir { get; set; } } }
using System; namespace NewFeatureEFCore.Models { public class CorporateCustomer:Customer { public string CompanyName { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } } }
برای اطلاع از این مدلهای جدید، باید ApplicationDbSettings خود را بهروزرسانی کنیم، در روش OnModelCreating، موارد زیر را اضافه میکنیم.
public DbSet<VipCustomer> VipCustomer { get; set; } public DbSet<CorporateCustomer> CorporateCustomer { get; set; }
modelBuilder.Entity<VipCustomer>(); modelBuilder.Entity<CorporateCustomer>();
Let us add our migration scripts
Add-Migration "Adding 2 tables" Update-Database
اگر به پایگاه داده خود نگاه کنیم، می بینیم که به جای داشتن 3 جدول که نشان دهنده سلسله مراتب مورد نظر ما است، یک مشتری جدول با تمام فیلدهای داخل آن داریم. این روش پیاده سازی TPH - table per hierarchy نامیده می شود.
نحوه تمایز هسته Ef بین ردیف کدام جدول از طریق فیلد Discriminator است. این ویژگی از نسخههای قبلی در EF Core 5 در Ef core موجود بوده است تا ببینیم چگونه میتوانیم جداسازی را انجام دهیم.
Let us update our ApplicationDbContext
modelBuilder.Entity<VipCustomer>().ToTable("VipCustomers"); modelBuilder.Entity<CorporateCustomer>().ToTable("CorporateCustomers");
Let us add our migrations now
Add-Migration "Adding 2 tables TPT" Update-Database
حال اگر به پایگاه داده خود نگاه کنیم، به جای داشتن 1 جدول بزرگ که نشان دهنده 3 جدول است، نتیجه واقعاً متفاوت است. بنابراین در حال حاضر مشتری در جدول های مختلف پخش شده است زیرا ما فرمت TPT را پیاده سازی کرده ایم.
درک این پیاده سازی ساده است زیرا هر مدل دارای جدولی است که آن را نشان می دهد، با این حال عملکرد در پایگاه داده می تواند ضربه بخورد زیرا هر بار که ما نیاز به پرس و جو از مشتری داریم، پیوستن بین جداول باید اتفاق بیفتد، و اتصال ها عملیات سنگینی در پایگاه داده هستند. . به عنوان مثال، اگر ما 6 سلسله مراتب جدول داشته باشیم، 6 اتصال وجود دارد که یک پرس و جو عملکرد بسیار سنگین است.
اگرچه این ویژگی منتشر شده است، روش توصیه شده همچنان استفاده از TPH به طور پیش فرض است.
با تشکر از مطالعه مقاله من
این مقاله در حین تهیه می باشد و ادامه دارد
برای دانلود سورس های این مقاله می توانید از گیت هاب بنده Clone کنید
در دوره های آموزش تضمینی مجتمع فنی ارومیه که به صورت خصوصی و عمومی در دو شیوه حضوری و آنلاین برگزار می شود سرفصل های بسیار متنوع و کاربردی را بصورت پروژه محور آموزش داده می شود تا شخص کارآموز بتواند بلافاصله پس از اتمام این دوره در کمترین زمان ممکن وارد بازار کار شود.
آموزش تخصص ماست با ما حرفه ای شوید
جهت مشاوره با شماره 09149431772 در ارتباط باشید ...