بهبود کارایی صفحهبندی اطلاعات در SQL Server با استفاده از Entity Framework Core
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۱۰/۰۲ ۱۰:۱۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
OnModelCreating در کلاس DbContext استفاده میشود. این کار به SQL Server کمک میکند تا کوئریها را سریعتر اجرا کند، به ویژه در صفحهبندی که مرتبسازی (Sorting) نقش کلیدی دارد.using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
public class User
{
[Key]
public int UserID { get; set; }
public DateTime CreationDate { get; set; }
public string UserName { get; set; } // برای مثالهای مرتبسازی اضافی
// سایر فیلدها...
}
public class UserPaginationRowNumber // برای روش پیشمحاسبهشده
{
[Key]
public int UserID { get; set; }
public int RowNumberByCreationDate { get; set; } // شماره ردیف بر اساس تاریخ ایجاد
// میتوانید ستونهای دیگری برای مرتبسازیهای مختلف اضافه کنید، مانند RowNumberByUserName
}
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<UserPaginationRowNumber> UserPaginationRowNumbers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ایندکس برای مرتبسازی بر اساس تاریخ ایجاد و شناسه (برای شکستن تساویها)
modelBuilder.Entity<User>()
.HasIndex(u => new { u.CreationDate, u.UserID })
.HasDatabaseName("IX_CreationDate");
// ایندکس منحصربهفرد برای جدول پیشمحاسبهشده
modelBuilder.Entity<UserPaginationRowNumber>()
.HasIndex(r => r.RowNumberByCreationDate)
.IsUnique()
.HasDatabaseName("IX_RowNumberByCreationDate");
}
}AsNoTracking() برای کوئریهای فقطخواندنی استفاده کنید تا عملکرد بهبود یابد، زیرا ردیابی تغییرات (Change Tracking) غیرضروری است.Skip و Take در LINQ است و مستقیماً به OFFSET/FETCH در SQL ترجمه میشود. اما همانطور که مقاله اصلی اشاره میکند، با افزایش شماره صفحه، عملکرد افت میکند زیرا تمام ردیفهای قبلی اسکن میشوند.var pageSize = 10;
var pageNumber = 30000; // مثلاً صفحه آخر
var query = context.Users
.OrderBy(u => u.CreationDate)
.ThenBy(u => u.UserID)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.AsNoTracking(); // بهینهسازی اضافی
var results = await query.ToListAsync(); // استفاده از async برای عملکرد بهترcontext.Users.Where(u => u.UserName.Contains("search")).OnModelCreating، EF Core از آن برای مرتبسازی استفاده میکند. کد همان روش قبل است، اما عملکرد برای صفحات پایین بهتر میشود. مقاله اصلی توضیح میدهد که بدون ایندکس، اسکن کامل جدول رخ میدهد؛ اما اینجا ایندکس تعریف شده، اسکن ایندکس (Index Scan) را فعال میکند.HasIndex برای هر ترکیب استفاده کنید.modelBuilder.Entity<User>()
.HasIndex(u => new { u.CreationDate, u.UserID })
.HasDatabaseName("IX_CreationDate");var pageSize = 10;
var skip = (pageNumber - 1) * pageSize;
var keyQuery = context.Users
.OrderBy(u => u.CreationDate)
.ThenBy(u => u.UserID)
.Select(u => u.UserID)
.Skip(skip)
.Take(pageSize)
.AsNoTracking();
var keys = await keyQuery.ToListAsync();
var results = await context.Users
.Where(u => keys.Contains(u.UserID))
.AsNoTracking()
.ToListAsync();Batch در EF Core برای جلوگیری از محدودیتهای SQL استفاده کنید.var lastCreationDate = DateTime.Parse("2022-07-28"); // از صفحه قبلی
var lastUserID = 299990; // از صفحه قبلی
var keyQuery = context.Users
.Where(u => u.CreationDate > lastCreationDate ||
(u.CreationDate == lastCreationDate && u.UserID > lastUserID))
.OrderBy(u => u.CreationDate)
.ThenBy(u => u.UserID)
.Select(u => u.UserID)
.Take(pageSize)
.AsNoTracking();
var keys = await keyQuery.ToListAsync();
var results = await context.Users
.Where(u => keys.Contains(u.UserID))
.AsNoTracking()
.ToListAsync();// پر کردن جدول (در یک متد جداگانه، مثلاً با Quartz.NET برای زمانبندی)
var users = await context.Users
.OrderBy(u => u.CreationDate)
.ThenBy(u => u.UserID)
.Select((u, index) => new UserPaginationRowNumber { UserID = u.UserID, RowNumberByCreationDate = index + 1 })
.ToListAsync();
context.UserPaginationRowNumbers.Truncate(); // یا DeleteAll
context.UserPaginationRowNumbers.AddRange(users);
await context.SaveChangesAsync();// ایندکس منحصربهفرد برای جدول پیشمحاسبهشده
modelBuilder.Entity<UserPaginationRowNumber>()
.HasIndex(r => r.RowNumberByCreationDate)
.IsUnique()
.HasDatabaseName("IX_RowNumberByCreationDate");var startRow = (pageNumber - 1) * pageSize + 1;
var endRow = startRow + pageSize - 1;
var keyQuery = context.UserPaginationRowNumbers
.Where(r => r.RowNumberByCreationDate >= startRow && r.RowNumberByCreationDate <= endRow)
.Select(r => r.UserID)
.AsNoTracking();
var keys = await keyQuery.ToListAsync();
var results = await context.Users
.Where(u => keys.Contains(u.UserID))
.AsNoTracking()
.ToListAsync();