اضافه کردن سعی مجدد به اجرای عملیات Migration در EF Core
نویسنده: وحید نصیری
تاریخ: ۱۳۹۹/۰۷/۰۸ ۱۵:۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
public class Startup
{
// Other code ...
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// ...
services.AddDbContext<CatalogContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
});
}
//...
} public static IHost MigrateDbContext<TContext>(this IHost host) where TContext : DbContext
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
logger.LogInformation($"Migrating the DB associated with the context {typeof(TContext).Name}");
var retry = Policy.Handle<Exception>().WaitAndRetry(new[]
{
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(15),
});
retry.Execute(() =>
{
context.Database.Migrate();
});
logger.LogInformation($"Migrated the DB associated with the context {typeof(TContext).Name}");
}
return host;
} public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.MigrateDbContext<AppDbContext>()
.Run();
} System.InvalidOperationException: The configured execution strategy ‘SqlServerRetryingExecutionStrategy’ does not support user initiated transactions. Use the execution strategy returned by ‘DbContext.Database.CreateExecutionStrategy()’ to execute all the operations in the transaction as a retriable unit.
public async Task<IActionResult> UpdateProduct([FromBody]CatalogItem productToUpdate)
{
var strategy = _catalogContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using (var transaction = _catalogContext.Database.BeginTransaction())
{
_catalogContext.CatalogItems.Update(productToUpdate);
await _catalogContext.SaveChangesAsync();
transaction.Commit();
}
});
} public class ResilientTransaction
{
private readonly DbContext _context;
private ResilientTransaction(DbContext context) =>
_context = context ?? throw new ArgumentNullException(nameof(context));
public static ResilientTransaction New(DbContext context) => new(context);
public async Task ExecuteAsync(Func<Task> action)
{
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await using var transaction = await _context.Database.BeginTransactionAsync();
await action();
await transaction.CommitAsync();
});
}
}
// How to use it
await ResilientTransaction.New(_dbContext).ExecuteAsync(async () =>
{
// ...
await _dbContext.SaveChangesAsync();
}); Migrate() را فراخوانی میکردید اما هیچ فایلی در پوشه Migrations پروژه وجود نداشت، EF Core بدون هیچ مشکلی عبور میکرد. صرفاً یک پیام اطلاعاتی (Information Log) ثبت میشد و برنامه به کار خود ادامه میداد.Migrate() در پروژهای که اصلاً مایگریشنی ندارد، معمولاً نشاندهنده یک اشتباه در پیکربندی (Misconfiguration) یا فراموشی توسعهدهنده است. EF Core برای جلوگیری از رفتارهای پیشبینینشده و اطمینان از اینکه پایگاه داده در وضعیت درستی قرار دارد، این سکوت را شکسته و بلافاصله با یک خطای صریح، توسعهدهنده را آگاه میکند.options.ConfigureWarnings(w => w.Ignore(RelationalEventId.MigrationsNotFound));
options.ConfigureWarnings(w => w.Log(RelationalEventId.MigrationsNotFound));
dotnet ef database update یا کد درونبرنامهای context.Database.Migrate() را اجرا میکنند. اگر پروژهای تازه باشد و هنوز اولین Migration آن ثبت نشده باشد، این خط لوله (Pipeline) در EF Core 11 شکست خواهد خورد. بنابراین باید قبل از اولین انتشار، حتماً یک InitialCreate ثبت شده باشد.context.Database.EnsureCreated() استفاده میکنید، نیازی به نگرانی نیست. این تغییر صرفاً روی مکانیزم Migrate() اثر میگذارد. پیشنهاد میشود در پروژههایی که ساختار دیتابیس از طریق مایگریشن مدیریت نمیشود، اصلاً متد Migrate() صدا زده نشود تا نیازی به Suppress کردن این خطا نیز نباشد.