بهترین روش‌های بهینه‌سازی EF Core برای دیتابیس‌های بزرگ (قابلیت‌های EF Core 8 تا 10)

EF Core طی سال‌های اخیر از یک ORM ساده به یک موتور داده‌ی پیشرفته با قابلیت‌های سطح Enterprise تبدیل شده است. هنگامی که دیتابیس در ابعاد بزرگ کار می‌کند—ده‌ها میلیون رکورد، صدها جدول، Queryهای پیچیده و بار ترافیکی بالا—دیگر استفاده‌ی عادی از EF Core جواب نمی‌دهد و باید با معماری، تکنیک‌ها و قابلیت‌های پرفورمنسی جدید به سراغ طراحی لایه داده رفت.

نسخه‌های 8، 9 و به‌خصوص 10 EF Core جهش‌های بزرگی در سرعت Queryها، مصرف حافظه، عملیات Bulk، پشتیبانی از JSON، پردازش موازی، مدل‌سازی پیشرفته و حتی جستجوی برداری (Vector Search) ایجاد کرده‌اند. این محتوا یک راهنمای جامع است که توضیح می‌دهد در دیتابیس‌های بزرگ چطور باید EF Core را بهینه کرد، از چه قابلیت‌هایی استفاده کرد، و چگونه از مشکلات رایج جلوگیری کرد.

امکانات EF Core 8

EF Core 8 بیشتر از هر نسخه‌ای روی عملکرد و بهینه‌سازی تمرکز کرد. این نسخه برای سناریوهایی طراحی شد که داده‌ها حجم بالا دارند و Queryها باید سبک و قابل پیش‌بینی باشند. یکی از پایه‌ای‌ترین بهبودها مربوط به پشتیبانی بهتر از JSON و عملیات‌های Bulk بدون نیاز به Track کردن داده‌ها است.

۱) پشتیبانی بهبود یافته JSON

در بسیاری از پروژه‌های مدرن، بخشی از داده‌ها ساختار پویا دارند و نگه‌داشتن آن‌ها در چند جدول مجزا منجر به پیچیدگی زیاد و Joinهای سنگین می‌شود. JSON بهترین گزینه برای مدل‌سازی داده‌های نیمه‌ساخت‌یافته است. EF Core 8 اجازه می‌دهد بخش‌هایی از مدل را به آسانی در ستون JSON ذخیره کنید و همچنان روی آن Query بزنید.


public class User
{
    public int Id { get; set; }
    public string UserName { get; set; } = default!;
    public UserProfile Profile { get; set; } = default!;
}

public class UserProfile
{
    public string FullName { get; set; } = default!;
    public string? Address { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity(entity =>
    {
        entity.OwnsOne(u => u.Profile);
    });
}
        

۲) عملیات Bulk بدون Track

در دیتابیس‌های بزرگ، عملیات‌هایی مانند حذف دسته‌ای رکوردها یا به‌روزرسانی میلیون‌ها ردیف اگر با Track کردن انجام شود، عملاً غیرممکن یا بسیار سنگین خواهد بود. EF Core 8 با معرفی ExecuteDelete و ExecuteUpdate این عملیات را مستقیماً در دیتابیس انجام می‌دهد، بدون نیاز به لودکردن Entityها در حافظه.


// حذف گروهی
await context.Orders
    .Where(o => o.IsExpired)
    .ExecuteDeleteAsync();

// بروزرسانی گروهی
await context.Orders
    .Where(o => o.Status == OrderStatus.Pending)
    .ExecuteUpdateAsync(setters => setters
        .SetProperty(o => o.Status, OrderStatus.Expired)
        .SetProperty(o => o.UpdatedAt, DateTime.UtcNow));
        

۳) Split Query برای Navigationهای سنگین

در EF Core وقتی چندین Navigation سنگین را Include می‌کنید، ممکن است یک Query غول‌آسا با ده‌ها Join ایجاد شود که هم حافظه می‌خورد و هم زمان پردازش را بالا می‌برد. SplitQuery این مشکل را با تقسیم Query به چند درخواست کوچک‌تر حل می‌کند.


