Share via


Rugalmas Entity Framework Core SQL-kapcsolatok implementálása

Tipp.

Ez a tartalom egy részlet a .NET-alkalmazásokhoz készült .NET-alkalmazásokhoz készült eBook, .NET Microservices Architecture című eBookból, amely elérhető a .NET Docs-on vagy egy ingyenesen letölthető PDF-fájlként, amely offline módban is olvasható.

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

Az Azure SQL DB esetében az Entity Framework (EF) Core már belső adatbázis-kapcsolati rugalmasságot és újrapróbálkozási logikát biztosít. Ha azonban rugalmas EF Core-kapcsolatokat szeretne használni, engedélyeznie kell az Entity Framework végrehajtási stratégiáját minden kapcsolathozDbContext.

Az EF Core-kapcsolat szintjén például az alábbi kód lehetővé teszi az olyan rugalmas SQL-kapcsolatokat, amelyek újrapróbálkozódnak, ha a kapcsolat meghiúsul.

// 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);
        });
    });

Végrehajtási stratégiák és explicit tranzakciók a BeginTransaction és több DbContext használatával

Ha az újrapróbálkozások engedélyezve vannak az EF Core-kapcsolatokban, az EF Core használatával végrehajtott minden művelet saját újrapróbálkozási művelet lesz. Ha átmeneti hiba történik, az egyes lekérdezéseket és hívásokat SaveChanges egységként újrapróbáljuk.

Ha azonban a kód egy tranzakciót kezdeményez, BeginTransactionön határozza meg a saját műveletcsoportját, amelyet egységként kell kezelni. Ha hiba történik, a tranzakció minden elemét vissza kell állítani.

Ha ef végrehajtási stratégia (újrapróbálkozási szabályzat) használatakor próbálja végrehajtani a tranzakciót, és több DbContextsből hív SaveChanges meg, az alábbihoz hasonló kivételt fog kapni:

System.InvalidOperationException: Az SqlServerRetryingExecutionStrategy konfigurált végrehajtási stratégia nem támogatja a felhasználó által kezdeményezett tranzakciókat. Használja a DbContext.Database.CreateExecutionStrategy()) által visszaadott végrehajtási stratégiát a tranzakció összes műveletének újrapróbálkozható egységként való végrehajtásához.

A megoldás az EF végrehajtási stratégiájának manuális meghívása egy meghatalmazottal, amely minden végrehajtandó elemet képvisel. Átmeneti hiba előfordulásakor a végrehajtási stratégia ismét meghívja a delegáltat. Az alábbi kód például bemutatja, hogyan implementálható két több DbContexts (_catalogContext és IntegrationEventLogContext) eShopOnContainersben egy termék frissítésekor, majd a ProductPriceChangedIntegrationEvent objektum mentésekor, amelynek egy másik DbContextet kell használnia.

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();
    }
}

Az első DbContext , _catalogContext a második DbContext pedig az objektumon _catalogIntegrationEventService belül van. A véglegesítési műveletet az összes DbContext objektumon végrehajtja egy EF végrehajtási stratégia használatával.

A több DbContext véglegesítés eléréséhez a SaveEventAndCatalogContextChangesAsync rendszer egy osztályt ResilientTransaction használ, ahogyan az a következő kódban látható:

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());
        });
    }
}

A ResilientTransaction.ExecuteAsync metódus alapvetően elindít egy tranzakciót az átadott DbContext (_catalogContext) értékről, majd ezt a EventLogService tranzakciót használja a módosítások mentéséhez, IntegrationEventLogContext majd véglegesíti a teljes tranzakciót.

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();
        });
    }
}

További erőforrások