چکیده: فیلتر کردن پرسوجوها با مجموعهای از شناسهها با متد Contains یکی از الگوهای متداول در برنامههای مبتنی بر Entity Framework Core است. در ارتباط با SQL Server، همواره محدودیت تاریخی ۲۱۰۰ پارامتر مطرح بوده است. برخلاف تصور عمومی، EF Core 10 با عبور از این مرز خطایی صادر نمیکند؛ بلکه استراتژی ترجمه پرسوجو را بهصورت خودکار تغییر میدهد. این تغییر رفتار اگرچه از بروز استثنا جلوگیری میکند، اما در بازه مقادیر نزدیک به مرز مجاز پدیدهای موسوم به افت ناگهانی کارایی (Performance Cliff) ایجاد میکند؛ بهگونهای که یک پرسوجو با ۲۰۹۸ شناسه زمان اجرایی معادل ۳۴.۶ میلیثانیه ثبت میکند، درحالیکه با اضافه شدن تنها یک شناسه (۲۰۹۹ شناسه)، زمان اجرا به ۴.۰ میلیثانیه کاهش مییابد (تقریباً ۸ برابر سریعتر). این مقاله با تکیه بر بنچمارکهای استاندارد BenchmarkDotNet روی جدولی با ۱ میلیون رکورد، سازوکار داخلی ترجمه در EF Core 10، دستهبندی پارامترها (Parameter Bucketing)، محدودیت واقعی ۲۰۹۸ پارامتر در هماهنگی با sp_executesql، اثرات کش نقشه اجرای پرسوجو (Plan Cache)، و راهکارهای کارآمد برای کلیدهای ترکیبی (Composite Keys) را منحصراً بر پایه قابلیتهای توکار فریمورک و رویکردهای Native داتنت بررسی میکند.
۱. مقدمه
توسعهدهندگان بستر داتنت سالهاست با خطای نامآشنای زیر در کار با پایگاهداده SQL Server مواجه میشوند:
The incoming request has too many parameters. The server supports a maximum of 2100 parameters.
این محدودیت، تنظیماتی در سطح EF Core نیست؛ بلکه سقفی تعریفشده در معماری SQL Server برای رویههای ذخیرهشده (Stored Procedures) است. از آنجا که درایور Microsoft.Data.SqlClient پرسوجوهای پارامتریشده را بهکمک sp_executesql به سرور ارسال میکند، این سقف عیناً به پرسوجوهای ارسالی اعمال میشود.
در نگارشهای پیشین نظیر EF Core 8 و EF Core 9، فریمورک مجموعههای ورودی را بهصورت پیشفرض در قالب یک رشته آرایه JSON به همراه تابع OPENJSON به دیتابیس میفرستاد. این شیوه تنها از یک پارامتر بهره میبرد و خطر برخورد به سقف ۲۱۰۰ پارامتر را در متد Contains به صفر میرساند، اما چالشهایی نظیر عدم تخمین دقیق کاردینالیتی (Cardinality Estimation) برای مجموعههای کوچک ایجاد میکرد.
تیم توسعه EF Core در نگارش ۱۰، استراتژی پیشفرض ترجمه را به چندین پارامتر اسکالر به ازای هر مقدار (Scalar Parameters) تغییر داد. این تصمیم اگرچه به تولید برنامههای اجرایی پایدارتر و تخمین بهتر کمک میکند، اما پیامدهای پنهان و شایان توجهی در ابعاد بزرگ داده به همراه دارد.
۲. رفتار ترجمه در EF Core 10 و دستهبندی پارامترها
در یک پرسوجوی معمولی:
var ids = new List<int> { 1, 2, 3 };
var matched = await context.Products
.AsNoTracking()
.Where(p => ids.Contains(p.Id))
.ToListAsync();در EF Core 10، عبارت بالا به یک گزاره IN سنتی همراه با پارامترهای مجزا ترجمه میشود:
SELECT [p].[Id], [p].[Name], ...
FROM [Products] AS [p]
WHERE [p].[Id] IN (@ids1, @ids2, @ids3)
نکته نامگذاری: پیشوند سنتی @__ids_0 در EF Core 10 حذف شده و نامها به فرمت تمیزتر @ids1 تغییر یافتهاند. این تغییر موجب بیاعتبار شدن نقشههای اجرایی کششده قبلی (Plan Cache Invalidation) در زمان ارتقای نسخه و بار کاری موقت کامپایل در سرور پایگاهداده خواهد شد.
مکانیسم لایهبندی پارامترها (Parameter Bucketing)
برای جلوگیری از آلودگی کش نقشه اجرا (Plan Cache Pollution) در اثر طولهای متفاوت لیستها، EF Core 10 از تکنیک Padding (تکمیل ظرفیت تا سقف یک باکت مشخص) استفاده میکند. به عنوان مثال، اگر ۸ شناسه ارسال کنید، EF Core تعداد ۱۰ پارامتر تولید میکند و پارامترهای ۹ و ۱۰ را با مقدار پارامتر ۸ تکرار مینماید تا تغییری در نتیجه منطقی شرط ایجاد نشود.
قاعده این دستهبندی در متد داخلی CalculateParameterBucketSize در سورسکد درایور SQL Server پیادهسازی شده است:
protected override int CalculateParameterBucketSize(int count, RelationalTypeMapping elementTypeMapping)
{
if (count <= 5) return 1;
if (count <= 150) return 10;
if (count <= 750) return 50;
if (count <= 2000) return 100;
if (count <= 2070) return 10;
if (count <= MaxParameterCount) return 1; // عدم Padding بین 2070 تا سقف نهایی
return 200;
}
جدول زیر اندازه باکتها و رفتار واقعی سیستم را نشان میدهد:
| دامنه طول لیست | اندازه باکت (گام افزایش) | نمونه طول ورودی | تعداد پارامتر ارسالی به SQL Server |
| ۱ تا ۵ | دقیق (بدون تکمیل) | ۳ | ۳ |
| ۶ تا ۱۵۰ | مضرب بعدی ۱۰ | ۸ | ۱۰ |
| ۱۵۱ تا ۷۵۰ | مضرب بعدی ۵۰ | ۱۵۱ | ۲۰۰ |
| ۷۵۱ تا ۲۰۰۰ | مضرب بعدی ۱۰۰ | ۷۵۱ | ۸۰۰ |
| ۲۰۰۱ تا ۲۰۷۰ | مضرب بعدی ۱۰ | ۲۰۰۱ | ۲۰۱۰ |
| ۲۰۷۱ تا ۲۰۹۸ | دقیق (بدون تکمیل) | ۲۰۹۴ | ۲۰۹۴ |
این رفتار نشان میدهد که به دلیل Padding، تعداد پارامترهای ارسالی همواره بزرگتر یا مساوی طول لیست شماست و مرز بحرانی زودتر از آنچه به نظر میرسد فرا میرسد.
۳. سقف واقعی: ۲۰۹۸ پارامتر و رفتار پس از عبور از آن
سقف مستندشده SQL Server عدد ۲۱۰۰ است، اما در واقعیت سقف کاربردی در پرسوجوهای پارامتری برابر با ۲۰۹۸ است. دو پارامتر از این بودجه صرف پارامترهای کنترلی دستور sp_executesql در لایه SqlClient میشود (این نکته در انتشار EF Core 10.0.2 اصلاح و نهایی شد).
عبور نامحسوس به استراتژی JSON
هنگامی که طول لیست شما از ۲۰۹۸ پارامتر فراتر رود، متد داخلی VisitIn در موتور تحلیل عبارتهای EF Core نوع ترجمه را تغییر میدهد:
| تعداد شناسهها | تعداد پارامتر ارسالی | استراتژی تولید کد SQL |
| ۲۰۹۶ | ۲۰۹۶ | IN (@ids1, ... @ids2096) |
| ۲۰۹۸ | ۲۰۹۸ | IN (@ids1, ... @ids2098) |
| ۲۰۹۹ | ۱ | IN (SELECT [Value] FROM OPENJSON(@ids) ...) |
| ۵۰۰۰ | ۱ | IN (SELECT [Value] FROM OPENJSON(@ids) ...) |
| ۱۰۰,۰۰۰ | ۱ | IN (SELECT [Value] FROM OPENJSON(@ids) ...) |
کد ترجمهشده در مقادیر ۲۰۹۹ و بالاتر:
SELECT COUNT(*)
FROM [Products] AS [p]
WHERE [p].[Id] IN (
SELECT [__openjson0].[Value]
FROM OPENJSON(@ids) WITH ([Value] int '$') AS [__openjson0]
)
مقایسه کارایی در مرز تغییر استراتژی
تغییر استراتژی بدون استثنا انجام میگیرد، اما اثر آن بر عملکرد سیستم چشمگیر است. نتایج ارزیابی بر روی یک جدول با ۱ میلیون رکورد دارای ایندکس خوشهای:
- ۲۰۹۸ شناسه (استراتژی پیشفرض پارامتری): زمان اجرا ۳۴.۶ میلیثانیه | حافظه تخصیصدادهشده: ۳,۳۹۳ کیلوبایت
- ۲۰۹۹ شناسه (استراتژی OPENJSON): زمان اجرا ۴.۰ میلیثانیه | حافظه تخصیصدادهشده: ۷۴۳ کیلوبایت
این پدیده بیانگر آن است که محدوده ۱۰۰۰ تا ۲۰۹۸ پارامتر در استراتژی پیشفرض، پرهزینهترین بازه پردازشی در EF Core 10 است؛ پدیدهای که در کد #C اثری از آن دیده نمیشود.
۴. مقایسه حالتهای ترجمه توکار و اثر آن بر کش نقشه اجرا (Plan Cache)
فریمورک EF Core سه حالت اصلی ترجمه برای مجموعهها ارائه میدهد:
۱. تنظیم سراسری (Global Configuration)
از طریق متد UseParameterizedCollectionMode میتوان استراتژی پیشفرض را در سطح DbContext تغییر داد:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString, sqlOptions =>
{
// تغییر رفتار پیشفرض به استراتژی تکپارامتری JSON
sqlOptions.UseParameterizedCollectionMode(ParameterTranslationMode.Parameter);
});
}نکته مهاجرت: متدهای قدیمیتر مانند TranslateParameterizedCollectionsToParameters در EF Core 10 منسوخ (Obsolete) شده و با متد فوق جایگزین شدهاند.
۲. تنظیم در سطح پرسوجو (Per-Query Overrides)
میتوان بدون تغییر پیکربندی سراسری، استراتژی فیلتر را صراحتاً تعیین کرد:
// ۱. پیشفرض نگارش ۱۰: ارسال پارامترهای اسکالر به تعداد عناصر (با اعمال Padding)
.Where(p => EF.MultipleParameters(ids).Contains(p.Id))
// ۲. رفتار نگارشهای ۸ و ۹: یک پارامتر آرایه JSON همراه با OPENJSON
.Where(p => EF.Parameter(ids).Contains(p.Id))
// ۳. جایگذاری مقادیر به صورت صریح در متن SQL (Inlined Constants)
.Where(p => EF.Constant(ids).Contains(p.Id))
نکته امنیتی و لاگینگ: در EF Core 10 مقادیر درونخطی (EF.Constant) برای اهداف امنیتی بهصورت پیشفرض در خروجی لاگها سانسور میشوند (IN (?, ?, ?))؛ مگر آنکه گزینه EnableSensitiveDataLogging() در زمان پیکربندی فعال شده باشد.
سنجش عملی آلودگی Plan Cache
در آزمایشی با اجرای ۲۰ پرسوجو در اندازههای مختلف لیست پس از پاکسازی کامل کش SQL Server (DBCC FREEPROCCACHE)، نتایج به شرح زیر ثبت شد:
| استراتژی | تعداد نقشههای متمایز ذخیرهشده در Cache | تحلیل رفتار |
EF.Parameter (تک پارامتر JSON) | ۲ | بالاترین انطباق؛ بدون تأثیرپذیری از طول یا محتوای لیست |
پیشفرض (EF.MultipleParameters) | ۴ | مکانیزم Padding موفق شده است ۲۰ طول مختلف را در ۴ نقشه ادغام کند |
EF.Constant (ثوابت درونخطی) | ۲۱ | به ازای هر ترکیب منحصربهفرد، یک کامپایل و نقشه جدید ثبت میشود |
استفاده از EF.Constant صرفاً برای مجموعههای کوتاه، ثابت و پرتکرار (مانند فیلتر کردن وضعیتهای Enum یا نقشهای ثابت سیستم) توجیهپذیر است؛ چرا که به موتور بهینهساز امکان بهرهگیری از آمار واقعی مقادیر (Histogram) را میدهد، بدون اینکه کش نقشه اجرا دچار تورم شود.
۵. ارزیابی جامع کارایی (Benchmarks)
آزمونهای زیر با استفاده از کتابخانه BenchmarkDotNet 0.15.8 روی سیستمعامل تحت .NET 10.0.11 و پایگاهداده SQL Server 2025 با جدولی شامل یک میلیون سطر و شناسه با ایندکس خوشهای (Clustered Index) به ازای سناریوهای مختلف اجرا شده است. تمامی پرسوجوها با AsNoTracking() اجرا شدهاند تا سربار Change Tracker حذف شود:
| روش اجرایی | ۱,۰۰۰ آیتم | ۲,۰۹۸ آیتم | ۲,۰۹۹ آیتم | ۵,۰۰۰ آیتم | ۱۰,۰۰۰ آیتم | ۱۰۰,۰۰۰ آیتم |
| Contains (پیشفرض) | 12.8 ms | 34.6 ms | 4.0 ms | 6.2 ms | 14.8 ms | 273 ms |
| EF.Parameter (تک پارامتر) | 2.6 ms | 4.3 ms | 4.6 ms | 7.6 ms | 13.5 ms | 243 ms |
| EF.Constant (ثوابت مستقیم) | 3.6 ms | 5.9 ms | 5.1 ms | 102 ms | 118 ms | شکست (خطای پردازشگر SQL) |
| تکهتکهکردن (Chunking 2000) | 16.0 ms | 34.3 ms | 35.5 ms | 72 ms | 145 ms | 1,347 ms |
| جدول موقت دستی (Temp Table) | 5.8 ms | 9.9 ms | 7.8 ms | 93 ms | 101 ms | 187 ms |
تحلیل دادهها
- افت در مجاورت سقف: در مرز ۲۰۹۸ شناسه، متد پیشفرض ۳۴.۶ میلیثانیه زمان برده در حالی که استفاده از
EF.Parameter آن را به ۴.۳ میلیثانیه کاهش داده است (بهبودی نزدیک به ۸ برابر تنها با یک تغییر کلمه در LINQ). - ناکارآمدی الگوی Chunking: قطعهقطعه کردن لیست به دستههای ۲۰۰۰تایی (که پاسخی سنتی در پروژهها بود) بدترین کارایی را ثبت کرده است. در ۱۰۰,۰۰۰ رکورد، این الگو ۵۰ رفتوبرگشت به دیتابیس (Round-trip) تحمیل کرده و بیش از ۱.۳ ثانیه زمان برده است.
- ریزش شدید
EF.Constant: تا مرز ۲۰۰۰ آیتم عملکرد مناسبی دارد، اما در ۵۰۰۰ آیتم با افت چشمگیر مواجه شده و در مقیاس ۱۰۰,۰۰۰ آیتم، موتور بهینهسازی SQL Server با خطای کمبود منابع داخلی در تولید Query Plan متوقف میشود (۲۰.۵ ثانیه نگهداشتن اتصال پیش از لغو). - برتری جدول موقت در مقیاس بسیار بزرگ: تا ۱۰,۰۰۰ آیتم، راهکارهای توکار داتنت به مراتب سریعتر از تولید جدول موقت هستند. تنها در ۱۰۰,۰۰۰ آیتم است که ساخت جدول موقت دستی با ۱۸۷ میلیثانیه گوی سبقت را میرباید.
۶. عبور از فیلترهای اسکالر: چالش کلیدهای ترکیبی (Composite Keys)
تمامی راهکارهای مبتنی بر متد Contains محدود به کلیدهای تکستونی (Scalar) هستند. در سناریوهایی نظیر کلیدهای ترکیبی (TenantId, ProductId)، هیچیک از ساختارهای زیر در EF Core قابل ترجمه به SQL نیستند:
// تمام این الگوها خطای ترجمه (Translation Failure) صادر میکنند:
.Where(i => keys.Contains(new { i.TenantId, i.ProductId }))
.Where(i => keys.Any(k => k.TenantId == i.TenantId && k.ProductId == i.ProductId))
۱. تله استفاده از ادغام رشتهای (String Concatenation Trap)
برخی توسعهدهندگان ستونها را به یک رشته واحد تبدیل میکنند تا از Contains استفاده کنند:
var keys = pairs.Select(p => $"{p.TenantId}-{p.ProductId}").ToList();
var matched = await context.Inventory
.Where(i => keys.Contains(i.TenantId + "-" + i.ProductId))
.ToListAsync();- پیامد: ستونهای کلیدی داخل تابع تبدیل (
CAST) قرار میگیرند. این امر موجب از بین رفتن خاصیت SARGability و ناکارآمد شدن ایندکس میشود. - دیتابیس برای تمامی رکوردهای جدول، تبدیل و الحاق رشتهای را ارزیابی میکند. در آزمون ۱۰۰۰ جفتکلید، این روش به اتمام مهلت فرمان (Command Timeout - ۳۰ ثانیه) برخورد کرد.
۲. زنجیره شرطیORو خطر سرریز پشته (Stack Overflow)
الگوی معمول دیگر، ترکیب شروط با عملگر OR به کمک حلقه یا افزونههای ساخت Expression Tree است:
WHERE ([TenantId] = @p0 AND [ProductId] = @p1)
OR ([TenantId] = @p2 AND [ProductId] = @p3) ...
- فروپاشی پردازه در ۴۹۰ شرط: ایجاد زنجیره با الحاق مستقیم در حلقه، یک درخت عبارت با عمق زیاد (Left-Deep Expression Tree) میسازد. در زمان تحلیل بازگشتی این درخت در لایه داخلی EF Core، پردازه داتنت در ۴۹۰ شرط با خطای بحرانی
0xC00000FD (STATUS_STACK_OVERFLOW) بسته میشود؛ خطایی که قابل رهگیری با try-catch نیست و به خاموشی ناگهانی پردازه منجر میگردد. - درخت متعادل (Balanced Tree): در صورت بازنویسی الگوریتم بهصورت درخت دودویی متعادل، مشکل سرریز پشته برطرف میشود، اما با رسیدن به ۱۰۴۹ جفتکلید (معادل ۲۰۹۸ پارامتر اسکالر)، خطای واقعی
Too many parameters رخ میدهد؛ زیرا عبارت OR ساختار آرایهای ندارد و از مکانیزم نجاتبخش OPENJSON بهرهمند نمیشود.
// الگوریتم ساخت درخت متعادل برای جلوگیری از Stack Overflow
static Expression BuildBalancedOrTree(IReadOnlyList<Expression> expressions)
{
var current = expressions;
while (current.Count > 1)
{
var next = new List<Expression>((current.Count + 1) / 2);
for (int i = 0; i < current.Count; i += 2)
{
next.Add(i + 1 < current.Count
? Expression.OrElse(current[i], current[i + 1])
: current[i]);
}
current = next;
}
return current[0];
}
۷. راهکارهای پیشنهادی بدون اتکا به پکیجهای ثالث
برای مدیریت حجمهای بزرگ داده و بهویژه کلیدهای ترکیبی، بدون نیاز به لایسنس یا پکیجهای تجاری، دو رویکرد توکار و پایدار وجود دارد:
راهکار اول: جدول موقت دستی به همراهSqlBulkCopy
برای مقیاسهای بسیار بزرگ (۱۰۰,۰۰۰ شناسه یا کلیدهای ترکیبی حجیم)، ایجاد جدول موقت روی کانکشن جاریِ DbContext بالاترین کارایی را فراهم میسازد:
public async Task<List<Product>> FilterByLargeKeysAsync(
AppDbContext context,
DataTable keysDataTable,
CancellationToken ct = default)
{
var connection = (SqlConnection)context.Database.GetDbConnection();
await context.Database.OpenConnectionAsync(ct);
try
{
// ۱. ساخت جدول موقت
await using (var cmd = connection.CreateCommand())
{
cmd.CommandText = @"
CREATE TABLE #TempKeys (
TenantId INT NOT NULL,
ProductId INT NOT NULL,
PRIMARY KEY (TenantId, ProductId)
);";
await cmd.ExecuteNonQueryAsync(ct);
}
// ۲. درج فوق سریع مقادیر با SqlBulkCopy
using (var bulkCopy = new SqlBulkCopy(connection))
{
bulkCopy.DestinationTableName = "#TempKeys";
bulkCopy.ColumnMappings.Add("TenantId", "TenantId");
bulkCopy.ColumnMappings.Add("ProductId", "ProductId");
await bulkCopy.WriteToServerAsync(keysDataTable, ct);
}
// ۳. اجرای پرسوجو با اتصال (Join) روی جدول موقت در بستر EF Core
var result = await context.Inventory
.FromSqlRaw(@"
SELECT i.*
FROM [Inventory] AS i
INNER JOIN #TempKeys AS k
ON i.[TenantId] = k.[TenantId]
AND i.[ProductId] = k.[ProductId]")
.AsNoTracking()
.ToListAsync(ct);
return result;
}
finally
{
await context.Database.CloseConnectionAsync();
}
}این روش با حفظ مدیریت تراکنش و استفاده از FromSqlRaw، نتیجه را مستقیماً به موجودیتهای EF Core نگاشت میکند، بدون آنکه پارامتری به SQL Server ارسال شود.
راهکار دوم: استفاده از User-Defined Table Type (UDTT) و پارامتر ساختاریافته
برای حجم دادههای متوسط تا بزرگ با کلیدهای اسکالر یا ترکیبی، تعریف ساختار جدولی درون دیتابیس یک رویکرد استاندارد سازمانی است:
-- یکبار در دیتابیس ایجاد میشود
CREATE TYPE [dbo].[IntIdList] AS TABLE (
[Id] INT PRIMARY KEY
);سپس در برنامه:
var table = new DataTable();
table.Columns.Add("Id", typeof(int));
foreach (var id in ids) table.Rows.Add(id);
var parameter = new SqlParameter("@IdList", SqlDbType.Structured)
{
TypeName = "dbo.IntIdList",
Value = table
};
var matched = await context.Products
.FromSqlRaw("SELECT p.* FROM [Products] p INNER JOIN @IdList t ON p.[Id] = t.[Id]", parameter)
.AsNoTracking()
.ToListAsync();این روش علاوه بر ارسال تنها یک پارامتر به پایگاهداده، کامپایل بهینه، تایپ امن و قابلیت بازاستفاده در رویههای ذخیرهشده را به همراه دارد.
۸. پایش و مراقبت در محیط عملیاتی (Observability)
تغییر آرام و بدون استثنای استراتژی در EF Core 10 ایجاب میکند سیاستهای مانیتورینگ متفاوتی اتخاذ شود:
رهگیری تعداد واقعی پارامترها با Interceptor
تعداد آیتمهای لیست ارسالی نشاندهنده تعداد پارامترها نیست. برای آگاهی از پارامترهای واقعی، یک اینترسپتور ساده پیادهسازی کنید:
public sealed class ParameterCountDiagnosticsInterceptor : DbCommandInterceptor
{
private readonly ILogger<ParameterCountDiagnosticsInterceptor> _logger;
public ParameterCountDiagnosticsInterceptor(ILogger<ParameterCountDiagnosticsInterceptor> logger)
{
_logger = logger;
}
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
// ثبت هشدار در صورت ورود به بازه هزینهبر
if (command.Parameters.Count >= 1000 && command.Parameters.Count <= 2098)
{
_logger.LogWarning(
"Query executed in the expensive parameter band. Parameter Count: {Count}. Command: {Sql}",
command.Parameters.Count,
command.CommandText);
}
return base.ReaderExecuting(command, eventData, result);
}
}
۹. ماتریس تصمیمگیری (Decision Matrix)
| شرایط سناریو | استراتژی پیشنهادی | علت و منطق فنی |
| کمتر از ۱,۰۰۰ شناسه اسکالر | متد پیشفرض Contains | تفاوت کارایی روشها در این دامنه ناچیز است؛ سادگی کد اولویت دارد. |
| ۱,۰۰۰ تا ۲,۰۹۸ شناسه اسکالر | EF.Parameter(ids) | جلوگیری از افت کارایی پیشفرض؛ حدود ۸ برابر سریعتر در سقف بازه. |
| بیش از ۲,۰۹۸ شناسه اسکالر | متد پیشفرض Contains | فریمورک بهصورت خودکار به استراتژی OPENJSON سوئیچ میکند. |
| مجموعه کوتاه و پایدار (مانند Enum) | EF.Constant(ids) | ایجاد نقشه بهینه بر اساس مقادیر واقعی بدون آلوده ساختن Plan Cache. |
| فیلتر با کلیدهای ترکیبی | جدول موقت دستی با SqlBulkCopy یا FromSqlRaw | عدم پشتیبانی موتور ترجمه از تاپلها؛ عبور از ریسک Stack Overflow در عبارات OR. |
| بیش از ۱۰۰,۰۰۰ شناسه (تمام حالات) | جدول موقت دستی با SqlBulkCopy | بالاترین کارایی و کمترین مصرف حافظه در مقیاسهای کلان. |
۱۰. نتیجهگیری
رفتار ترجمه پرسوجوها در EF Core 10، دچار تحولی زیربنایی شده است. رفع خطای مرز ۲۱۰۰ پارامتر در متد Contains به بهای رفتارهای نامحسوس پردازشی در بازههای نزدیک به سقف تمام شده است. کلید تسلط بر عملکرد پایگاهداده در داتنت، شناخت نحوه تعامل لایههای انتزاعی با سرور پایگاهداده است:
- مرز عملیاتی مفید پارامترها با در نظر گرفتن
sp_executesql برابر با ۲۰۹۸ است نه ۲۱۰۰. - استراتژی پیشفرض برای مقادیر بین ۱,۰۰۰ تا ۲,۰۹۸ شناسه نامناسبترین بهرهوری را دارد؛ در این بازه استفاده صریح از
EF.Parameter توصیه میشود. - در مقیاسهای کلان یا ساختارهای پیچیده مانند کلیدهای ترکیبی، نباید به لایه ترجمه خودکار اتکا کرد؛ بلکه باید از الگوهای مبتنی بر مجموعههای جدولی موقت (مانند Table Types یا Temp Tables با Bulk Copy) استفاده نمود.