var orders = await context.Orders
    .Include(o => o.Items)
    .Include(o => o.Customer)
    .AsSplitQuery()
    .ToListAsync();
        

امکانات EF Core 9

EF Core 9 تحول مهمی در بخش Query Pipeline داشت. بسیاری از مشکلات عملکردی LINQهای پیچیده در این نسخه بهبود یافتند. همچنین StateManager سبک‌تر شد؛ یعنی عملیات Tracking کمتر حافظه مصرف می‌کند و بهتر مدیریت می‌شود.

۱) Query Pipeline سریع‌تر

نسخه 9 موتور تحلیل Query را بازطراحی کرد. در نتیجه Queryهایی که شامل چندین شرط، Join، GroupBy یا Projection هستند، توجه‌پذیر و سریع‌تر اجرا می‌شوند. در دیتابیس‌های بزرگ این تفاوت کاملاً قابل لمس است.

۲) استفاده از AsNoTracking در گزارش‌گیری

در Queryهایی که فقط برای نمایش داده‌ها هستند، Track کردن غیرضروری است. AsNoTracking سرعت بازگشت داده‌ها را به شکل چشمگیری افزایش می‌دهد و مصرف حافظه را پایین می‌آورد.


var products = await context.Products
    .AsNoTracking()
    .Where(p => p.IsActive)
    .OrderBy(p => p.Name)
    .ToListAsync();
        

۳) DbContext Pooling بهبود یافته

DbContext یک شیء سنگین است و ساخت مداوم آن هزینه CPU می‌گیرد. Pooling باعث می‌شود EF Core یک مجموعه از DbContextهای آماده نگه دارد و در درخواست‌های جدید از همان‌ها استفاده کند.


builder.Services.AddDbContextPool(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
        

امکانات EF Core 10

EF Core 10 یک نسخه کاملاً Enterprise است. تمرکز اصلی این نسخه روی پردازش‌های سنگین، مدل‌سازی پیچیده، جستجوی داده‌های نیمه‌ساخت‌یافته، عملیات‌های موازی و مدیریت حجم بالا در دیتابیس‌های نسل جدید است. پشتیبانی از Vector Search یکی از تحولاتی است که امکان ساخت سیستم‌های هوشمند را فراهم می‌کند.

۱) نگاشت JSON به Complex Type

EF Core 10 اجازه می‌دهد ComplexTypeها مستقیم روی ستون JSON نگاشت شوند، بدون نیاز به اعمال تنظیمات پیچیده. این قابلیت باعث می‌شود مدل‌سازی در دیتابیس‌های مدرن بسیار ساده‌تر و سریع‌تر شود.


public class Customer
{
    public int Id { get; set; }
    public Address ShippingAddress { get; set; } = default!;
}

public class Address
{
    public string City { get; set; } = default!;
    public string Street { get; set; } = default!;
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity(b =>
    {
        b.ComplexProperty(c => c.ShippingAddress, cp => cp.ToJson());
    });
}
        

Query روی JSON

Queryگیری از JSON همانند Queryهای معمولی EF Core است—دیگر نیاز به روش‌های دستی یا Raw SQL نیست.


var list = await context.Customers
    .Where(c => c.ShippingAddress.City == "Tehran")
    .ToListAsync();
        

۲) جستجوی برداری (Vector Search)

جستجوی برداری برای ساخت سیستم‌های هوشمند مانند تشخیص شباهت متون، جستجو بر اساس مفهوم، توصیه‌گرها و رده‌بندی محتوا استفاده می‌شود. EF Core 10 این ویژگی را به‌صورت First-Class پشتیبانی می‌کند.


[Column(TypeName = "vector(1536)")]
public SqlVector Embedding { get; set; } = default!;

var topBlogs = await context.Blogs
    .OrderBy(b =>
        EF.Functions.VectorDistance(
            "cosine", b.Embedding, queryVector))
    .Take(5)
    .ToListAsync();
        

۳) Bulk Update روی JSON

