ارتقا به NET 10. و EF Core 10: تغییرات رفتار زمان اجرا که در زمان کامپایل پنهان میمانند
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۵/۲۴ ۱۰:۲۰
آدرس: www.dntips.ir
چکیده: مهاجرت به نسخههای جدید داتنت معمولاً با بررسی خطاهای زمان کامپایل (Compile-time Errors) و APIهای منسوخشده آغاز میشود؛ با این حال، حساسترین چالشها مربوط به «تغییرات رفتاری زمان اجرا» (Runtime Behavioral Breaking Changes) هستند. در Entity Framework Core 10، تغییر بنیادینی در استراتژی ترجمه LINQ به SQL برای عملگر متداولContains()اعمال شده است. در این نسخه، ترجمه پیشفرض از آرایههای مبتنی بر JSON (نظیرOPENJSONدر SQL Server یاjson_eachدر SQLite) به پارامترهای اسکالر تفکیکشده (Multiple Scalar Parameters) تغییر یافته است. این تغییر اگرچه در خروجی دادهها تفاوتی ایجاد نمیکند، اما ساختار متن SQL، شیوهی نامگذاری پارامترها و رفتار حافظه موقت پلنهای اجرایی دیتابیس (Query Plan Cache) را دگرگون میسازد. این مقاله به تحلیل دقیق این تغییر، اثرات آن بر کارایی و روشهای کنترل یا بازگردانی رفتار قبلی میپردازد.
Contains() را در قالب یک آرایه متنی یا JSON فشرده به دیتابیس ارسال میکرد تا با یک پارامتر واحد، کوئریهای با طول پارامتر متغیر پردازش شوند. با انتشار EF Core 10، الگوی ترجمه به صورت پیشفرض بازنگری شده است. این تغییر بدون هیچگونه هشدار یا خطایی در زمان کامپایل اعمال میشود، اما میتواند در لایههای زیرین (نظیر رفتارهای کشینگ SQL Server، ابزارهای پایش عملکرد، تستهای Snapshot و رهگیرهای DbCommand) اثرات غیرمنتظرهای بر جای بگذارد.Contains()var idList = new List<int> { 1, 3 };
var blogs = await db.Blogs
.Where(b => idList.Contains(b.Id))
.ToListAsync();-- EF Core 9 (SQL Server)
SELECT [b].[Id], [b].[Name], [b].[Address_City], [b].[Address_Street]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (
SELECT [value]
FROM OPENJSON(@__idList_0)
)
-- Parameters: @__idList_0='[1,3]'-- EF Core 9 (SQLite)
SELECT "b"."Id", "b"."Name", "b"."Tags", "b"."Address_City", "b"."Address_Street"
FROM "Blogs" AS "b"
WHERE "b"."Id" IN (
SELECT "i"."value"
FROM json_each(@__idList_0) AS "i"
)
-- Parameters: @__idList_0='[1,3]'-- EF Core 10 (SQL Server) SELECT [b].[Id], [b].[Name], [b].[Address_City], [b].[Address_Street] FROM [Blogs] AS [b] WHERE [b].[Id] IN (@idList1, @idList2) -- Parameters: @idList1=1, @idList2=3
-- EF Core 10 (SQLite) SELECT "b"."Id", "b"."Name", "b"."Tags", "b"."Address_City", "b"."Address_Street" FROM "Blogs" AS "b" WHERE "b"."Id" IN (@idList1, @idList2) -- Parameters: @idList1='1', @idList2='3'
__ همراه با اندیس متغیر اعمال میشد (مانند @__idList_0 یا @__city_0).@idList1, @idList2 یا @city).نکته حائز اهمیت: اگرچه این تغییر پایگاهداده را تحت تأثیر منفی قرار نمیدهد، اما دو دسته از پیادهسازیها را بلافاصله با خطا مواجه میکند:
DbCommandInterceptor با عبارات باقاعده (Regex) یا جستجوی متنی روی DbCommand.CommandText به دنبال پارامترهایی با پیشوند __ میگشتند.| ویژگی | رویکرد JSON (نسخه ۹) | رویکرد پارامترهای اسکالر (نسخه ۱۰) |
| تعداد پلنهای کششده | بسیار پایین (یک پلن برای تمام اندازههای آرایه) | بر اساس بازههای پارامترها کش ایجاد میشود |
| هزینه پردازش در دیتابیس | هزینه اضافی جهت Parse و خواندن ساختار JSON | پردازش مستقیم و سریعتر بدون Overhead جیسان |
| خطر Plan Cache Bloat | نزدیک به صفر | در صورت نبود مکانیزم باکتبندی (Parameter Bucketing)، خطر انباشت پلنها وجود دارد |
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
connectionString,
sqlServerOptions => sqlServerOptions.UseParameterizedCollectionMode(
ParameterTranslationMode.Parameter
)
);
}EF.Parameter بهره بگیرید:var blogs = await db.Blogs
.Where(b => EF.Parameter(idList).Contains(b.Id))
.ToListAsync();Contains()، بهینهسازی موثری برای سناریوهای معمول با تعداد آیتمهای محدود است، اما نیازمند ارزیابی مجدد رفتار دیتابیس در ترافیکهای بالا، بازبینی تستهای Snapshot و دقت در اینترسپتورهای سفارشی است. پیشنهاد میشود پیش از استقرار نهایی در محیط عملیاتی، تستهای بارگذاری با تمرکز بر مصرف حافظه موقت دیتابیس انجام شود.