راهنمای جامع بهینه‌سازی عملکرد در EF Core: سرعت را قربانی نکنید

Entity Framework Core (EF Core) یکی از محبوب‌ترین ORMها در اکوسیستم دات‌نت است که توسعه‌دهندگان را قادر می‌سازد تا با دیتابیس به صورت شی‌گرا (Object-Oriented) تعامل کنند. اما این راحتی گاهی هزینه‌بر است. "انتزاع" (Abstraction) می‌تواند جزئیات ناکارآمدی کوئری‌ها را پنهان کند و منجر به کندی اپلیکیشن شود. در این مقاله، به بررسی عمیق تکنیک‌های Query Performance Tuning می‌پردازیم تا مطمئن شویم کدهای ما نه تنها تمیز، بلکه سریع و مقیاس‌پذیر هستند.
کینگتو - آموزش برنامه نویسی تخصصصی - دات نت - سی شارپ - بانک اطلاعاتی و امنیت

راهنمای جامع بهینه‌سازی عملکرد در EF Core: سرعت را قربانی نکنید

13 بازدید 0 نظر ۱۴۰۴/۰۹/۲۰

۱. تشخیص گلوگاه‌ها: نمی‌توانید آنچه را نمی‌بینید اصلاح کنید

قبل از هرگونه بهینه‌سازی، باید بدانید مشکل کجاست. حدس زدن در پرفورمنس خطرناک است.

ابزارهای مانیتورینگ

  • LogTo در Console: ساده‌ترین راه برای دیدن کوئری‌های SQL تولید شده در محیط توسعه.

  • SQL Server Profiler / Azure Data Studio: برای مشاهده دقیق آنچه به دیتابیس ارسال می‌شود.

  • MiniProfiler: ابزاری عالی برای دیدن مدت زمان اجرای هر کوئری در صفحات وب.

  • Application Insights: برای محیط‌های Production حیاتی است.

نکته مهم: همیشه به تعداد کوئری‌های ارسالی و زمان اجرای آن‌ها (Execution Time) دقت کنید.

 

۲. مشکل کلاسیک N+1 (و راه حل آن)

یکی از رایج‌ترین دلایل کندی در EF Core، مشکل N+1 است. این اتفاق زمانی می‌افتد که شما یک لیست از موجودیت‌ها را واکشی می‌کنید (1 کوئری) و سپس برای دسترسی به موجودیت‌های وابسته هر کدام، یک کوئری جداگانه اجرا می‌شود (N کوئری).

سناریوی مشکل‌دار (Lazy Loading)

فرض کنید لیستی از "سفارش‌ها" (Orders) دارید و می‌خواهید نام "مشتری" (Customer) هر سفارش را چاپ کنید:

// این کد ابتدا تمام سفارش‌ها را می‌گیرد (1 کوئری)
var orders = context.Orders.ToList();

foreach (var order in orders)
{
    // اینجا برای هر سفارش، یک کوئری به دیتابیس می‌زند تا مشتری را پیدا کند (+N کوئری)
    Console.WriteLine(order.Customer.Name);
}

راه حل: Eager Loading

استفاده از Include باعث می‌شود EF Core با استفاده از JOIN، داده‌های وابسته را در همان کوئری اول واکشی کند.

var orders = context.Orders
    .Include(o => o.Customer) // داده‌های مشتری را همین حالا بیار
    .ToList();

 

۳. جادوی AsNoTracking برای کوئری‌های فقط خواندنی

EF Core به صورت پیش‌فرض تمام موجودیت‌هایی که واکشی می‌کند را در حافظه Track (ردیابی) می‌کند تا تغییرات آن‌ها را تشخیص دهد و در SaveChanges اعمال کند. این فرآیند هزینه حافظه و CPU دارد.

اگر هدف شما فقط نمایش داده‌هاست و قرار نیست آن‌ها را ویرایش کنید، حتماً از AsNoTracking استفاده کنید.

مقایسه عملکرد

  • با Tracking: ایجاد Snapshot از داده‌ها، بررسی مداوم تغییرات.

  • بدون Tracking: صرفاً نگاشت داده‌ها از SQL به اشیاء C#.

// بهینه‌ترین حالت برای لیست‌های نمایش (Grids, Reports)
var products = context.Products
    .AsNoTracking()
    .Where(p => p.Price > 1000)
    .ToList();

 

۴. فقط آنچه نیاز دارید را بردارید (Projection)

آیا واقعاً به تمام ۵۰ ستون جدول Users نیاز دارید وقتی فقط می‌خواهید نام کاربر را در هدر سایت نمایش دهید؟ واکشی ستون‌های اضافی باعث افزایش ترافیک شبکه و مصرف حافظه می‌شود.

استفاده از Select برای انتخاب فیلدهای خاص (Projection) یکی از موثرترین روش‌های بهینه‌سازی است.

// بد: واکشی تمام ستون‌ها (شامل عکس پروفایل با حجم بالا و...)
var users = context.Users.ToList();

// عالی: فقط ستون‌های مورد نیاز
var userNames = context.Users
    .Select(u => new { u.Id, u.Name }) // Anonymous Type
    .ToList();

 

