Информация об изменениях

Сообщение Re[3]: Правильный Unit of work от 21.02.2020 12:14

Изменено 21.02.2020 15:24 Danchik

Re[3]: Правильный Unit of work
Здравствуйте, varenikAA, Вы писали:

AA>Здравствуйте, Danchik, Вы писали:


D>>Я вижу бредовый неполный кусок кода, нужен ли он? Без малейшего понятия.

D>>Не придумывайте себе на голову свои фреймворки, атипатерны — вижу generic repository уже освоен.

AA>Спасибо, за ответ, ну в асп-нете же жизненным циклом объектов управляет Microsoft.Extensions.DependencyInjection,

AA> юнит создается на скоуп, как только скоуп покинули, коннекш закрывается автоматом(в диспозе).
AA>И как иначе, если параллельные транзакции в большинстве субд запрещены?

Тоисть на каждый вызов метода контроллера будем стартовать транзакцию или как-то по другому использовать?
Неявный старт транзакции вам потом боком вылезет.
Как вам такой вариант?
using (var tran = GetTransactionScope(db))
{
    /// bla, bla
    tran.Commit();
}


Если Commit не вызывался, значит было исключение. В таком случае на Dispose делаем Rollback, иначе ничего не делаем.
Если все правильно написать можно, например сделать TransactionManager, пускай считает вложенность чтобы методы его использовали по DI

public class TransactionManager : IDisposable
{
    private readonly DbContext _db;
    private int _nesting;
    private IDbContextTransaction _transaction;
 
    public TransactionManager(DbContext db)
    {
        _db = db;
    }
 
    public void Dispose()
    {
        _transaction?.Dispose();
    }
 
    public TransactionScope BeginTransaction()
    {
        if (_nesting == 0) 
            _transaction = _db.Database.BeginTransaction();
 
        var scope = new TransactionScope(this);
        ++_nesting;
        return scope;
    }
 
    private void RollbackInternal()
    {
        if (_transaction != null)
        {
            _transaction.Rollback();
            _transaction.Dispose();
            _transaction = null;
        }
    }
 
    private void LeaveInternal()
    {
        if (--_nesting < 0)
            throw new Exception("Invalid nesting.");
    }
 
    private void CommitInternal()
    {
        if (_transaction == null)
            throw new Exception("Transaction not stared.");
        if (_nesting == 1) 
            _transaction.Commit();
    }
 
    public class TransactionScope : IDisposable
    {
        private readonly TransactionManager _tm;
        private bool _committed;
 
        internal TransactionScope(TransactionManager tm)
        {
            _tm = tm;
        }
 
        public void Dispose()
        {
            if (!_committed)
            {
                // rollback ASAP
                _tm.RollbackInternal();
            }   
            _tm.LeaveInternal();
        }
 
        public void Commit()
        {
            _tm.CommitInternal();
            _committed = true;
        }
    }
}

....


using (var tran1 = tm.BeginTransaction())
{
    using (var tran2 = tm.BeginTransaction())
    {
        using (var tran3 = tm.BeginTransaction())
        {
            throw new Exception("Something happened.");
            tran3.Commit();
        }
 
        tran2.Commit();
    }
 
    tran1.Commit();
}


Думал приблизительно напишу, но вроде как рабочее решение
Re[3]: Правильный Unit of work
Здравствуйте, varenikAA, Вы писали:

AA>Здравствуйте, Danchik, Вы писали:


D>>Я вижу бредовый неполный кусок кода, нужен ли он? Без малейшего понятия.

D>>Не придумывайте себе на голову свои фреймворки, атипатерны — вижу generic repository уже освоен.

AA>Спасибо, за ответ, ну в асп-нете же жизненным циклом объектов управляет Microsoft.Extensions.DependencyInjection,

AA> юнит создается на скоуп, как только скоуп покинули, коннекш закрывается автоматом(в диспозе).
AA>И как иначе, если параллельные транзакции в большинстве субд запрещены?

Тоисть на каждый вызов метода контроллера будем стартовать транзакцию или как-то по другому использовать?
Неявный старт транзакции вам потом боком вылезет.
Как вам такой вариант?
using (var tran = GetTransactionScope(db))
{
    /// bla, bla
    tran.Commit();
}


Если Commit не вызывался, значит было исключение. В таком случае на Dispose делаем Rollback, иначе ничего не делаем.
Если все правильно написать можно, например сделать TransactionManager, пускай считает вложенность чтобы методы его использовали по DI
public class TransactionManager : IDisposable
{
    private readonly DbContext _db;
    private int _nesting;
    private IDbContextTransaction _transaction;

    public TransactionManager(DbContext db)
    {
        _db = db;
    }

    public void Dispose()
    {
        _transaction?.Dispose();
    }

    public TransactionScope BeginTransaction()
    {
        if (_nesting == 0) 
            _transaction = _db.Database.BeginTransaction();

        var scope = new TransactionScope(this);
        ++_nesting;
        return scope;
    }

    private void RollbackInternal()
    {
        if (_transaction != null)
        {
            _transaction.Rollback();
            _transaction.Dispose();
            _transaction = null;
        }
    }

    private void LeaveInternal()
    {
        if (--_nesting < 0)
            throw new Exception("Invalid nesting.");
    }

    private void CommitInternal()
    {
        if (_transaction == null)
            throw new Exception("Transaction not stared. Possible you have forgot to call Commit() in nested scope.");
        if (_nesting == 1) 
            _transaction.Commit();
    }

    public class TransactionScope : IDisposable
    {
        private readonly TransactionManager _tm;
        private bool _committed;

        internal TransactionScope(TransactionManager tm)
        {
            _tm = tm;
        }

        public void Dispose()
        {
            if (!_committed)
            {
                // rollback ASAP
                _tm.RollbackInternal();
            }   
            _tm.LeaveInternal();
        }

        public void Commit()
        {
            _tm.CommitInternal();
            _committed = true;
        }
    }
}
....


using (var tran1 = tm.BeginTransaction())
{
    using (var tran2 = tm.BeginTransaction())
    {
        using (var tran3 = tm.BeginTransaction())
        {
            throw new Exception("Something happened.");
            tran3.Commit();
        }
 
        tran2.Commit();
    }
 
    tran1.Commit();
}


Думал приблизительно напишу, но вроде как рабочее решение