Aracılığıyla paylaş


Dayanıklı Entity Framework Core SQL bağlantıları uygulama

İpucu

Bu içerik, .NET Docs'ta veya çevrimdışı olarak okunabilen ücretsiz indirilebilir bir PDF olarak sağlanan Kapsayıcılı .NET Uygulamaları için .NET Mikro Hizmet Mimarisi e-Kitabı'ndan bir alıntıdır.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Azure SQL DB için Entity Framework (EF) Core zaten iç veritabanı bağlantısı dayanıklılığı ve yeniden deneme mantığı sağlar. Ancak dayanıklı EF Core bağlantılarına sahip olmak istiyorsanız her DbContext bağlantı için Entity Framework yürütme stratejisini etkinleştirmeniz gerekir.

Örneğin, EF Core bağlantı düzeyinde aşağıdaki kod, bağlantı başarısız olursa yeniden denenen dayanıklı SQL bağlantılarını etkinleştirir.

// Program.cs from any ASP.NET Core Web API
// Other code ...
builder.Services.AddDbContext<CatalogContext>(options =>
    {
        options.UseSqlServer(builder.Configuration["ConnectionString"],
        sqlServerOptionsAction: sqlOptions =>
        {
            sqlOptions.EnableRetryOnFailure(
            maxRetryCount: 10,
            maxRetryDelay: TimeSpan.FromSeconds(30),
            errorNumbersToAdd: null);
        });
    });

BeginTransaction ve birden çok DbContext kullanarak yürütme stratejileri ve açık işlemler

EF Core bağlantılarında yeniden denemeler etkinleştirildiğinde, EF Core kullanarak gerçekleştirdiğiniz her işlem kendi yeniden denenebilir işlemi olur. Her sorgu ve her çağrısı SaveChanges , geçici bir hata oluşursa birim olarak yeniden denenir.

Ancak kodunuz kullanarak BeginTransactionbir işlem başlatırsa, birim olarak ele alınması gereken kendi işlem grubunuzu tanımlarsınız. Bir hata oluşursa işlem içindeki her şeyin geri alınması gerekir.

EF yürütme stratejisini (yeniden deneme ilkesi) kullanırken bu işlemi yürütmeye çalışırsanız ve birden çok DbContexts'ten çağırırsanız SaveChanges aşağıdakine benzer bir özel durumla karşı karşıyasınız:

System.InvalidOperationException: Yapılandırılan yürütme stratejisi 'SqlServerRetryingExecutionStrategy' kullanıcı tarafından başlatılan işlemleri desteklemiyor. İşlemdeki tüm işlemleri yeniden denenebilir bir birim olarak yürütmek için 'DbContext.Database.CreateExecutionStrategy()' tarafından döndürülen yürütme stratejisini kullanın.

Çözüm, EF yürütme stratejisini, yürütülmesi gereken her şeyi temsil eden bir temsilciyle el ile çağırmaktır. Geçici bir hata oluşursa yürütme stratejisi temsilciyi yeniden çağırır. Örneğin aşağıdaki kod, bir ürünü güncelleştirirken ve sonra farklı bir DbContext kullanması gereken ProductPriceChangedIntegrationEvent nesnesini kaydederken iki birden çok DbContext (_catalogContext ve IntegrationEventLogContext) içeren eShopOnContainers'da nasıl uygulandığını gösterir.

public async Task<IActionResult> UpdateProduct(
    [FromBody]CatalogItem productToUpdate)
{
    // Other code ...

    var oldPrice = catalogItem.Price;
    var raiseProductPriceChangedEvent = oldPrice != productToUpdate.Price;

    // Update current product
    catalogItem = productToUpdate;

    // Save product's data and publish integration event through the Event Bus
    // if price has changed
    if (raiseProductPriceChangedEvent)
    {
        //Create Integration Event to be published through the Event Bus
        var priceChangedEvent = new ProductPriceChangedIntegrationEvent(
          catalogItem.Id, productToUpdate.Price, oldPrice);

       // Achieving atomicity between original Catalog database operation and the
       // IntegrationEventLog thanks to a local transaction
       await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(
           priceChangedEvent);

       // Publish through the Event Bus and mark the saved event as published
       await _catalogIntegrationEventService.PublishThroughEventBusAsync(
           priceChangedEvent);
    }
    // Just save the updated product because the Product's Price hasn't changed.
    else
    {
        await _catalogContext.SaveChangesAsync();
    }
}

Birincisi DbContext , _catalogContext ikincisi DbContext nesnenin _catalogIntegrationEventService içindedir. İşleme eylemi, EF yürütme stratejisi kullanılarak tüm DbContext nesneler arasında gerçekleştirilir.

Bu birden çok DbContext işlemeyi SaveEventAndCatalogContextChangesAsync başarmak için aşağıdaki kodda gösterildiği gibi bir ResilientTransaction sınıf kullanır:

public class CatalogIntegrationEventService : ICatalogIntegrationEventService
{
    //…
    public async Task SaveEventAndCatalogContextChangesAsync(
        IntegrationEvent evt)
    {
        // Use of an EF Core resiliency strategy when using multiple DbContexts
        // within an explicit BeginTransaction():
        // https://learn.microsoft.com/ef/core/miscellaneous/connection-resiliency
        await ResilientTransaction.New(_catalogContext).ExecuteAsync(async () =>
        {
            // Achieving atomicity between original catalog database
            // operation and the IntegrationEventLog thanks to a local transaction
            await _catalogContext.SaveChangesAsync();
            await _eventLogService.SaveEventAsync(evt,
                _catalogContext.Database.CurrentTransaction.GetDbTransaction());
        });
    }
}

ResilientTransaction.ExecuteAsync yöntemi temel olarak geçirilen DbContext (_catalogContext) işleminden bir işlem başlatır ve ardından dosyasındaki EventLogService değişiklikleri IntegrationEventLogContext kaydetmek için bu işlemi kullanır ve ardından işlemin tamamını işler.

public class ResilientTransaction
{
    private DbContext _context;
    private ResilientTransaction(DbContext context) =>
        _context = context ?? throw new ArgumentNullException(nameof(context));

    public static ResilientTransaction New (DbContext context) =>
        new ResilientTransaction(context);

    public async Task ExecuteAsync(Func<Task> action)
    {
        // Use of an EF Core resiliency strategy when using multiple DbContexts
        // within an explicit BeginTransaction():
        // https://learn.microsoft.com/ef/core/miscellaneous/connection-resiliency
        var strategy = _context.Database.CreateExecutionStrategy();
        await strategy.ExecuteAsync(async () =>
        {
            await using var transaction = await _context.Database.BeginTransactionAsync();
            await action();
            await transaction.CommitAsync();
        });
    }
}

Ek kaynaklar