Session-Per-Request с SqlConnection / System.Transactions - PullRequest
14 голосов
/ 01 июня 2011

Я только начал использовать 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();
        }
    }
}

Ответы [ 2 ]

10 голосов
/ 02 июня 2011

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

Мы определяем статику с именем 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 
   }

Тогда в любом месте вашего кода, где вам нужен доступ к БД, вы просто вызываете Current.DB, и все автоматически работает.Это также подходит для модульных тестов из-за всех вещей #if DEBUG.


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

4 голосов
/ 01 июня 2011

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

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

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

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

...