{"id":8535,"date":"2017-03-26T17:37:32","date_gmt":"2017-03-27T01:37:32","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/cesardelatorre\/?p=8535"},"modified":"2019-03-18T14:38:58","modified_gmt":"2019-03-18T21:38:58","slug":"using-resilient-entity-framework-core-sql-connections-and-transactions-retries-with-exponential-backoff","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/using-resilient-entity-framework-core-sql-connections-and-transactions-retries-with-exponential-backoff\/","title":{"rendered":"Using Resilient Entity Framework Core Sql Connections and Transactions: Retries with Exponential Backoff"},"content":{"rendered":"<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/32\/2019\/03\/image701.png\"><img decoding=\"async\" style=\"padding-top: 0px; padding-left: 0px; margin: 0px; padding-right: 0px; border: 0px;\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/32\/2019\/03\/image_thumb650.png\" alt=\"image\" width=\"244\" height=\"149\" border=\"0\" \/><\/a><\/p>\n<p>There are many possible approaches to implement retries logic with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Exponential_backoff\">exponential backoff<\/a> also depending on the context\/protocol, etc. (Database connections, HTTP requests, etc.)<\/p>\n<p>Retries with exponential backoff is a technique that assumes failure by nature and attempts to retry the operation, with an exponentially increasing wait time, until a maximum retry count has been reached. This technique embraces the fact that intermittently, cloud resources may be unavailable more than a few seconds, for any reason out of your control.<\/p>\n<p>An important case is about <strong>SQL Azure Databases<\/strong> that may be moved to another server at any time for load balancing reasons, causing the database from being unavailable for a few seconds.<\/p>\n<p>Another possibility could be if you have <strong>SQL Server deployed as a container<\/strong> and for some reason (like when spinning up the containers with docker-compose up) the SQL Server container is still not ready for other microservices\/containers, like we do at the <a href=\"https:\/\/github.com\/dotnet\/eShopOnContainers\">eShopOnContainers reference application<\/a> for the dev\/test environment.<\/p>\n<h2>Resilient Entity Framework Core Sql Connections<\/h2>\n<p>In regards the Azure SQL DB case, Entity Framework Core already provides internal database connection resiliency and retry logic, but you need to enable your desired execution strategy per DbContext connection if you want to have <a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/miscellaneous\/connection-resiliency\">resilient EF Core connections<\/a>.<\/p>\n<p>For instance, the following code at the EF Core connection level is enabling resilient SQL connections that will re-try if it gets any temporal failure like it is possible when using Azure SQL Database or SQL Server deployed as a container if it is not ready.<\/p>\n<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"600\">\/\/Startup.cs from any ASP.NET Core Web API<\/p>\n<p>public class Startup<\/p>\n<p>{<\/p>\n<p>\/\/Other code\u2026<\/p>\n<p>\/\/&#8230;<\/p>\n<p>public IServiceProvider ConfigureServices(IServiceCollection services)<\/p>\n<p>{<\/p>\n<p>\/\/&#8230;<\/p>\n<p><b>\u00a0\u00a0\u00a0\u00a0\u00a0 services.AddDbContext&lt;<\/b><b>OrderingContext<\/b><b>&gt;(options =&gt;<\/b><\/p>\n<p>{<\/p>\n<p>options.<strong>UseSqlServer<\/strong>(Configuration[&#8220;ConnectionString&#8221;],<\/p>\n<p><b>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sqlServerOptionsAction: sqlOptions =&gt;<\/b><\/p>\n<p><b>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 { <\/b><\/p>\n<p><b>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sqlOptions.<\/b><b>EnableRetryOnFailure<\/b><b>(maxRetryCount: 5,<\/b><\/p>\n<p><b>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 maxRetryDelay: <\/b><b>TimeSpan<\/b><b>.FromSeconds(30), <\/b><\/p>\n<p><b>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 errorNumbersToAdd: <\/b><b>null<\/b><b>);<\/b><\/p>\n<p><b>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }); <\/b><\/p>\n<p>});<\/p>\n<p>\/\/&#8230;<\/p>\n<p>}<\/p>\n<p>}<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Execution strategies and explicit transactions using BeginTransaction()<\/h2>\n<p>When retries are enabled in EF Core connections, each operation you perform via EF Core becomes its own retriable operation, i.e. each query and each call to SaveChanges() will be retried as a unit if a transient failure occurs.<\/p>\n<p>However, if your code initiates a transaction using <i>BeginTransaction()<\/i> you are defining your own group of operations that need to be treated as a unit, i.e. everything inside the transaction would need to be played back shall a failure occur. You will receive an exception like the following if you attempt to do this when using an execution strategy.<\/p>\n<p><i>System.InvalidOperationException: The configured execution strategy &#8216;SqlServerRetryingExecutionStrategy&#8217; does not support user initiated transactions. Use the execution strategy returned by &#8216;DbContext.Database.CreateExecutionStrategy()&#8217; to execute all the operations in the transaction as a retriable unit.<\/i><\/p>\n<p>The solution, is to manually invoke the execution strategy with a delegate representing everything that needs to be executed. If a transient failure occurs, the execution strategy will invoke the delegate again, as in the following code implemented in <i>eShopOnContainers<\/i> when using two multiple DbContexts related to the <i>CatalogContext<\/i> and the <i>IntegrationEventLogContext<\/i> when updating a product and saving the ProductPriceChanged integration event that needs to use a different DbContext.<\/p>\n<p>&nbsp;<\/p>\n<table border=\"1\" width=\"750\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"750\">public async Task&lt;IActionResult&gt; UpdateProduct([FromBody]CatalogItem productToUpdate)<\/p>\n<p>{<\/p>\n<p>\/\/Other code\u2026<\/p>\n<p>\/\/Update current product<\/p>\n<p>catalogItem = productToUpdate;<\/p>\n<p>\/\/ Use of an EF Core resiliency strategy when using multiple DbContexts<\/p>\n<p>\/\/ within an explicit BeginTransaction():<\/p>\n<p>\/\/ See: <a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/miscellaneous\/connection-resiliency\">https:\/\/docs.microsoft.com\/en-us\/ef\/core\/miscellaneous\/connection-resiliency<\/a><\/p>\n<p><strong>var strategy =\u00a0 <\/strong><strong>_catalogContext.Database.CreateExecutionStrategy();<\/strong><\/p>\n<p><strong>\u00a0\u00a0\u00a0 await strategy.ExecuteAsync(async () =&gt;<\/strong><\/p>\n<p>{<\/p>\n<p>\/\/ Achieving atomicity between original Catalog database operation and the<\/p>\n<p>\/\/ IntegrationEventLog thanks to a local transaction<\/p>\n<p><strong> using (var transaction = _catalogContext.Database.BeginTransaction())<\/strong><\/p>\n<p>{<\/p>\n<p>_catalogContext.CatalogItems.Update(catalogItem);<\/p>\n<p>await _catalogContext.SaveChangesAsync();<\/p>\n<p>\/\/Save to EventLog only if product price changed<\/p>\n<p>if (raiseProductPriceChangedEvent)<\/p>\n<p>await _integrationEventLogService.SaveEventAsync(priceChangedEvent);<\/p>\n<p><strong> transaction.Commit();<\/strong><\/p>\n<p>}<\/p>\n<p>});<\/p>\n<p>\/\/Other code<\/p>\n<p>}<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;<\/p>\n<p>The code above is actual code we\u2019re implementing in the <a href=\"https:\/\/github.com\/dotnet\/eShopOnContainers\">eShopOnContainers reference application<\/a>, so you can see it in action across a microservice + Docker containers application here:<\/p>\n<p><a title=\"https:\/\/github.com\/dotnet\/eShopOnContainers\" href=\"https:\/\/github.com\/dotnet\/eShopOnContainers\">https:\/\/github.com\/dotnet\/eShopOnContainers<\/a>\u00a0\u00a0\u00a0 (The code above, currently in the DEV branch, will be merged to the MASTER branch pretty soon).<\/p>\n<p>&nbsp;<\/p>\n<p>There are other scenarios with Retries with Exponential Backoff related to protocols like HTTP. I\u2019ll blog about it in upcoming posts. \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There are many possible approaches to implement retries logic with exponential backoff also depending on the context\/protocol, etc. (Database connections, HTTP requests, etc.) Retries with exponential backoff is a technique that assumes failure by nature and attempts to retry the operation, with an exponentially increasing wait time, until a maximum retry count has been reached. [&hellip;]<\/p>\n","protected":false},"author":362,"featured_media":12806,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-8535","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cesardelatorre"],"acf":[],"blog_post_summary":"<p>There are many possible approaches to implement retries logic with exponential backoff also depending on the context\/protocol, etc. (Database connections, HTTP requests, etc.) Retries with exponential backoff is a technique that assumes failure by nature and attempts to retry the operation, with an exponentially increasing wait time, until a maximum retry count has been reached. [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/posts\/8535","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/users\/362"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/comments?post=8535"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/posts\/8535\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/media\/12806"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/media?parent=8535"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/categories?post=8535"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cesardelatorre\/wp-json\/wp\/v2\/tags?post=8535"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}