۵. فیلترینگ سمت کلاینت در برابر سمت سرور

یکی از تغییرات مهم در EF Core 3.0 به بعد، سخت‌گیری در مورد Client Evaluation بود. شما باید مطمئن شوید که شرط‌های Where شما به SQL ترجمه می‌شوند، نه اینکه کل داده‌ها به حافظه رم سرور بیاید و آنجا فیلتر شود.

اشتباه رایج

استفاده از متدهای C# که معادل SQL ندارند در داخل کوئری.

// این کد ممکن است باعث شود تمام رکوردها فیلتر نشده به حافظه بیایند (اگر EF نتواند ترجمه کند)
var result = context.Employees
    .Where(e => SomeCustomCSharpMethod(e.Name))
    .ToList();

راه حل: تا حد امکان از توابع استاندارد LINQ که قابل ترجمه به SQL هستند استفاده کنید یا داده‌ها را ابتدا فیلتر اولیه (در دیتابیس) و سپس فیلتر نهایی (در حافظه) کنید (فقط اگر حجم داده کم شده باشد).

 

۶. انفجار کارتزین و Split Queries

وقتی شما چندین رابطه یک-به-چند (Collection) را Include می‌کنید، دیتابیس برای بازگرداندن نتیجه مجبور به ایجاد ضرب دکارتی (Cartesian Product) می‌شود که حجم داده‌های تکراری را به شدت افزایش می‌دهد.

راه حل: AsSplitQuery (معرفی شده در EF Core 5)

این متد به EF می‌گوید به جای یک کوئری غول‌پیکر با چندین JOIN، کوئری را به چند کوئری کوچکتر و بهینه تقسیم کند.

var authors = context.Authors
    .Include(a => a.Books)
    .Include(a => a.Awards)
    .AsSplitQuery() // این دستور کوئری‌ها را تفکیک می‌کند
    .ToList();

توجه: در AsSplitQuery باید مراقب سازگاری داده‌ها (Data Consistency) باشید، زیرا بین اجرای کوئری اول و دوم ممکن است داده‌ها تغییر کنند (هرچند در اکثر سناریوهای خواندن مشکلی نیست).

 

۷. عملیات‌های گروهی (Bulk Operations)

تا قبل از EF Core 7، برای آپدیت کردن ۱۰۰۰ رکورد، باید ۱۰۰۰ رکورد را واکشی می‌کردید، تغییر می‌دادید و سپس SaveChanges را صدا می‌زدید.

در نسخه‌های جدید (EF Core 7 و 8)، متدهای ExecuteUpdate و ExecuteDelete معرفی شده‌اند که مستقیماً روی دیتابیس عمل می‌کنند بدون اینکه داده‌ها را به حافظه بیاورند.

// روش جدید و فوق‌سریع: بدون واکشی داده به حافظه
context.Products
    .Where(p => p.Category == "Old")
    .ExecuteUpdate(s => s.SetProperty(p => p.Price, p => p.Price * 1.1));

 

۸. نکات تکمیلی و پیشرفته

  • ایندکس‌گذاری (Indexing): هیچ‌کدام از تکنیک‌های بالا اگر دیتابیس شما ایندکس‌های مناسب (روی کلیدهای خارجی و ستون‌های جستجو) نداشته باشد، کارساز نخواهد بود. همیشه Execution Plan دیتابیس را چک کنید.

  • استفاده از Compiled Queries: برای کوئری‌هایی که بسیار پرتکرار هستند (مثلاً هزاران بار در ثانیه)، هزینه تبدیل LINQ به SQL قابل توجه است. EF.CompileQuery می‌تواند این ترجمه را کش کند.

  • Pagination (صفحه‌بندی): همیشه از Skip و Take استفاده کنید تا حجم داده‌های برگشتی مدیریت شود.

 

خلاصه تکنیک‌های کلیدی

تکنیک کاربرد تاثیر بر عملکرد
AsNoTracking کوئری‌های فقط خواندنی کاهش مصرف حافظه و CPU
Select (Projection) انتخاب ستون‌های خاص کاهش ترافیک شبکه و IO دیتابیس
Include جلوگیری از N+1 کاهش شدید تعداد رفت و برگشت به دیتابیس
AsSplitQuery روابط یک-به-چند پیچیده جلوگیری از انفجار حجم داده‌ها
ExecuteUpdate/Delete آپدیت/حذف گروهی حذف سربار واکشی و Change Tracking

 

نتیجه‌گیری

بهینه‌سازی در EF Core به معنای کنار گذاشتن LINQ و نوشتن Stored Procedure برای همه چیز نیست. با درک نحوه ترجمه کوئری‌ها، استفاده صحیح از AsNoTracking، مدیریت بارگذاری داده‌ها (Eager vs Lazy) و بهره‌گیری از ویژگی‌های مدرن مثل ExecuteUpdate، می‌توانید به سرعتی بسیار نزدیک به Dapper یا ADO.NET دست پیدا کنید، در حالی که همچنان از مزایای توسعه سریع EF Core بهره می‌برید.

 
لینک استاندارد شده: HjuV

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.
جستجوی مقاله و آموزش
دوره‌ها با تخفیفات ویژه