使用SqlConnection / System.Transactions实现每个请求的会话管理

15

我刚开始在一个项目中使用Dapper,以前几年大多使用NHibernate和EF等ORM。

通常在我们的Web应用程序中,我们实现每个请求一个会话,从请求开始时启动事务,在请求结束时提交事务。

当直接使用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();
        }
    }
}

这种方法对你仍然有效吗? - mosesfetters
是的,我们仍在生产中使用上述代码,尽管我们已经有一段时间没有更新 Dapper 了。 - Ben Foster
2个回答

10
这是我们的工作内容:
我们在一个叫做Current的对象上定义了一个静态变量DB
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操作,这也非常适合单元测试。


我们不会在每个会话中启动任何事务,如果我们在会话开始时进行更新操作,我们将遇到严重的锁定问题,因为锁定直到会话结束才会释放。


谢谢Sam,我已经在上面发布了我的解决方案。 - Ben Foster

4
只有当您使用事务处理“写入”调用数据库时,才需要像TransactionScope这样的东西来启动SQL Server事务。 在最近的问题中,可以看到一个随机示例: 为什么即使没有调用TransactionScope.Complete(),嵌套事务也会被提交? 不应该在每个HTTP请求中打开连接并启动事务。 只有在需要时才可操作。我很难理解为什么有些人主张在会话中打开数据库事务,因为这纯属愚蠢,当你看看数据库事务是什么时就知道了。
注意:我不反对此模式本身。 我反对不必要的、过长的客户端数据库事务,并调用MSDTC。

你对这篇文章有什么看法?因为我一直到现在都会将我的查询封装在一个事务中 - http://ayende.com/blog/3775/nh-prof-alerts-use-of-implicit-transactions-is-discouraged - Ben Foster
1
@Ben:坦白说,完全胡说八道。有人不理解事务锁定、并发和隔离级别。我与一些C#同事讨论了你的问题,我们都达成了共识。 - gbn
@Ben,我这个模式是在你第一次需要它时打开连接...我们确保在会话结束时关闭它。在理想的情况下,如果您调用dispose db,您可能希望稍早关闭连接,这是可选的。我同意@gbn关于事务的看法,除非需要,否则不要开始事务。同样地...不要仅为显示静态内容而打开db连接。 - Sam Saffron
我已经添加了我的解决方案。我选择不在读取查询中使用事务。我仅在需要时打开连接(而不是在每个请求上),然后在请求的其余部分共享该连接。连接在请求结束时被处理掉。我已经+1,但选择了Sams的答案,因为它更具体地涉及了我的问题,即Dapper和SO的做法。 - Ben Foster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接