بهترین روشهای مدیریت Migration در EF Core
درک چرخه حیات Migration
قبل از ورود به بهترین روشها، باید بدانیم که یک Migration در EF Core صرفاً یک فایل C# نیست؛ بلکه بخشی از یک چرخه است:
-
تغییر در DbContext یا کلاسهای Entity.
-
تولید فایل Migration (شامل متدهای Up و Down).
-
بروزرسانی ModelSnapshot.
-
اعمال تغییرات روی دیتابیس.
نقش Model Snapshot
فایل ModelSnapshot وضعیت فعلی مدل دیتابیس شما را نگه میدارد. وقتی شما یک Migration جدید میسازید، EF Core کدهای شما را با دیتابیس مقایسه نمیکند، بلکه آن را با آخرین وضعیت Snapshot مقایسه میکند.
نکته مهم: هرگز فایل Snapshot را دستی ویرایش نکنید و در صورت بروز تداخل در Git، با دقت بسیار بالا آن را Merge کنید.
اصول نامگذاری و ساختاردهی (The Basics)
اولین قدم برای داشتن یک تاریخچه Migration تمیز، رعایت نظم است.
نامگذاری توصیفی (Descriptive Naming)
از نامهایی مثل Update1، FixedBug یا Migration2 به شدت پرهیز کنید. نام Migration باید دقیقاً بگوید چه اتفاقی افتاده است.
-
✅ AddProductsTable
-
✅ AddIndexToUserEmail
-
✅ RenameCustomerAddressColumn
این کار باعث میشود هنگام بررسی تاریخچه تغییرات (مثلاً ۶ ماه بعد)، دقیقاً بدانید هر فایل چه تغییری ایجاد کرده است.
یک تغییر، یک Migration
سعی کنید هر Migration فقط یک کار منطقی انجام دهد. اگر همزمان هم جدول Users را تغییر میدهید و هم یک جدول جدید برای Orders میسازید، بهتر است آنها را در دو Migration جداگانه انجام دهید. این کار، Rollback کردن و دیباگ کردن را آسانتر میکند.
استراتژیهای پیشرفته کدنویسی (Coding Strategies)
بازبینی کد قبل از اعمال (Review Generated Code)
EF Core باهوش است، اما دانای کل نیست. همیشه فایل .cs تولید شده توسط دستور Add-Migration را باز کنید و بررسی کنید.
مثال خطرناک:
اگر نام یک Property را از FullName به Name تغییر دهید، EF Core ممکن است فکر کند شما FullName را حذف کردهاید و یک ستون جدید Name ساختهاید. نتیجه؟ حذف ستون قبلی و از دست رفتن تمام دادهها!
در چنین شرایطی، باید دستی وارد عمل شوید و به جای DropColumn و AddColumn، از RenameColumn استفاده کنید:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "FullName",
table: "Users",
newName: "Name");
}
استفاده از SQL خام (Raw SQL) برای عملیات پیچیده
گاهی اوقات متدهای استاندارد EF Core کافی نیستند. مثلاً فرض کنید میخواهید یک ستون IsActive اضافه کنید و مقدار پیشفرض رکوردهای قدیمی True باشد، اما مقدار پیشفرض دیتابیس False.
در اینجا میتوانید از migrationBuilder.Sql استفاده کنید:
migrationBuilder.AddColumn(
name: "IsActive",
table: "Products",
nullable: false,
defaultValue: false);
// آپدیت دادههای موجود
migrationBuilder.Sql("UPDATE Products SET IsActive = 1");
دادههای اولیه (Data Seeding)
برای دادههای ثابت (مثل لیست استانها، وضعیتهای سفارش و ...)، از متد HasData در OnModelCreating استفاده کنید. این کار باعث میشود دادههای شما نیز توسط Migrationها مدیریت شوند.
هشدار: برای دادههای تستی یا حجم بالا از HasData استفاده نکنید. برای آنها بهتر است یک Seeder جداگانه داشته باشید که در زمان شروع برنامه اجرا شود.
۴. استراتژیهای اعمال Migration در محیط Production
این حساسترین بخش مقاله است. چطور تغییرات را به سرور اصلی منتقل کنیم؟
| روش | امنیت | توصیه شده؟ | توضیحات |
| Database.Migrate() در Startup | پایین | ❌ خیر | خطر Race Condition در محیطهای کلاستر شده و کاهش سرعت بالا آمدن اپلیکیشن. |
| آپدیت دستی با CLI | متوسط | ❌ خیر | نیاز به دسترسی توسعهدهنده به دیتابیس Production که امن نیست. |
| اسکریپت SQL (Idempotent) | بالا | ✅ بله | تولید اسکریپت و اجرای آن توسط DBA یا ابزار CI/CD. |
| EF Core Bundles | بسیار بالا | ⭐ عالی | بهترین روش مدرن (معرفی شده در EF Core 6). |
چرا Database.Migrate() در استارتاپ بد است؟
بسیاری از آموزشها پیشنهاد میکنند که در Program.cs متد context.Database.Migrate() را صدا بزنید. این روش برای محیطهای کوچک خوب است، اما در مقیاس بزرگ (Enterprise) مشکلساز است:
-
مشکل همزمانی (Concurrency): اگر اپلیکیشن شما روی چند سرور (Load Balanced) اجرا شود، ممکن است چند نمونه همزمان بخواهند دیتابیس را تغییر دهند.
-
اصل کمترین دسترسی (Least Privilege): اپلیکیشن نباید دسترسی ALTER TABLE یا DROP TABLE روی دیتابیس Production داشته باشد.
راهکار طلایی ۱: اسکریپتهای Idempotent
با استفاده از دستور زیر میتوانید یک اسکریپت SQL دریافت کنید که هوشمند است:
dotnet ef migrations script --idempotent --output script.sql
سویچ --idempotent باعث میشود که اسکریپت قبل از اجرای هر تغییر چک کند که آیا آن Migration قبلاً اعمال شده است یا خیر (با بررسی جدول __EFMigrationsHistory). این اسکریپت را میتوان با خیال راحت بارها اجرا کرد.
راهکار طلایی ۲: Migration Bundles (پیشنهاد ما)
از نسخه EF Core 6 به بعد، قابلیت Bundle معرفی شد. این دستور یک فایل اجرایی (exe در ویندوز یا باینری در لینوکس) میسازد که تمام Migrationهای شما را در خود دارد.
dotnet ef migrations bundle --self-contained -r linux-x64
مزایا:
-
نیاز به نصب SDK داتنت روی سرور مقصد نیست.
-
میتوانید آن را به عنوان یک Step در پایپلاین CI/CD (مثلاً در GitHub Actions یا Azure DevOps) قبل از دیپلوی کردن کد اصلی اجرا کنید.
![]()
۵. مدیریت تیمی و حل تعارضات (Merge Conflicts)
وقتی چند نفر روی یک پروژه کار میکنند، تداخل در فایل ModelSnapshot اجتنابناپذیر است.
سناریو:
-
برنامهنویس A یک جدول Blogs اضافه میکند.
-
برنامهنویس B یک فیلد به جدول Users اضافه میکند.
-
هر دو Migration میسازند و Snapshot تغییر میکند.
راه حل:
-
قانون: فایلهای Migration (آنهایی که Timestamp دارند) را هرگز Merge نکنید. هر فایل باید منحصر به فرد بماند.
-
وقتی تداخل پیش آمد، فایلهای Migration همکار خود را Pull کنید.
-
فایل Migration خودتان را حذف کنید (فایل فیزیکی).
-
سپس Snapshot را با کد جدید همکار Merge کنید (یا Revert کنید).
-
مجدداً دستور Add-Migration را بزنید.
این کار باعث میشود EF Core تغییرات شما را بر اساس "وضعیت جدید دیتابیس (که شامل تغییرات همکار است)" دوباره محاسبه کند.
مدیریت چندین DbContext (Bounded Contexts)
در معماری میکروسرویس یا سیستمهای ماژولار، ممکن است بخواهید جداول را دستهبندی کنید. EF Core اجازه میدهد چندین DbContext داشته باشید.
بهترین روش:
همیشه Migrationهای مربوط به هر Context را در پوشه جداگانه نگه دارید:
dotnet ef migrations add InitialCreate --context AuthContext --output-dir Migrations/Auth
dotnet ef migrations add InitialCreate --context BillingContext --output-dir Migrations/Billing
این جداسازی مانع از تداخل دامینهای مختلف میشود.
نکات ایمنی و Rollback
استراتژی "فقط به جلو" (Roll-Forward)
اگرچه دستور Update-Database TargetMigration اجازه میدهد به عقب برگردید، اما در محیط Production این کار خطرناک است (باعث حذف داده میشود).
بهترین استراتژی این است که اگر یک Migration با باگ به Production رفت، آن را Revert نکنید؛ بلکه یک Migration جدید بسازید که اثرات آن را خنثی کند (مثلاً اگر ستونی را اشتباه اضافه کردید، در مایگریشن جدید آن را حذف کنید).
تست Migrationها
هرگز فرض نکنید کدی که روی سیستم لوکال با دیتابیس SQLite یا SQL Express کار میکند، روی SQL Server Enterprise هم کار خواهد کرد.
-
همیشه یک محیط Staging داشته باشید که کپی دقیقی از دیتابیس Production است.
-
اسکریپت نهایی یا Bundle را ابتدا آنجا تست کنید.
جمعبندی
استفاده از EF Core Migrations قدرتمند است اما نیازمند دیسیپلین است. اگر بخواهیم کل مقاله را در چند قانون خلاصه کنیم:
-
نامگذاری: واضح و دقیق باشد.
-
بازبینی: همیشه کد C# تولید شده را بخوانید و در صورت نیاز ویرایش کنید (مخصوصاً برای Rename).
-
دیپلوی: در Production از Database.Migrate() استفاده نکنید. از Bundles یا Idempotent Scripts در CI/CD استفاده کنید.
-
تیمورک: در زمان تداخل، Migration خود را دور بریزید و پس از دریافت کد همکار، دوباره بسازید.
-
سادگی: هر Migration فقط یک تغییر منطقی را در بر بگیرد.
با رعایت این اصول، دیتابیس شما همگام با کدتان رشد میکند، بدون اینکه ترس از دست دادن دادهها یا خرابی سیستم، شبهای دیپلوی را برای شما تلخ کند.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.