Implemented EF Core DB connections resiliency with explicit retries and execution strategy when using multiple DbContexts

This commit is contained in:
Cesar De la Torre 2017-03-26 18:00:04 -07:00
parent c07665aef6
commit 0d78461a08
3 changed files with 40 additions and 13 deletions

View File

@ -158,18 +158,26 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
//Update current product //Update current product
catalogItem = productToUpdate; catalogItem = productToUpdate;
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
using (var transaction = _catalogContext.Database.BeginTransaction()) //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
var strategy = _catalogContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{ {
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
using (var transaction = _catalogContext.Database.BeginTransaction())
{
_catalogContext.CatalogItems.Update(catalogItem); _catalogContext.CatalogItems.Update(catalogItem);
await _catalogContext.SaveChangesAsync(); await _catalogContext.SaveChangesAsync();
//Save to EventLog only if product price changed //Save to EventLog only if product price changed
if(raiseProductPriceChangedEvent) if (raiseProductPriceChangedEvent)
await _integrationEventLogService.SaveEventAsync(priceChangedEvent); await _integrationEventLogService.SaveEventAsync(priceChangedEvent);
transaction.Commit(); transaction.Commit();
} }
});
//Publish to Event Bus only if product price changed //Publish to Event Bus only if product price changed
if (raiseProductPriceChangedEvent) if (raiseProductPriceChangedEvent)

View File

@ -13,6 +13,7 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Reflection; using System.Reflection;
@ -39,21 +40,35 @@
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
//Using the same SqlConnection for both DbContexts (CatalogContext and IntegrationEventLogContext)
var sqlConnection = new SqlConnection(Configuration["ConnectionString"]); var sqlConnection = new SqlConnection(Configuration["ConnectionString"]);
services.AddDbContext<CatalogContext>(c => services.AddDbContext<CatalogContext>(options =>
{ {
c.UseSqlServer(sqlConnection); options.UseSqlServer(sqlConnection,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
// Changing default behavior when client evaluation occurs to throw. // Changing default behavior when client evaluation occurs to throw.
// Default in EF Core would be to log a warning when client evaluation is performed. // Default in EF Core would be to log a warning when client evaluation is performed.
c.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval //Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
}); });
services.AddDbContext<IntegrationEventLogContext>(c => services.AddDbContext<IntegrationEventLogContext>(options =>
{ {
c.UseSqlServer(sqlConnection, b => b.MigrationsAssembly("Catalog.API")); options.UseSqlServer(sqlConnection,
c.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}); });
services.Configure<Settings>(Configuration); services.Configure<Settings>(Configuration);

View File

@ -52,10 +52,14 @@
services.AddEntityFrameworkSqlServer() services.AddEntityFrameworkSqlServer()
.AddDbContext<OrderingContext>(options => .AddDbContext<OrderingContext>(options =>
{ {
options.UseSqlServer(Configuration["ConnectionString"], options.UseSqlServer(Configuration["ConnectionString"],
sqlop => sqlop.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name)); sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
}, },
ServiceLifetime.Scoped //DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request) ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
); );
services.AddSwaggerGen(); services.AddSwaggerGen();