Закрытие соединения при использовании Dapper

Необходимо ли закрывать соединение, когда запрос выполняется явным образом, вызывая метод Close или помещая соединение в оператор Using? Приведет ли оставление открытого соединения к повторному использованию соединения и повысит производительность SQL для будущих запросов?

1 ответ

Решение

Я предполагаю, что вы используете последнюю версию Dapper.

В Dapper есть два способа управления соединением:

  • Полностью управляйте собой: здесь вы несете полную ответственность за открытие и закрытие соединения. Это так же, как вы относитесь к соединению при работе с ADO.NET.

  • Разрешить Dapper управлять им: Dapper автоматически открывает соединение (если оно не было открыто) и закрывает его (если оно было открыто Dapper) для вас. Это похоже на DataAdapter.Fill() метод. Я лично не рекомендую этот путь. Это не может быть применимо каждый раз. Вот что говорит Марк Гравелл в одном из комментариев к этому ответу: /questions/14807979/pochemu-dapper-dot-net-ne-otkryivaet-i-ne-zakryivaet-samo-soedinenie/14807990#14807990

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

Конечно, вы можете вызывать несколько запросов по одному соединению. Но соединение должно быть закрыто Close(), Dispose() метод или приложив его в using блок), чтобы избежать утечки ресурса. Закрытие соединения возвращает его в пул соединений. Вовлечение пула соединений повышает производительность по сравнению со стоимостью нового соединения.


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

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

public sealed class DalSession : IDisposable
{
    public DalSession()
    {
        _connection = new OleDbConnection(DalCommon.ConnectionString);
        _connection.Open();
        _unitOfWork = new UnitOfWork(_connection);
    }

    IDbConnection _connection = null;
    UnitOfWork _unitOfWork = null;

    public UnitOfWork UnitOfWork
    {
        get { return _unitOfWork; }
    }

    public void Dispose()
    {
        _unitOfWork.Dispose();
        _connection.Dispose();
    }
}

public sealed class UnitOfWork : IUnitOfWork
{
    internal UnitOfWork(IDbConnection connection)
    {
        _id = Guid.NewGuid();
        _connection = connection;
    }

    IDbConnection _connection = null;
    IDbTransaction _transaction = null;
    Guid _id = Guid.Empty;

    IDbConnection IUnitOfWork.Connection
    {
        get { return _connection; }
    }
    IDbTransaction IUnitOfWork.Transaction
    {
        get { return _transaction; }
    }
    Guid IUnitOfWork.Id
    {
        get { return _id; }
    }

    public void Begin()
    {
        _transaction = _connection.BeginTransaction();
    }

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

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

    public void Dispose()
    {
        if(_transaction != null)
            _transaction.Dispose();
        _transaction = null;
    }
}

interface IUnitOfWork : IDisposable
{
    Guid Id { get; }
    IDbConnection Connection { get; }
    IDbTransaction Transaction { get; }
    void Begin();
    void Commit();
    void Rollback();
}

Теперь ваши репозитории должны каким-то образом принимать этот UnitOfWork. Я выбираю Инъекцию зависимостей с помощью Конструктора.

public sealed class MyRepository
{
    public MyRepository(IUnitOfWork unitOfWork) 
    {
        this.unitOfWork = unitOfWork;
    }

    IUnitOfWork unitOfWork = null;

    //You also need to handle other parameters like 'sql', 'param' ect. This is out of scope of this answer.
    public MyPoco Get()
    {
        return unitOfWork.Connection.Query(sql, param, unitOfWork.Transaction, .......);
    }

    public void Insert(MyPoco poco)
    {
        return unitOfWork.Connection.Execute(sql, param, unitOfWork.Transaction, .........);
    }
}

И тогда вы называете это так:

С транзакцией:

using(DalSession dalSession = new DalSession())
{
    UnitOfWork unitOfWork = dalSession.UnitOfWork;
    unitOfWork.Begin();
    try
    {
        //Your database code here
        MyRepository myRepository = new MyRepository(unitOfWork);
        myRepository.Insert(myPoco);
        //You may create other repositories in similar way in same scope of UoW.

        unitOfWork.Commit();
    }
    catch
    {
        unitOfWork.Rollback();
        throw;
    }
}

Без транзакции:

using(DalSession dalSession = new DalSession())
{
    //Your database code here
    MyRepository myRepository = new MyRepository(dalSession.UnitOfWork);//UoW have no effect here as Begin() is not called.
    myRepository.Insert(myPoco);
}

Таким образом, вместо прямого отображения соединения в вашем коде вызова, вы управляете им в одном месте.

Более подробную информацию о репозитории в приведенном выше коде можно найти здесь.

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

Другие вопросы по тегам