Mohammad Ehsan Tabakhian
Mohammad Ehsan Tabakhian
خواندن ۸ دقیقه·۶ سال پیش

Eager Loading در EntityFramework Core

Eager Loading
Eager Loading

در مقاله آغازی براتون به صورت خلاصه هر سه حالت لود اطلاعات NavigationProperty ها رو توضیح دادم!تو این مقاله قصد دارم که داستان لود زودهنگام(Eager Loading) رو براتون تعریف کنم ! همونطور که تو مقاله آغازین هم خدمتتون گفتم لود زودهنگام اولین روش برای دریافت اطلاعات موجود در پراپرتی نویگیشن های یه مدله ! شما با نوشتن کامند Include(Target Navigation Property) در دستورات لینک خودتون که بر روی یک Entity از DatabaseContext شما زده میشه می تونید برای ORM مشخص کنید که قصد دارید تمام اطلاعات مربوط به اون نویگیشن پراپرتی رو هم همزمان در کوئری اولیه که برای دریافت اطلاعات اصل مدل درخواستی به سمت دیتابیس فرستاده میشه ، دریافت کنید !

در مثال پایین ما در واقع یه Entity بلاگ داریم که با Entity دیگری به نام پست ارتباط یک به چند داره. و قصد داریم که لیست بلاگ ها رو از دیتابیس دریافت کنیم و همزمان لیست تمام پست های مربوط به هر بلاگ در نویگیشن پراپرتی مربوطه( blog.Posts ) در هنگام دریافت اطلاعات بلاگ ها موجود باشه.

خوبیه این داستان اینکلود اینکه محدودیت استفاده نداره ! یعنی شما ممکنه همزمان مشخص کنید که دیتا چند نویگیشن پراپرتی رو در کوئری اولیه نیاز دارید و اصلا و ابدا دوووشششواررری نداریم ???!!

یکم ممکنه بد توضیح داده باشم ولی به طور کلی بسیار آسونه و احتمال خیلی زیاد بلد بودید از قبل ! ولی این داستان آقای اینکلود ریزه کاری هایی هم داره که قراره امروز با هم یادشون بگیریم !

فقط همین اول کاریه بزارید یه چیزی رو براتون توضیح بدم که بعدا نیایید بگید چرا ما هنوز اینکلود استفاده نکردیم ولی بعضی از مدل هامون (فرض کنید بعضی از بلاگ های بالا از لیست بلاگ ها) انگار به صورت پیش فرض نویگیشن پراپرتی هاشون دیتا دارن !!!! ????? !!!!

همونجور که میدونید وقتی ما میخوایم در کد خودمون از EF استفاده کنیم ، برای ارتباط با دیتابیس روال به این شکله که اول یه آبجکت(اینستنس یا شئ) از کلاس DatabaseContex خودمون میسازیم(ممکنه اینجکتش کرده باشیم ولی خب مهم اینکه یک آبجکت حاضر و زنده ازش داریم)، در مرحله بعد ما برای استفاده یه دات میزنیم بعد این آبجکت و یه کوئری با استفاده از دستورات Linq ایجاد میکنیم! این کاری بود که ما میکنیم برای گرفتن اطلاعات! ولی حالا سوال اینکه خود EF داره برای آوردن اطلاعات تا وسط کد ما چیکار میکنه ؟!؟!

خیلی ساده بخوام بگم! بعد از ساختن کوئری ، این کوئری شما اول یه سری مراحل برای تصحیح رو طی میکنه و تبدیل به کامند TSQL میشه و در نهایت به دیتابیس میره! اطلاعات از دیتابیس بازگشت داده میشن ولی به صورت لیست های جنریک نیستن! پس ORM اونا رو براتون کانورت و کست میکنه به فرمت کلاس های مدلی که برای جدول های دیتابیس نوشتین! نکته مهمش اینجاست که در نهایت این دیتاها که در فرمت لیست های جنریک یا آبجکت هایی از EntityModel های شما هستن میان و میرسن به اون آبجکتی که از DatabaseContex داشتیم و کوئری هم توسط اون ارسال کرده بودیم! انگاری اصل خویش رو بازجووییدن ?!و اون آبجکت عزیز هم به شما پس میده اطلاعات کوئری مورد نظرتون رو و شما تو لیستی چیزی ذخیره میکنید! نکته ای که اینجا خیلی مهمه!!!!! اینکه اطلاعات یکبار از این آقای آبجکت DatabaseContext ما عبور کرده قبل از رسیدن به ما و همین کافی بوده تا ایشون زحمت بکشن و کش(ذخیره موقت) بکنن دیتا هارو ! فلذا دفعه بعد که شما درباره اون آبجکت کوئری بفرستید ایشون چک میکنن اول اگر که ببینه تو خودش کش شدش رو داره همون رو بهتون میدن یه جورایی!

