Отключите DbContext.SaveChanges () от создания транзакций в EntityFramework 6 - вложенные транзакции не поддерживаются с помощью npgsql - PullRequest
1 голос
/ 18 июня 2020

Reasoning

Я нахожусь в несколько уродливой ситуации.

Мне нужен DbTransaction, обернутый вокруг «API плагина», к которому потенциальные клиенты и другие люди имеют доступ , что позволяет мне откатить изменения, внесенные в базу данных, если что-то пойдет не так (иначе: случаются исключения).

Раньше я предоставлял один DbContext для этого, но это оказалось сложной задачей для более сложного кода, заполняя изменить трекер на быстрое (что: падение производительности, интенсивное использование памяти и т. д. c.)

Я перешел на новый дизайн, позволяющий запускать несколько DbContexts, но теперь возникла проблема: Entity Framework 6 с НПГ SQL жалуется, вызывается момент DbContext.SaveChanges().

A transaction is already in progress; nested/concurrent transactions aren't supported.

  at Npgsql.NpgsqlConnection.BeginTransaction(IsolationLevel level)
  at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)
  at System.Data.Entity.Infrastructure.Interception.DbConnectionDispatcher.BeginTransaction(DbConnection connection, BeginTransactionInterceptionContext interceptionContext)
  at System.Data.Entity.Core.EntityClient.EntityConnection.BeginDbTransaction(IsolationLevel isolationLevel)

Упрощенный пример кода

// Example. Resides in another assembly
async Task Plugin(DbConnection dbConnection)
{
    using (var cntxt = new Database.Context(dbConnection))
    {
        cntxt.LogMessages.Add("Fancy Log Message");
        await cntxtb.SaveChangesAsync();
    }
}
// Simplified version of what actually happens
void PluginCaller()
{
    using var dbConnection = Database.Context.Connection(usereadonly: false);
    using var dbTransaction = dbConnection.BeginTransaction();
    Plugin(dbConnection).Wait();
}

Теперь возникает вопрос:

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

Примечание:

Я уже пытался использовать TransactionScope для этого, но это дало то же исключение.

Ответы [ 2 ]

1 голос
/ 27 июня 2020

Если вы можете наложить правила на автора плагина (в отличие от работы с неизменяемой зависимостью), потребуйте, чтобы они приобрели существующую транзакцию, как описано здесь :

using (var context = new BloggingContext(conn, contextOwnsConnection: false))
{
    context.Database.UseTransaction(sqlTxn);
    //...
    context.SaveChanges();
}

Возможно, даже обернуть его в служебной функции и передать его в качестве аргумента вместо dbConnection.

async Task Plugin(DbContextGetter getDbContext)
{
    using (var cntxt = getDbContext())
    {
        //....
    }
}

В качестве побочного примечания у меня есть подозрение, что плагин - как только он получит доступ к DbContext или DbConnection - может выполнить команду raw sql. По умолчанию команды заключены в транзакции, , если они не замаскированы под запросы . Не уверен, возможно ли это на самом деле, но это серьезный вектор атаки, который следует учитывать. Чтобы обойти это и в качестве общей гарантии, вы можете установить перехватчик , чтобы перехватить любое событие, в котором отсутствует command.DbTransaction.

0 голосов
/ 23 июня 2020

При использовании транзакции с EntityFramework у вас есть доступ к двум методам:

Commit(), используемым для завершения работы, которую вы проделали внутри своей транзакции.

Rollback(), используется для отмены всех изменений, которые вы сделали внутри транзакции.

Если вы используете DbContext, вы можете сделать что-то вроде этого, чтобы предотвратить любое необработанное исключение, которое может повредить вашу базу данных, и безопасно выполнить откат с помощью транзакция:

using (var transaction = yourDbContext.BeginTransaction())
{
    try
    {
        // Your code here...
        transaction.Commit(); // And you finalize your transaction here
    }
    catch (Exception e)
    {
        // Your potential error handling here
        transaction.Rollback(); // And you rollback all the changes here
    }
}
...