ResilientSaveChanges.EFCore 9.0.0

dotnet add package ResilientSaveChanges.EFCore --version 9.0.0                
NuGet\Install-Package ResilientSaveChanges.EFCore -Version 9.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ResilientSaveChanges.EFCore" Version="9.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ResilientSaveChanges.EFCore --version 9.0.0                
#r "nuget: ResilientSaveChanges.EFCore, 9.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ResilientSaveChanges.EFCore as a Cake Addin
#addin nuget:?package=ResilientSaveChanges.EFCore&version=9.0.0

// Install ResilientSaveChanges.EFCore as a Cake Tool
#tool nuget:?package=ResilientSaveChanges.EFCore&version=9.0.0                

ResilientSaveChanges.EFCore ResilientSaveChanges.EFCore

GitHub Workflow Status Nuget Nuget Codacy Grade

A library that allows resilient context.SaveChanges / SaveChangesAsync in Entity Framework Core, logging of long-running transactions and limiting of concurrent SaveChanges.

Installation

The recommended means is to use NuGet, but you could also download the source code from here.

Configuration

ResilientSaveChangesConfig.Logger = _logger;
ResilientSaveChangesConfig.LoggerWarnLongRunning = 3_000;
ResilientSaveChangesConfig.ConcurrentSaveChangesLimit = 5;

Setting up an exectution strategy

You now need to create an execution strategy. An example using MySQL and Pomelo:

public static class Constants
{
    public const int MAX_RETRY_COUNT = 10;
    public const int MAX_RETRY_DELAY_SECONDS = 6;
    public const int COMMAND_TIMEOUT = 120;
}

public class MyExecutionStrategy : ExecutionStrategy
{
    public MyExecutionStrategy(MyDbContext context) : base(
        context,
        Constants.MAX_RETRY_COUNT,
        TimeSpan.FromSeconds(Constants.MAX_RETRY_DELAY_SECONDS))
    { }

    public MyExecutionStrategy(ExecutionStrategyDependencies dependencies) : base(
        dependencies,
        Constants.MAX_RETRY_COUNT,
        TimeSpan.FromSeconds(Constants.MAX_RETRY_DELAY_SECONDS))
    { }

    public MyExecutionStrategy(MyDbContext context, int maxRetryCount, TimeSpan maxRetryDelay) : base(
        context,
        maxRetryCount,
        maxRetryDelay)
    { }

    protected override bool ShouldRetryOn([NotNull] Exception exception)
    {
        if (exception is MySqlException mySqlException)
        {
            if (mySqlException.IsTransient)
            {
                Debug.WriteLine($"MySqlException transient error detected. Retrying in {Constants.MAX_RETRY_DELAY_SECONDS} seconds");
                return true;
            }
            Debug.WriteLine($"Non-transient MySqlException detected.");
            return false;
        }

        if (exception is DbUpdateException)
        {
            Debug.WriteLine($"DbUpdateException detected. Retrying in {Constants.MAX_RETRY_DELAY_SECONDS} seconds");
            return true;
        }

        Debug.WriteLine($"Error that won't be retried. Type is {exception.GetType()}");
        return false;
    }
}

Using the execution strategy

You now need to set your MySQL or SQL Server options to enable retry on failure and to use your execution strategy. An example using MySQL and Pomelo:

services.AddPooledDbContextFactory<MyDbContext>(options =>
{
    options.UseMySql(
        Configuration.GetConnectionString("DefaultConnection"),
        "8.0.29",
        options =>
        {
            options.EnableRetryOnFailure(
                Constants.MAX_RETRY_COUNT, 
                TimeSpan.FromSeconds(Constants.MAX_RETRY_DELAY_SECONDS),
                null);
            options.CommandTimeout(Constants.COMMAND_TIMEOUT);
            options.ExecutionStrategy(s => new MyExecutionStrategy(s));
        }
    ).EnableDetailedErrors();
});

How to use

Now simply replace your context.SaveChanges(); and await context.SaveChangesAsync(); with context.ResilientSaveChanges(); and context.ResilientSaveChangesAsync(); respectively.

Credits

Some code has been adapted from code found in .NET Microservices: Architecture for Containerized .NET Applications (de la Torre, Wagner, & Rousos, 2022)

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
9.0.0 76 11/17/2024
8.0.11 71 11/17/2024
8.0.10 91 10/13/2024
8.0.7 114 7/10/2024
8.0.0 695 12/2/2023
7.0.14 137 12/2/2023
7.0.13 137 12/2/2023
7.0.12 170 10/11/2023
7.0.5 131 9/17/2023
7.0.4 316 3/25/2023
7.0.2.2 315 1/29/2023
7.0.2 312 1/11/2023
7.0.1.2 324 12/16/2022
7.0.1.1 311 12/13/2022
7.0.1 316 12/12/2022
7.0.0 482 11/13/2022
6.0.10 448 10/18/2022
1.0.5 460 6/29/2022
1.0.4 444 6/29/2022
1.0.3 443 6/26/2022

Support for EFCore 9.0.0.