مقدمه

در درس قبل، با اصول اولیه‌ی کوئری زدن به پایگاه داده با استفاده از EF Core و LINQ آشنا شدیم. اما کار با داده‌ها همیشه به سادگیِ خواندن یک جدول نیست. ما اغلب با داده‌های مرتبط (related data) که در جداول مختلف قرار دارند، سروکار داریم. برای مثال، یک "سفارش" به یک "مشتری" تعلق دارد و شامل لیستی از "محصولات" است. نحوه‌ی بارگذاری این داده‌های مرتبط، تأثیر زیادی بر عملکرد برنامه دارد. علاوه بر این، EF Core دارای یک مکانیزم هوشمند به نام ردیابی تغییرات (Change Tracking) است که به طور خودکار وضعیت اشیائی را که از پایگاه داده خوانده‌ایم، زیر نظر می‌گیرد تا فرآیند به‌روزرسانی و حذف آن‌ها را ساده کند. در این درس، با الگوهای مختلف بارگذاری داده و مکانیزم ردیابی تغییرات آشنا خواهیم شد.

الگوهای بارگذاری داده‌های مرتبط

EF Core سه الگوی اصلی برای بارگذاری داده‌های مرتبط فراهم می‌کند.

۱. بارگذاری حریصانه (Eager Loading)

در این الگو، شما به EF Core به طور صریح می‌گویید که داده‌های مرتبط را به همراه کوئری اصلی و در یک سفر به پایگاه داده، بارگذاری کند. این کار با استفاده از متد بسطی Include() انجام می‌شود. این روش معمولاً کارآمدترین الگوست، زیرا تمام داده‌های مورد نیاز را در یک کوئری واحد دریافت می‌کند و از مشکل "N+1 query" جلوگیری می‌کند.

۲. بارگذاری صریح (Explicit Loading)

در این الگو، شما ابتدا موجودیت اصلی را بارگذاری کرده و سپس در زمان دلخواه، به صورت جداگانه درخواست بارگذاری داده‌های مرتبط با آن را می‌دهید. این کار با استفاده از متد Load() بر روی DbContext.Entry(entity).Collection(...) یا DbContext.Entry(entity).Reference(...) انجام می‌شود.

۳. بارگذاری تنبل (Lazy Loading)

در این الگو، داده‌های مرتبط تنها زمانی از پایگاه داده خوانده می‌شوند که شما برای اولین بار به پراپرتی ناوبری (navigation property) آن‌ها دسترسی پیدا کنید. این کار به صورت خودکار و در پشت صحنه انجام می‌شود. هرچند این روش ساده به نظر می‌رسد، اما می‌تواند بسیار خطرناک باشد، زیرا ممکن است منجر به ارسال تعداد زیادی کوئری به پایگاه داده شود (مشکل N+1) بدون اینکه شما متوجه شوید. برای استفاده از این الگو، پراپرتی‌های ناوبری شما باید virtual باشند و پکیج NuGet به نام Microsoft.EntityFrameworkCore.Proxies باید نصب و پیکربندی شود.

ردیابی تغییرات (Change Tracking)

یکی از قدرتمندترین ویژگی‌های EF Core، ردیاب تغییرات داخلی آن است. وقتی شما یک موجودیت را با استفاده از یک کوئری از DbContext می‌خوانید، EF Core یک "عکس فوری" (snapshot) از وضعیت اولیه‌ی آن موجودیت می‌گیرد و آن را در حافظه‌ی خود نگه می‌دارد. به این موجودیت، یک موجودیت ردیابی‌شده (tracked) گفته می‌شود.

سپس، هر زمان که شما متد SaveChanges() را فراخوانی می‌کنید، EF Core:

  1. مقادیر فعلی تمام موجودیت‌های ردیابی‌شده را با عکس فوری اولیه‌ی آن‌ها مقایسه می‌کند.
  2. اگر تغییری پیدا کند (مثلاً مقدار یک پراپرتی عوض شده باشد)، وضعیت آن موجودیت را به Modified تغییر می‌دهد.
  3. برای تمام موجودیت‌هایی که به DbContext اضافه (Add) شده‌اند، وضعیت را Added قرار می‌دهد.
  4. برای تمام موجودیت‌هایی که حذف (Remove) شده‌اند، وضعیت را Deleted قرار می‌دهد.
  5. در نهایت، دستورات SQL مناسب (INSERT, UPDATE, DELETE) را فقط برای موجودیت‌های تغییریافته تولید و در یک تراکنش واحد به پایگاه داده ارسال می‌کند.

این مکانیزم هوشمند، فرآیند به‌روزرسانی داده‌ها را فوق‌العاده ساده می‌کند. شما فقط باید شیء را از پایگاه داده بخوانید، پراپرتی‌های آن را تغییر دهید و SaveChanges() را فراخوانی کنید. نیازی به نوشتن هیچ دستور UPDATE یا مشخص کردن اینکه کدام فیلدها تغییر کرده‌اند، نیست.

غیرفعال کردن ردیابی با AsNoTracking

ردیابی تغییرات هزینه دارد، زیرا EF Core باید عکس فوری تمام اشیاء خوانده‌شده را در حافظه نگه دارد. در سناریوهایی که شما داده‌ها را فقط برای نمایش می‌خوانید و قصد هیچ‌گونه تغییری در آن‌ها را ندارید (یک سناریوی فقط-خواندنی)، می‌توانید با استفاده از متد بسطی AsNoTracking()، به EF Core بگویید که این موجودیت‌ها را ردیابی نکند. این کار باعث بهبود قابل توجهی در عملکرد کوئری‌های فقط-خواندنی می‌شود.