Поделиться через


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

Подключение устойчивость автоматически повторяет команды базы данных сбоем. Эту функцию можно использовать с любой базой данных, указав "стратегию выполнения", которая инкапсулирует логику, необходимую для обнаружения сбоев и повторных команд. Поставщики 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;ConnectRetryCount=0",
            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: настроенная стратегия выполнения "SqlServerRetryingExecutionStrategy" не поддерживает транзакции, инициированные пользователем. Используйте стратегию выполнения, возвращенную '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. Делать (почти) ничего

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

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

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

  1. Dis карта текущего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 избежать изменения состояния сущности Unchanged в случае SaveChanges успешного Blog выполнения. Это позволяет повторить ту же операцию, если фиксация завершается ошибкой, и транзакция откатится.

Вариант 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();

Примечание.

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

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