پس زمانی که شما دیتا یه نویگیشن پراپرتی رو درخواست نمیکنید از EF (اینکلود استفاده نمیکنید). ولی میبینید که به جای نال بودن ، این بنده خدا دیتا داره توش ! برنگردید بگید یاللعجب ??? چرا اینجوری شد! بدانید و آگاه باشید که قبلا با اون آبجکت DatabaseContext یه بار دیتای اون نویگیشن پراپرتی رو درخواست کرده بودید و ایشون دارن از کَش خودشون بهتون میدن ?!

آها تا یادم نرفته! یه نکته ای رو همینجا بهتون میگم که بعدا ممکنه دیگه شرایطش پیش نیاد! این از اون نکاتیه که ممکنه هرجایی نبینیدش و اگر بهش دقت نکنید و ندونید که داره چه اتفاقی میافته ! به فنا میرید! من خودم به شخصه چندین روز دنبال باگی میگشتم که این مسئله ایجاد کرده بود! کش کردن اطلاعات توسط این آبجکت DatabaseContext در 90 درصد اوقات باعث بهبود سرعت میشه! ولی در 10 درصد بقیه باعث بدبختی فلذا خاموش کردن حالت کش موقت اصلا درست نیست چون مزایای اون 90 درصد هم از دست میدید!

برای اینکه این کش شدن اذیتتون نکنه باید حواستون باشه آبجکت های مهمی که هر چند ثانیه یه بار ممکنه اطلاعاتشون عوض بشه در دیتابیس، توسط DatabaseContext های دیگه ای در جاهای دیگه از برنامه باید حتما قبل از استفاده حیاتی یکبار از دیتابیس ریلود بشن! یعنی ممکنه در دیتابیس اون دیتا تغییر کرده باشه ولی DatabaseContext ما هنوز فکر میکنه تغییری نکرده و کش خودش رو بهمون میده ???! اینجا ما باید وادارش کنیم که یکبار ریلود کنه اطلاعات اون آبجکت به خصوص رو از دیتابیس (این اتفاق در اپلیکیشن های وب بیشتر رخ میده که چندین آبجکت DatabaseContext دارید) !

بدانید و آگاه باشید برای ریلود کردن یک آبجکتی که قبلا استفاده کردیم (کش شده) و احتمال میدیم که تغییر کرده در دیتابیس ! باید از متود Reload استفاده کنیم :
myDatabaseContextObject.Entry(blog).Reference(b => b.Owner).Reload() ;
یا حتی به صورت کلی تر :
myDatabaseContextObject.Entry(blog).Reload() ;

یکی از نکات دیگه در استفاده از اینکلود ، لود چندین سطحی از اطلاعات هست. به طوریکه میتونید با متد ThenInclude لیست ها و ابجکت ها رو تا جایی که دوست دارید بشکافید و برید پایین و هرکدوم از داده هایی که نیاز دارید رو اینکلود کنید. مثلا در مثال پایین ، ما لیست رکورد های جدول Blogs رو از دیتابیس درخواست میکنیم ! و قصد داریم در همین درخواست اطلاعات لیست Posts از هر رکورد بلاگ هم داشته باشیم(اینکلود اول) و همینطور از داخل اطلاعات Post ها که با اینکلود اول لود کردیم! اطلاعات نویگیشن پراپرتی (نویسنده)Author هر پست که خودش یه آبجکتی به نام Author داره هم لود بشه!

همینطور که میبینید هی داریم در اطلاعاتی که با اینکلود اولیه لود کردیم میریم پایین و پایین تر و محدودیتی هم نداره ! مثلا میتونیم تا چندین مرحله دریل کنیم و به سطوح پایین تری بریم و اطلاعات مورد نیازمون رو مشخص کنیم !

قبلا گفتیم که میتونستید همزمان از چند اینکلود استفاده کنید ! الان هم مشکلی نیست میتونید همزمان هم چندین Include و هم چندین ThenInclude داشت باشید و لذت ببرید ???!!!

