Session-Per-Request с SqlConnection/System.Transactions

Я только что начал использовать Dapper для проекта, в основном использующий ORM, такие как NHibernate и EF в течение последних нескольких лет.

Обычно в наших веб-приложениях мы реализуем сеанс на запрос, начинаем транзакцию в начале запроса и фиксируем его в конце.

Должны ли мы делать что-то подобное при прямой работе с SqlConnection/System.Transactions?

Как это делает StackOverflow?

Решение

Принимая советы как @gbn, так и @Sam Safron, я не использую транзакции. В моем случае я делаю только запросы на чтение, поэтому, похоже, нет реального требования использовать транзакции (вопреки тому, что мне рассказали о неявных транзакциях).

Я создаю легкий интерфейс сеанса, чтобы я мог использовать соединение для каждого запроса. Это очень полезно для меня, так как с Dapper мне часто нужно создавать несколько разных запросов для создания объекта и, скорее, использовать одно и то же соединение.

Работа по определению местоположения для каждого запроса и его утилизации выполняется моим контейнером IoC (StructureMap):

public interface ISession : IDisposable {
    IDbConnection Connection { get; }
}

public class DbSession : ISession {

    private static readonly object @lock = new object();
    private readonly ILogger logger;
    private readonly string connectionString;
    private IDbConnection cn;

    public DbSession(string connectionString, ILogger logger) {
        this.connectionString = connectionString;
        this.logger = logger;
    }

    public IDbConnection Connection { get { return GetConnection(); } }

    private IDbConnection GetConnection() {
        if (cn == null) {
            lock (@lock) {
                if (cn == null) {
                    logger.Debug("Creating Connection");
                    cn = new SqlConnection(connectionString);
                    cn.Open();
                    logger.Debug("Opened Connection");
                }
            }
        }

        return cn;
    }

    public void Dispose() {
        if (cn != null) {
            logger.Debug("Disposing connection (current state '{0}')", cn.State);
            cn.Dispose();
        }
    }
}

Ответ 1

Это то, что мы делаем:

Мы определяем static, называемый DB для объекта с именем Current

public static DBContext DB
{
    var result = GetContextItem<T>(itemKey);

    if (result == null)
    {
        result = InstantiateDB();
        SetContextItem(itemKey, result);
    }

    return result;
}

public static T GetContextItem<T>(string itemKey, bool strict = true)
{

#if DEBUG // HttpContext is null for unit test calls, which are only done in DEBUG
    if (Context == null)
    {
        var result = CallContext.GetData(itemKey);
        return result != null ? (T)result : default(T);
    }
    else
    {
#endif
        var ctx = HttpContext.Current;
        if (ctx == null)
        {
            if (strict) throw new InvalidOperationException("GetContextItem without a context");
            return default(T);
        }
        else
        {
            var result = ctx.Items[itemKey];
            return result != null ? (T)result : default(T);
        }
#if DEBUG
    }
#endif
}

public static void SetContextItem(string itemKey, object item)
{
#if DEBUG // HttpContext is null for unit test calls, which are only done in DEBUG
    if (Context == null)
    {
        CallContext.SetData(itemKey, item);
    }
    else
    {
#endif
        HttpContext.Current.Items[itemKey] = item;

#if DEBUG
    }
#endif
}

В нашем случае InstantiateDB возвращает контекст L2S, однако в вашем случае это может быть открытый SQLConnection или любой другой.

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

   protected void Application_EndRequest(object sender, EventArgs e)
   {
        Current.DisposeDB(); // closes connection, clears context 
   }

Тогда где-нибудь в вашем коде, где вам нужен доступ к db, вы просто вызываете Current.DB, а материал автоматически работает. Это также дружественно unit test благодаря всем материалам #if DEBUG.


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

Ответ 2

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

См. случайный пример в этом недавнем вопросе: Почему выполняется вложенная транзакция, даже если TransactionScope.Complete() никогда не вызывается?

Вы не должны открывать соединение и запускать транзакцию для каждого запроса HTTP. Только по запросу. Мне трудно понять, почему какой-то народный адвокат открывает транзакцию базы данных за сеанс: явный идиотизм, когда вы смотрите на то, что транзакция с базой данных

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