بررسی جامع قابلیتها و بهبودهای بخش Migrations در Entity Framework Core 11.0
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۶/۲۱ ۰۸:۳۵
آدرس: www.dntips.ir
چکیده: مدیریت تغییرات مدل پایگاه داده (Database Migrations) همواره یکی از ارکان اساسی در چرخه حیات نرمافزارهای مبتنی بر داده است. با انتشار نسخه پیشنمایش .NET 11 RC1 و در آستانه انتشار نهایی EF Core 11.0، تیم توسعه مایکروسافت مجموعهای از قابلیتهای کلیدی و بهینهسازیهای زیرساختی را برای ابزار مهاجرت (Migrations) ارائه کرده است. این بهبودها بر سه محور اصلی تمرکز دارند: ارتقای انعطافپذیری مدلسازی برای سناریوهای دادهای پیچیده، تسریع جریانهای کاری توسعه و یکپارچهسازی در خطوط لوله CI/CD، کاهش اصطکاک و تعارضات ناشی از کار گروهی در مخازن کنترل نسخه. این مقاله با تحلیل دقیق فنی، قابلیتهایی نظیر تفکیک قیدهای کلید خارجی از مهاجرت، ایجاد و اِعمال تکمرحلهای مایگریشن با کامپایل درجا (Roslyn)، ثبت شناسه آخرین مهاجرت در Model Snapshot، پشتیبانی از پیکربندی سراسری ابزار CLI و اجرای دستهجمعی روی چند کانتکست را مورد واکاوی قرار میدهد.
dotnet ef) و رابط روان کدنویسی (Fluent API) را به گونهای ارتقا داده است که توسعهدهنده بر طراحی دامنه تمرکز کند و درگیر خطاهای محیطی یا محدودیتهای سختگیرانه RDBMS نشود.FOREIGN KEY CONSTRAINT) در پایگاه داده مشکلساز خواهد بود.ExcludeForeignKeyFromMigrations()، مایکروسافت راهکاری تمیز و استاندارد معرفی کرده است:public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public class Order
{
public int Id { get; set; }
public string OrderNumber { get; set; } = string.Empty;
public int CustomerId { get; set; }
public Customer? Customer { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<Customer> Customers => Set<Customer>();
public DbSet<Order> Orders => Set<Order>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasOne(o => o.Customer)
.WithMany()
.HasForeignKey(o => o.CustomerId)
.ExcludeForeignKeyFromMigrations(); // غیرفعالسازی ساخت قید در دیتابیس
}
}Up، مشاهده میشود که ستون CustomerId و اندیس IX_Orders_CustomerId تولید میشوند، اما قید foreignKey حذف شده است:protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Orders",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
OrderNumber = table.Column<string>(type: "nvarchar(max)", nullable: false),
CustomerId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Orders", x => x.Id);
});
// ایندکس جهت کارایی کوئریها همچنان ساخته میشود:
migrationBuilder.CreateIndex(
name: "IX_Orders_CustomerId",
table: "Orders",
column: "CustomerId");
}نکته تحلیلی: اگرچه قید فیزیکی حذف میشود، اما سیستم Change Tracking، کوئریهای جوین (Include) و بارگذاری تنبل/حریصانه در EF Core دقیقاً مانند یک رابطه نرمال رفتار میکنند. در واقع مسئولیت حفظ جامعیت ارجاعی در صورت نیاز به لایه بیزنس (Application Logic) منتقل میشود.dotnet ef migrations adddotnet ef database update--add (یا -Add در Package Manager Console) این دو گام را با یکدیگر ادغام کرده است:dotnet ef database update InitialCreate --add
Update-Database -Migration InitialCreate -Add
ارزش افزوده در توسعه مدرن: در معماریهای مبتنی بر .NET Aspire، ابزارهای تست محلی با Testcontainers، و محیطهای توسعه در قالب کانتینر که امکان Rebuild مجدد کل پروژه بلافاصله پس از تولید فایل وجود ندارد، این قابلیت موجب تداوم اجرای سریع کانتینر و اعمال فوری طرحواره میشود.
feature/auth مایگریشن اضافه میکند.feature/catalog مایگریشن دیگری اضافه میکند.ModelSnapshot شناسه (ID) آخرین مهاجرت ایجادشده را در کد خود ذخیره میکند. هنگامی که شاخههای موازی ادغام شوند، هر دو شاخه تلاش میکنند این مقدار یکتا را تغییر دهند، که این امر منجر به Git Merge Conflict صریح در فایل Snapshot میشود.dotnet ef migrations remove و dotnet ef database drop دستخوش بازنگری مهمی شدهاند.--connection:appsettings.json یا کلاس DbContext برای اجرای عملیاتی بر روی دیتابیس مشخصی نیست:dotnet ef migrations remove --connection "Server=dev.local;Database=TestDb;User Id=sa;Password=secret;" dotnet ef database drop --connection "Server=dev.local;Database=TestDb;..." --force
--offline:migrations remove برای بررسی اینکه آیا مهاجرت قبلاً روی دیتابیس اعمال شده یا نه، حتماً یک ارتباط با دیتابیس برقرار میکرد. اگر سرور دیتابیس در دسترس نبود، امکان پاکسازی فایل محلی مهاجرت وجود نداشت.--offline، این بررسی متوقف شده و تنها فایلهای محلی و Snapshot بازنشانی میشوند:dotnet ef migrations remove --offline
قاعده طراحی: سوئیچهای--offlineو--forceناسازگار هستند؛ زیرا--forceبرای عقبگرد (Revert) تغییرات نیازمند اتصال فعال به پایگاه داده است تا بداند وضعیت ساختار فعلی چیست.
dotnet-ef.json)--project، --startup-project یا کانتکست مورد نظر در هر بار اجرای دستور نیست. ابزار خط فرمان با جستجو در سلسلهمراتب پوشهها از دایرکتوری فعلی رو به بالا، فایل .config/dotnet-ef.json را تشخیص میدهد:{
"project": "src/Infrastructure/Persistence",
"startupProject": "src/Presentation/Web.Api",
"framework": "net11.0",
"configuration": "Debug",
"context": "ApplicationDbContext",
"runtime": "win-x64",
"verbose": true,
"noColor": false,
"prefixOutput": false
}.config محاسبه و خوانده میشوند. همچنین پارامترهای صریح واردشده در خط فرمان همواره اولویت بالاتری نسبت به فایل تنظیمات دارند.DbContext در یک سورسکد وجود دارد، اعمال تغییرات نیازمند تکرار دستور به ازای هر کانتکست بود. در نسخه جدید، مقدار * برای پارامتر --context پشتیبانی میشود:dotnet ef migrations list --context "*" dotnet ef database update --context "*" dotnet ef migrations script --context "*" dotnet ef database drop --context "*"
-NoBuildدر Package Manager Console (PMC)Add-Migration میشدند. سوئیچ -NoBuild تضمین میکند که اگر بیلد پروژه از قبل آماده است، فرآیند کامپایل مجدد نادیده گرفته شود:Add-Migration UpdateCustomerEntity -NoBuild Update-Database -NoBuild
script) خواهد بود که برای اسکریپتهای اتوماسیون (CI/CD Pipes) بسیار کارآمد است.ToAsyncEnumerable با فرم بهینه و صحیح AsAsyncEnumerable در کوئریهای ناهمگام.