Устойчивость подключений

Устойчивость подключений автоматически повторяет команды базы данных, завершившиеся сбоем. Эту функцию можно использовать с любой базой данных, предоставив «стратегию выполнения», которая инкапсулирует логику, необходимую для обнаружения сбоев и команд повтора. Поставщики EF Core могут предоставлять стратегии выполнения, связанные с конкретными условиями сбоя базы данных и оптимальными политиками повтора.

например, поставщик SQL Server включает стратегию выполнения, специально адаптированную для SQL Server (включая SQL Azure). Он осведомлен о типах исключений, которые можно повторить, и имеет разумные значения по умолчанию для максимального количества повторных попыток, задержки между повторными попытками и т. д.

Стратегия выполнения указывается при настройке параметров для контекста. Обычно это происходит в OnConfiguring методе производного контекста:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(
            @"Server=(localdb)\mssqllocaldb;Database=EFMiscellanous.ConnectionResiliency;Trusted_Connection=True",
            options => options.EnableRetryOnFailure());
}

или в Startup.cs для приложения ASP.NET Core:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<PicnicContext>(
        options => options.UseSqlServer(
            "<connection string>",
            providerOptions => providerOptions.EnableRetryOnFailure()));
}

Примечание

Включение повтора в случае сбоя приводит к тому, что EF внутренним образом замещает результирующий набор, что может значительно увеличить требования к памяти для запросов, возвращающих большие результирующие наборы. Дополнительные сведения см. в разделе буферизация и потоковая передача .

Настраиваемая стратегия выполнения

Существует механизм регистрации пользовательской стратегии выполнения, если вы хотите изменить какие-либо значения по умолчанию.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseMyProvider(
            "<connection string>",
            options => options.ExecutionStrategy(...));
}

Стратегии и транзакции выполнения

Стратегия выполнения, которая автоматически повторяет попытки сбоев, должна иметь возможность воспроизводить каждую операцию в блоке повтора, которая завершается ошибкой. Если повторные попытки включены, каждая операция, выполняемая с помощью EF Core, станет собственной операцией возможностью повторной попытки. Это значит, что каждый запрос и каждый вызов SaveChanges() будет выполняться повторно как единица при возникновении временного сбоя.

Однако, если код инициирует транзакцию с помощью, BeginTransaction() вы определяете собственную группу операций, которую необходимо рассматривать как единую единицу, и все, что необходимо в рамках транзакции, должны быть воспроизведены, если произойдет сбой. При попытке сделать это при использовании стратегии выполнения вы получите исключение, аналогичное приведенному ниже.

InvalidOperationException: настроенная стратегия выполнения "Склсерверретринжексекутионстратеги" не поддерживает инициированные пользователем транзакции. Используйте стратегию выполнения, возвращенную 'DbContext.Database.CreateExecutionStrategy()', чтобы выполнить все операции в транзакции как повторяемую единицу.

Решение заключается в том, чтобы вручную вызвать стратегию выполнения с делегатом, представляющим все, что необходимо выполнить. В случае временного сбоя стратегия выполнения будет снова вызывать делегат.

using (var db = new BloggingContext())
{
    var strategy = db.Database.CreateExecutionStrategy();

    strategy.Execute(
        () =>
        {
            using (var context = new BloggingContext())
            {
                using (var transaction = context.Database.BeginTransaction())
                {
                    context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
                    context.SaveChanges();

                    context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
                    context.SaveChanges();

                    transaction.Commit();
                }
            }
        });
}

Этот подход также можно использовать с внешними транзакциями.

using (var context1 = new BloggingContext())
{
    context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });

    var strategy = context1.Database.CreateExecutionStrategy();

    strategy.Execute(
        () =>
        {
            using (var context2 = new BloggingContext())
            {
                using (var transaction = new TransactionScope())
                {
                    context2.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
                    context2.SaveChanges();

                    context1.SaveChanges();

                    transaction.Complete();
                }
            }
        });
}

Сбой фиксации транзакции и проблема идемпотентности

