【问题标题】:Properly disposing a SqlConnection in ASP.NET?在 ASP.NET 中正确处理 SqlConnection?
【发布时间】:2017-10-31 18:28:27
【问题描述】:

我有一个 ASP.NET Web API,它使用 SqlConnection 连接到数据库。我有一个数据访问层类,它有一个包含连接的实例变量。我这样做有几个原因:

  1. 调用代码可以覆盖 DAL 类的构造函数中的连接字符串(例如用于测试代码)
  2. 在某些情况下,API 控制器需要打开 SQL 连接,开始事务,然后在提交(或可能回滚)事务之前调用 DAL 类中的多个方法。因此,关闭和重新打开每个方法的连接将不起作用,因为我必须保持连接打开(甚至将 SqlTransaction 对象保持在范围内 - 我会通过将其设为实例变量来做到这一点)以便在 DAL 调用之间不回滚事务。
  3. 它还简化了代码的可读性,因此您无需到处复制代码来打开 SQL 连接。

当我对我的 API 进行压力测试时,通过每秒向它提供数百个请求,我遇到了 SQL 连接池耗尽问题。进一步调查表明,这似乎是因为 SQL 连接没有被释放。

我确实了解IDisposable 模式,但我不确定在这种情况下如何使用它。这是我的问题:

  1. 使用using 块或try/catch/finally 块都需要在一个方法中创建、使用和完成对象。在上面的示例中,可能需要在多个方法调用中保持事务,这是不可能的。
  2. Microsoft(以及 SO 上的其他帖子)建议不要在对象析构函数中简单地调用 Dispose(),而是建议您执行我在问题 1 中指定的任一选项。
  3. Microsoft 还说您不应该自己实现 IDisposable 来包装另一个管理对象的 Dispose 方法,而是应该“在完成后简单地调用该对象上的 Dispose()”。当控制器完成使用 SQL 连接时,当我不确定 DAL 中的情况时,我将如何在这种情况下执行此操作? (另外,这意味着我必须重构控制器,以便将对 DAL 的每个调用包装在 using 块中,所以它只是把罐子踢到了路上。)

理想的解决方案是能够以某种方式安排在控制器完成处理并将其响应返回给 SqlConnection 对象时调用服务器传送到前端。要“手动”执行此操作,我将不得不违反上面的第 3 点,并在我的 DAL 上创建我自己的 Dispose 方法,该方法又简单地调用 SqlConnection 的 Dispose。这也意味着我必须重构所有控制器中的许多方法,以将所有 DAL 访问包装在 using 块中。当控制器返回时,ASP.NET 似乎不会自动调用Dispose,这就是连接泄漏的原因。

在任何一种情况下,它都会产生更冗长的代码。例如:

// we only need one method call from the DAL, so let's be compact
string someData = new DAL().GetSomeData(someParam);

现在必须写成:

// we have to initialize here to keep the variable from falling out of scope after the using blocks
// we also must provide some value because the using block implies try/catch.
string someData = "";
using (DAL d = new DAL()) {
    someData = d.getSomeData(someParam);
}

推荐的实现方式是什么?

在更通用的层面上,您实际上如何处理必须在方法调用之间持续存在的一次性对象(例如,作为实例变量)?在 try/catch/finallyusing 等结构中使用一次性用品的需求似乎仅限于可以在单个方法中创建和处置对象的情况。

【问题讨论】:

  • 如果你使用 DI 容器 - 你可以让它在请求结束时释放你的资源。虽然我个人更喜欢自己管理连接 - 在需要时打开、做事、关闭。

标签: c# dispose sqlconnection


【解决方案1】:

关于这一段。

在更通用的层面上,您实际上如何处理必须在方法调用之间持续存在的一次性对象(例如,作为实例变量)?在 try/catch/finally 或 using 等结构中使用一次性对象的需求似乎仅限于可以在单个方法中创建和处理对象的情况。

根据经验,负责实例化一次性对象的方法应该是负责处置它的方法,可以将其作为参数传递给从实例化方法调用的其他方法,但是,这些方法,不应该负责处理对象。此规则的例外情况可能是您将要注入的对象实例化到另一个类中,但在这种情况下,此类应实现 IDisposable 并应在使用后由调用者处置。

【讨论】:

  • 这是有道理的,它只是不遵循我正在使用的逻辑模型......正如我上面所说,只要您需要 Web API 创建 SqlConnection 对象并传递它们到 DAL 中,您将失去拥有分离 DAL 的任何好处(例如能够通过简单地替换 DAL 来完全替换 DBMS)。这同样适用于其他场景。 DI 当然有它的位置,但它有时也违反了“组件”的想法,在这种情况下,你有一个类可以提供访问数据库所需的一切......
  • 我说当 API 实例化 SQLConnection 而不是 DAL 时,您违反了您的设计;事实上,API 不应该知道任何关于 SQLConnection 的存在,你应该有一个公共入口点到创建和处理连接的数据库
【解决方案2】:

作为MSDN recommends,你应该使用

using(var cn = new SqlConnection(xx)){ cn.Open(); }

注意:它不会每次都打开一个新连接

SqlConnection 从连接池中绘制一个打开的连接,如果 一个可用。否则,它会建立到一个新的连接 SQL Server 实例。

为了控制您的交易 - 您可以只使用TransactionScope()(尽管它可能需要MSDTC in some case`s


否则,如果使用TransactionScope 不是一个选项,并且您想显式控制您的事务,则唯一的选择是传递连接。因为它是discussed in this POST

您可以通过 2 种方式进行“传递”。手动或使用依赖注入 (DI)。所有 DI 容器都允许您控制实例的生命周期。因此,在您的情况下,它可能是“per request lifetime” 样本可以在this post中找到

【讨论】:

  • 这在大多数情况下都是正确的,但您需要确保连接字符串不会禁用连接池,这不是默认行为,但它可能会发生。
  • 基于这些答案,至少没有 TransactionScope 似乎没有办法做到这一点......这太糟糕了,它似乎破坏了将 DAL 分成一个单独的类的全部意义。一旦 Web API 必须管理创建和释放连接,您就失去了将 DAL 分开的好处(例如,能够简单地通过编写新的 DAL 来替换 DBMS,甚至使用模拟的 DAL 进行测试)。看起来最干净的选择可能是简单地将 DAL 设为一次性,然后在 DAL 的 Web API 中执行usings,即使这违反了“推荐”做法。
  • 您引用的帖子描述了我的确切问题:“有时它有助于提升与您正在从事工作的班级成员的联系,但这可能会造成尴尬的情况 - 并使控制复杂化连接对象的生命周期和处置(因为它通常排除使用 using 语句)。”所以我能做到这一点的唯一方法是使 DAL 一次性并将其包装在每个控制器方法中的 using...
猜你喜欢
  • 1970-01-01
  • 2013-01-29
  • 1970-01-01
  • 2021-12-29
  • 2011-04-12
  • 1970-01-01
  • 2012-01-01
  • 2010-10-11
相关资源
最近更新 更多