فقط به این نکته ریز دقت کنید که هربار شما از ThenInclude استفاده می کند شما رو توی اون داده ای که دفعه قبل اینکلود کردید و الان ThenIclude روش زدید فرو میبره ! یعنی مثلا اگر بخوایید که Photo از نویگیشن پراپرتی Author رو لود کنید چون Photo در درون Author قرار داره و یک سطح فرو میرید حق دارید ThenInclude استفاده کنید(مثل کد بالا) ! ولی مثلا فرض کنید دو تا نویگیشن پراپرتی هم سطح دارید که هر دوتاشون درون Post قرار دارن مثل Author و Tags :

Blog -> Posts -> Author و Blog -> Posts -> Tags

در این شرایط باید طبق نمونه زیر عمل کنید و هر دوتا شاخه هم عرض رو دوباره از ریشه اصلی اینکلود کنید :


نکته مهم بعدی هم که باید بهتون بگم در مورد کاربرد اینکلود بر روی مدل ارث برده از یه مدل دیگست ! یکم ممکنه توضیح این قسمت سخت باشه برا همین اول مثال رو میزارم ? که ببینید و بعد توضیح میدم :

خب همونجور که میبینید با استفاده از مدل های بالا یه جدول Person در دیتابیس داریم که بعضی از اون ها ممکنه Student باشند و بعضی هم نه! امیدوارم نحوه کار با InheritedEntities رو بلد باشید که چه اتفاقی میافته وقتی که این جوری جداول رو ایجاد میکنید(در واقع خود EF یه پراپرتی بولین میگیره تو جدول People که آیا این رکورد Student هست یا نه و خودش هندل میکنه ...) که خب طبیعتا اینجا واردش نمیشیم ولی در این حد بدونید که الان میخوایم یه حرکتی بزنیم که موقع اینکلود کردن به EF بگیم اگر که اون رکورد مورد نظر از Person که داریم لود میکنیم در لیست ، Student بود شما لطف کن و اون نویگیشن پراپرتی School رو هم دیتاشو برامون لود کن! اینکار توسط یکی از سه روش زیر قابل انجام هست :


به عنوان نکته پایانی هم میخوام بحث اینکلود های نادیده گرفته شده ?☹️ رو مطرح کنم ! عزیزان دل توجه کنید که اگه بعد از استفاده از اینکلود بیایید و از Select یا دستورات مشابه که جنس خروجی رو تغییر میده استفاده کنید به طوری که در تایپ خروجی جدید از اطلاعاتی که با اینکلود لود کردید استفاده نشه ! درواقع اون لود کردن شما هیچ استفاده ای نداشته و اشتباه بوده ! در این حالت خود EF زحمت میکشه و به جای شما برای بالا رفتن کارایی و سرعت کوئری ، کلا اینکلود یا اینکلود هایی که نوشتید رو بیخیال میشه و حتی نگاهشون هم نمیکنه و طبیعتا در TSQL کوئری تولیدی اثری هم ازشون پیدا نمیشه! مثال این حرکت خز :

در نهایت اینم بدونید که در حالت عادی خود EF این کد زشت ? که توش همچین اتفاقی افتاده رو اگه ببینه براتون تویه Output موقعه کامپایل یه وارنینگ و هشدار بسیار ساده لاگ میکنه که " من اینو نادیده گرفتم ! " اگر قصد دارید که به صورت جدی تر تو نرم افزار اگر همچین اتفاقی افتاده بود براتون وارنینگ درشت تر یا حتی اروری رو ایجاد کنه و پرت کنه تو چشای برنامه نویس ??? (throw کنه) ، میتونید تو DbContext.OnConfiguring یا Startup.cs(اگر از AspNetCore استفاده میکنید) برای کامپایلر مشخص کنید :


خوشحال شدم که منو همراهی کردید تا اینجا! این مقالات حداقل دو قسمت دیگه در این زمینه داره که امیدوارم اونم پیگیری کنید! قسمت بعد قراره درمورد Lazy Loading باشه که خیلی جذاب و جالبه ??❤️! به قول خارجی ها ??? Stay tuned for my next articles .

مطالب این مجموعه مقالات مستقیم از داکیومنت رسمی مایکروسافت اومده !
و سمپل ها رو هم میتونید از اینجا ببینید : AspNetCore GitHub
برنامه نویسیprogrammingEntityFrameworkmicrosoftasp net
شاید از این پست‌ها خوشتان بیاید