Как правило, при сбое соединения выполняется откат текущей транзакции. Однако, если соединение разорвано во время фиксации транзакции, результирующее состояние транзакции неизвестно.

По умолчанию стратегия выполнения повторяет операцию, как если бы был выполнен откат транзакции, но если это не так, это приведет к возникновению исключения, если новое состояние базы данных несовместимо или может привести к повреждению данных , если операция не зависит от определенного состояния, например при вставке новой строки с автоматически созданными значениями ключа.

Существует несколько способов решения этой проблемы.

Вариант 1-Do (почти) Nothing

Вероятность сбоя подключения во время фиксации транзакции мала, поэтому приложение может быть вполне неудачным, если это условие действительно происходит.

Однако необходимо избегать использования созданных хранилищем ключей, чтобы гарантировать, что вместо добавления повторяющейся строки создается исключение. Рассмотрите возможность использования генерируемого клиентом значения GUID или генератора значений на стороне клиента.

Вариант 2. Перестроение состояния приложения

  1. Удаление текущего DbContext .
  2. Создайте новое DbContext и восстановите состояние приложения из базы данных.
  3. Сообщите пользователю о том, что последняя операция могла завершиться неудачно.

Вариант 3. Проверка добавления состояния

Для большинства операций, изменяющих состояние базы данных, можно добавить код, который проверяет, была ли она успешной. EF предоставляет метод расширения для упрощения этого IExecutionStrategy.ExecuteInTransaction .

Этот метод начинает и фиксирует транзакцию, а также принимает функцию в verifySucceeded параметре, который вызывается при возникновении временной ошибки во время фиксации транзакции.

using (var db = new BloggingContext())
{
    var strategy = db.Database.CreateExecutionStrategy();

    var blogToAdd = new Blog { Url = "http://blogs.msdn.com/dotnet" };
    db.Blogs.Add(blogToAdd);

    strategy.ExecuteInTransaction(
        db,
        operation: context => { context.SaveChanges(acceptAllChangesOnSuccess: false); },
        verifySucceeded: context => context.Blogs.AsNoTracking().Any(b => b.BlogId == blogToAdd.BlogId));

    db.ChangeTracker.AcceptAllChanges();
}

Примечание

Здесь SaveChanges вызывается с acceptAllChangesOnSuccess параметром, чтобы false избежать изменения состояния Blog сущности на, если она UnchangedSaveChanges выполнена. Это позволяет повторить ту же операцию в случае сбоя фиксации и отката транзакции.

Вариант 4. Отслеживание транзакции вручную

Если необходимо использовать созданные хранилищем ключи или требуется универсальный способ обработки ошибок фиксации, которые не зависят от выполняемой операции, можно назначить идентификатор, который проверяется при сбое фиксации.

  1. Добавьте таблицу в базу данных, используемую для наблюдения за состоянием транзакций.
  2. Вставка строки в таблицу в начале каждой транзакции.
  3. В случае сбоя соединения во время фиксации проверьте наличие соответствующей строки в базе данных.
  4. Если фиксация прошла успешно, удалите соответствующую строку, чтобы избежать роста таблицы.
using (var db = new BloggingContext())
{
    var strategy = db.Database.CreateExecutionStrategy();

    db.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });

    var transaction = new TransactionRow { Id = Guid.NewGuid() };
    db.Transactions.Add(transaction);

    strategy.ExecuteInTransaction(
        db,
        operation: context => { context.SaveChanges(acceptAllChangesOnSuccess: false); },
        verifySucceeded: context => context.Transactions.AsNoTracking().Any(t => t.Id == transaction.Id));

    db.ChangeTracker.AcceptAllChanges();
    db.Transactions.Remove(transaction);
    db.SaveChanges();
}

Примечание

Убедитесь, что контекст, используемый для проверки, имеет стратегию выполнения, определенную, так как соединение, скорее всего, снова завершится сбоем во время проверки, если оно завершилось сбоем во время фиксации транзакции.

Дополнительные ресурсы