使用 SqlConnection / System.Transactions 的每个请求会话

发布于 2024-11-10 17:58:23 字数 1601 浏览 5 评论 0原文

我刚刚开始在一个项目中使用 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();
        }
    }
}

I've just started using Dapper for a project, having mostly used ORMs like NHibernate and EF for the past few years.

Typically in our web applications we implement session per request, beginning a transaction at the start of the request and committing it at the end.

Should we do something similar when working directly with SqlConnection / System.Transactions?

How does StackOverflow do it?

Solution

Taking the advice of both @gbn and @Sam Safron I'm not using transactions. In my case I'm only doing read queries so it seems there is no real requirement to use transactions (contrary to what I've been told about implicit transactions).

I create a lightweight session interface so that I can use a connection per request. This is quite beneficial to me as with Dapper I often need to create a few different queries to build up an object and would rather share the same connection.

The work of scoping the connection per request and disposing it is done by my IoC container (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();
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

放飞的风筝 2024-11-17 17:58:23

这就是我们所做的:

我们在名为 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 内容,这也是单元测试友好的。


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

This is what we do:

We define a static called DB on an object called 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
}

In our case InstantiateDB returns an L2S context, however in your case it could be an open SQLConnection or whatever.

On our application object we ensure that our connection is closed at the end of the request.

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

Then anywhere in your code where you need access to the db you simple call Current.DB and stuff automatically works. This is also unit test friendly due to all the #if DEBUG stuff.


We do not start any transactions per session, if we did and had updates at the beginning of our session, we would get serious locking issues, as the locks would not be released till the end.

红衣飘飘貌似仙 2024-11-17 17:58:23

仅当需要时,才可以使用 TransactionScope 当您通过“写入”调用调用数据库时。

请参阅最近问题中的一个随机示例:为什么即使从未调用 TransactionScope.Complete() 也会提交嵌套事务?

不会为每个http请求打开连接并启动事务。仅按需提供。我很难理解为什么有些人提倡在每个会话中打开一个数据库事务:当您查看数据库事务什么时,这纯粹是白痴。

注意:我本身并不反对这种模式。我反对调用 MSDTC 的不必要的、太长的客户端数据库事务

You'd only start a SQL Server Transaction when you need to with something like TransactionScope when you call the database with a "write" call.

See a random example in this recent question: Why is a nested transaction committed even if TransactionScope.Complete() is never called?

You would not open a connection and start a transaction per http request. Only on demand. I'm having difficulty understanding why some folk advocate opening a database transaction per session: sheer idiocy when you look at what a database transaction is

Note: I'm not against the pattern per se. I am against unnecessary, too long, client-side database transactions that invoke MSDTC

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文