EF Core 10 اجازه می‌دهد روی فیلدهای داخل JSON نیز عملیات‌های Bulk انجام دهید—امکانی که قبلاً وجود نداشت.


await context.Customers
    .Where(c => c.ShippingAddress.City == "Tehran")
    .ExecuteUpdateAsync(setters => setters
        .SetProperty(c => c.ShippingAddress.PostalCode, "000000"));
        

روش‌های عملی بهینه‌سازی EF Core

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

۱) مدیریت Tracking

Tracking برای عملیات‌های Update لازم است، اما برای نمایش اطلاعات سرعت را کاهش می‌دهد. همیشه در Queryهای Read-Only از AsNoTracking استفاده کنید.


// Query فقط برای Read
var users = await context.Users
    .AsNoTracking()
    .Where(u => u.IsActive)
    .ToListAsync();
        

Bulk Insert با AutoDetectChanges خاموش

AutoDetectChanges هنگام Insert رکوردهای زیاد به شدت مصرف CPU را بالا می‌برد. خاموش‌کردن آن هنگام عملیات دسته‌ای بهترین روش است.


context.ChangeTracker.AutoDetectChangesEnabled = false;

foreach (var item in bulkItems)
{
    context.Items.Add(item);
}

await context.SaveChangesAsync();

context.ChangeTracker.AutoDetectChangesEnabled = true;
        

۲) Projection برای کاهش بار حافظه

همیشه فقط داده مورد نیاز را Select کنید. لودکردن کل Entity مصرف حافظه را چند برابر می‌کند.


var list = await context.Orders
    .Where(o => o.Status == OrderStatus.Paid)
    .Select(o => new OrderSummaryDto(
        o.Id,
        o.Customer.Name,
        o.CreatedAt,
        o.Items.Sum(i => i.Price * i.Quantity)))
    .ToListAsync();
        

۳) SaveChanges کمتر → کارایی بیشتر

هر بار که SaveChanges اجرا می‌شود، EF Core همه Entityهای Track شده را بررسی می‌کند. این کار در دیتاست‌های بزرگ بسیار سنگین است. همیشه SaveChanges را تجمیع کنید.


// روش نادرست
foreach (var item in items)
{
    item.IsProcessed = true;
    await context.SaveChangesAsync();
}

// روش صحیح
foreach (var item in items)
{
    item.IsProcessed = true;
}
await context.SaveChangesAsync();
        

۴) ترکیب EF Core + Dapper

EF Core در عملیات‌های پیچیده و گزارش‌گیری‌های سنگین همیشه سریع‌ترین گزینه نیست. بهترین معماری در پروژه‌های بزرگ این است:

  • EF Core برای عملیات Write و مدل‌سازی
  • Dapper برای Queryهای بسیار سنگین و Real-Time

using var connection = new SqlConnection(connectionString);

var list = await connection.QueryAsync(@"
    SELECT o.Id, o.CreatedAt, c.Name AS CustomerName,
           SUM(oi.Price * oi.Quantity) AS TotalAmount
    FROM Orders o
    JOIN OrderItems oi ON o.Id = oi.OrderId
    JOIN Customers c ON o.CustomerId = c.Id
    WHERE CAST(o.CreatedAt AS date) = @Date
    GROUP BY o.Id, o.CreatedAt, c.Name
", new { Date = date.Date });
        

جمع‌بندی

EF Core با پیشرفت‌های نسخه‌های 8، 9 و 10 به مرحله‌ای رسیده که می‌تواند دیتابیس‌های بزرگ—even در سطح سازمانی—را با سرعت و پایداری بالا مدیریت کند. با استفاده از تکنیک‌هایی مثل NoTracking، Projection، Bulk Operations، JSON Mapping، Vector Search و مدیریت درست SaveChanges می‌توان لایه داده را چند برابر سریع‌تر کرد.

اصل مهم این است: ابزار به‌تنهایی کافی نیست؛ نحوه‌ی استفاده از آن تعیین‌کننده است. با رعایت نکات این محتوا می‌توانید EF Core را در سخت‌ترین شرایط و دیتابیس‌های حجیم نیز به بهترین عملکرد برسانید.