بررسی رفتار نگاشت زمانی در SQLite و ارتقاء به داتنت ۱۰: تغییرات ساختاری در DateTimeOffset و راهکارهای مهاجرت داده
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۵/۲۵ ۱۲:۰۵
آدرس: www.dntips.ir
چکیده: در فرآیند انتشار فریمورکهای مدرن داتنت، تغییرات ساختاری ناسازگار (Breaking Changes) معمولاً با خطاهای زمان کامپایل (Compile-time) یا تغییر در ساختار SQLهای تولیدی توسط ORM مشخص میشوند. با این حال، در NET 10. و در کتابخانهMicrosoft.Data.Sqlite 10(و لایه دیتابیسEF Core 10)، یک تغییر با درجه اهمیت بالا (High Impact) اعمال شده است که رفتاری کاملاً خاموش (Silent) دارد. این تغییر نحوه تفسیر و پارس رشتههای زمانی فاقد آفست (Offset-less Timestamps) را بازتعریف میکند؛ رکوردهایی که پیش از این در داتنت ۹ به افست محلی ماشین (TimeZoneInfo.Local) نگاشت میشدند، در داتنت ۱۰ به صورت پیشفرض و غیرمشروط به عنوان زمان جهانی هماهنگ (UTC / +00:00) تفسیر میشوند. این مقاله به بررسی دقیق فنی، بازتولید سناریوی تغییر، تبعات دادهای پنهان، تغییرات تکمیلی رویREALوDateTime.Kindو ارائه راهکارهای عملی جهت ممیزی و مهاجرت ایمن دادهها میپردازد.
Type Affinity)، فاقد نوع دادهای اختصاصی و بومی برای ذخیرهسازی نوعهای غنی زمانی مانند DateTimeOffset در داتنت است. به همین دلیل، پرووایدر Microsoft.Data.Sqlite و موتور EF Core دادههای زمانی را معمولاً در ستونهایی از نوع متنی (TEXT) با فرمت ISO-8601 یا نوع عددی ممیز شناور (REAL مبتنی بر رکوردهای جولیان - Julian Day Numbers) ذخیره و بازیابی میکنند.Z یا 03:30+) در پایگاه داده وجود داشت، پرووایدر کلاینت هنگام تبدیل آن به DateTimeOffset، متغیر آفست را بر اساس منطقه زمانی سرور پردازشکننده (TimeZoneInfo.Local) تنظیم میکرد.Microsoft.Data.Sqlite 10 را میتوان در سه محور اصلی دستهبندی کرد:GetDateTimeOffsetدر ستونهای متنی (TEXT)DateTimeOffset تعریف شده باشد، نحوه خواندن آن از جدول تفاوت بنیادین پیدا کرده است:01:00+ (مانند ساعت تابستانی بریتانیا/ایرلند IST)، خروجی نهایی به شکل 2026-08-14T20:00:00+01:00 حاصل میشد (معادل ساعت ۱۹:۰۰ زمان UTC).2026-08-14T20:00:00+00:00 پردازش میشود (دقیقاً ساعت ۲۰:۰۰ زمان UTC).چالش اصلی: این تغییر سبب جابهجایی زمانی دادههای تاریخی (Historical Data) میشود، بدون آنکه استثنایی (Exception) پرتاب گردد یا فرآیند اجرای برنامه متوقف شود.DateTimeOffsetدر ستونهای نوعREALREAL) پیکربندی شده باشد:REAL و ذخیرهسازی، داده را به صورت خودکار به معادل UTC تبدیل میکند.Kindدر خواندنDateTimeبا متدGetDateTimeGetDateTime برای واکشی فیلدی استفاده میشود که در پایگاه داده دارای آفست صریح است:DateTimeKind.Local علامتگذاری میشد.DateTimeKind.Utc برمیگرداند.date.Kind == DateTimeKind.Local وجود داشته باشد، این تغییر مسیر اجرای برنامه را تحت تأثیر قرار خواهد داد.public class Event
{
public int Id { get; set; }
public string Description { get; set; } = string.Empty;
public DateTimeOffset OccurredAt { get; set; }
}sqlite3 migrationchecklist.db "INSERT INTO \"Events\" (\"Description\",\"OccurredAt\") VALUES ('OffsetlessTest','2026-08-14 20:00:00');"GET /events/1 خروجیهای زیر حاصل میشوند:{
"id": 1,
"description": "OffsetlessTest",
"occurredAt": "2026-08-14T20:00:00+01:00"
}{
"id": 1,
"description": "OffsetlessTest",
"occurredAt": "2026-08-14T20:00:00+00:00"
}Compatibility Switch)Program.cs یا پیش از مقداردهی دیتابیس فعال میشود:AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);runtimeconfig.json یا .csproj) اضافه کرد:<ItemGroup> <RuntimeHostConfigurationOption Include="Microsoft.Data.Sqlite.Pre10TimeZoneHandling" Value="true" /> </ItemGroup>
هشدار: این کلید سازگاری صرفاً یک مسکّن موقت برای پیشبرد برنامهریزی مهاجرت است و نباید به عنوان راهحل دائمی سیستم تلقی شود.
Z یا الگوهای آفست (+ یا -) هستند شناسایی شوند. کوئری نمونه زیر برای یافتن رشتههای فاقد آفست کاربرد دارد:SELECT Id, Description, OccurredAt FROM "Events" WHERE OccurredAt NOT LIKE '%Z' AND OccurredAt NOT LIKE '%+%' AND OccurredAt NOT LIKE '%-%' AND OccurredAt GLOB '[0-9][0-9][0-9][0-9]*';
EF Core)، برای پیشگیری قطعی از درج رشتههای بدون آفست در آینده، توصیه میشود از Value Converter صریح استفاده شود:protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// تضمین ثبت مقادیر با فرمت کامل استاندارد ISO-8601 شامل آفست زمانی
modelBuilder.Entity<Event>()
.Property(e => e.OccurredAt)
.HasConversion(
v => v.ToString("O"), // مثال: 2026-08-14T20:00:00.0000000+00:00
v => DateTimeOffset.Parse(v, CultureInfo.InvariantCulture)
);
}Microsoft.Data.Sqlite 10 همراستاسازی ارزشمندی با اصول پایگاههای داده توزیعشده و استانداردهای جهانی زمانی است؛ زیرا وابستگی مقادیر ثبتشده به متغیرهای محیطی سیستمعامل سرور (TimeZoneInfo.Local) یک ضدالگو (Anti-pattern) به شمار میرود. با این حال، به دلیل ماهیت پنهان این تغییر در خواندن دادههای تاریخی بدون آفست، تیمهای توسعهدهنده داتنت پیش از مهاجرت نهایی به .NET 10 باید:"O") تثبیت نمایند.Pre10TimeZoneHandling صرفاً در طول دوره گذار بهره بگیرند.