【问题标题】:SqlConnection.Open() Establishing a connection EVERY query?SqlConnection.Open() 每次查询都建立连接?
【发布时间】:2014-04-28 17:40:20
【问题描述】:

在我使用远程数据库的 Winforms 应用程序中,我具有以下功能。 (我还有另外两个类似的函数:一个用于标量查询,失败时返回零,另一个用于更新和插入,失败时返回 false。)

目前,所有数据操作都是通过这三个函数进行的。

它工作正常,但总体而言,请告知我是否最好在启动我的应用程序时建立连接,然后在应用程序被终止时关闭它?还是在其他时间? (同样,它是一个 Windows 窗体应用程序,因此当用户需要长时间的午休时,它可能会停滞不前。)

到目前为止,我没有看到任何不良影响,因为一切似乎都是“眨眼之间”发生的......但是我获取数据的速度是否较慢,或者是否存在任何其他潜在危险,例如内存泄漏?请注意,无论函数如何终止,我都会关闭连接。

Public Function GetData(ByVal Query As String) As DataTable
    Dim Con As New SqlConnection(GlobalConnectionString)
    Dim da As New SqlDataAdapter
    Dim dt As New DataTable
    Try
        Con.Open()
        da = New SqlDataAdapter(Query, Con)
        Con.Close()
        da.Fill(dt)
    Catch ex As Exception
        Debug.Print(Query)
        MsgBox("UNABLE TO RETRIEVE DATA" & vbCrLf & vbCrLf & ex.Message, MsgBoxStyle.Critical, "Unable to retrieve data.")

    End Try
    da.Dispose()
    Con.Close()
    Return dt
End Function

【问题讨论】:

  • 你知道.NET中有一个连接池吗(实际上是在windows中,很多年了),连接会保持打开一段时间以被重用?
  • 我不知道这一点,但您认为这个事实可能会如何影响我的代码?我相信在退出该功能时,SqlConnection 会自动在.net 中作为垃圾收集,不是吗?如果是这样,似乎在第二次(或第 100 次)调用该函数时,它正在创建一个真正的新连接,即使它仍然具有相同的名称,对吧?
  • SqlConnection 应该被垃圾收集,是的,但在内部连接将保持打开状态。所以下次你调用 open 时,它只是从池中获取一个未使用的连接。更多信息:SQL Server Connection Pooling (ADO.NET)。关于您的代码,您可能会发现 Using 方法看起来更好:stackoverflow.com/questions/16903289/…
  • 最佳做法是尽快关闭每个数据连接。这意味着您应该按照第二段中的建议在启动时建立连接并在关闭时关闭它。您可能会发现这个MSDN Best Practices article 很有用;关于使用连接的部分大约在页面下方的 2/3 处。我也同意@Neolisk 关于Using 的看法。
  • @PaulOTron2000 当方法退出时,SqlConnection 不会自动收集...至少不会马上。垃圾收集是不确定的,因此对象可能会徘徊一段时间。依靠垃圾收集来关闭您的连接可能会导致拒绝服务情况,如果您的应用程序的足够实例这样做的时间足够长,以至于您耗尽了数据库服务器支持的连接数,并有效地将自己锁定在数据库之外。这就是为什么连接应该总是作为Finally块的一部分被关闭。

标签: sql sql-server vb.net winforms


【解决方案1】:

这也有例外,但 .Net 中的最佳实践确实要求为大多数查询创建一个全新的连接对象。真的。

要了解原因,首先要了解实际连接到数据库涉及协议协商、身份验证等方面的大量工作。 这并不便宜。为了解决这个问题,ADO.Net 提供了一个内置的连接池功能。大多数平台都利用这一点来帮助保持连接高效。代码中使用的实际SqlConnectionMySqlConnection 或类似对象的重量相对较轻。当您尝试重用该对象时,您正在以牺牲更大的东西(实际的底层连接资源)为代价来优化小东西(包装器)。

除了连接池带来的好处之外,使用新的连接对象还可以让您的应用更轻松地扩展到多个线程。想象一下编写一个尝试依赖单个全局连接对象的应用程序。稍后,您构建了一个进程,该进程希望生成单独的线程以在后台处理长时间运行的任务,却发现您的连接被阻止,或者本身阻止了对数据库的其他正常访问。更糟糕的是,想象一下尝试为 Web 应用程序执行此操作,结果出错,以至于整个应用程序域(站点的所有用户)共享单个连接。这是我亲眼目睹的真实情况。

所以这是您现有代码正确的做法。
但是,现有方法存在两个严重问题。

首先是作者似乎不明白什么时候打开和什么时候关闭一个连接。使用.Fill() 方法会使这复杂化,因为this method will open and close your connection all on its own.1 使用此方法时,没有充分的理由看到对.Open().Close().Dispose() 的单个调用anywhere 在那个方法中。当不使用.Fill() 方法时,应始终将连接作为Finally 块的一部分关闭:最简单的方法是使用Using 块。

第二个是SQL注入。所写的方法不允许在查询中包含参数数据。它只允许一个完整的 SQL 命令字符串。这实际上迫使您编写极易受到 SQL 注入攻击的代码。如果您还不知道什么是 SQL 注入攻击,请停止您正在做的任何其他事情,并花一些时间在 Google 上搜索该短语。

让我为您推荐一种替代方法来解决这些问题:

Public Function GetData(ByVal Query As String, ParamArray parameters() As SqlParameter) As DataTable
    Dim result As New DataTable()

    Using Con As New SqlConnection(GlobalConnectionString), _
          Cmd As New SqlCommand(Query, Con), 
          da As New SqlDataAdpapter(Cmd)

        If parameters IsNot Nothing Then Cmd.Parameters.AddRange(parameters)

        Try
            da.Fill(result)
        Catch ex As Exception
            Debug.Print(Query)
            'Better to allow higher-level method to handle presenting the error to the user
            'Just log it here and Rethrow so presentation tier can catch
            Throw 
        End Try
    End Using 'guarantees connection is not left hanging open
    Return result
End Function

1参见“备注”部分的第一段。

【讨论】:

  • 好东西,非常感谢乔尔!就上下文而言,我曾经是一名“真正的”开发人员。 (或者至少我能够诱骗人们付钱让我这样做。)我使用 VB3 到 VB6。 (我老了。)我一直是前端人员......我制作了信息亭以在触摸屏上展示信息。我的工作主要是让东西变得漂亮。我只学习了基本的 SQL 查询和图形,让其他人建立连接。今天我发现我的技能很有用,因为我正在为我在不相关领域拥有的企业做一些事情。希望把它卖给竞争对手。
【解决方案2】:

这不是我自己问题的真正“答案”,但我有一些东西要添加,我想添加一些代码。

致 Joe:谢谢,我的代码几乎完全可以使用参数化查询。虽然我知道什么是 SQL 注入攻击,而且它们非常重要,但这是我的借口:过去我使用存储过程进行参数化查询,我有点讨厌写这些,第一年我的代码将是仅由我的 5 名家庭成员的小公司内部使用...如果我出售该软件,我计划稍后将所有内容切换到存储过程。这种方法更好,我可能根本不需要存储过程。

我特别喜欢参数化查询如何优雅地处理日期,因为我不必将日期转换为适当的文本。容易得多。

我看到的另一个优势:有时“保存按钮”必须执行插入或更新,具体取决于显示的记录是否是新的。使用参数允许我编写两个备用的简短基本查询,但可以使用相同的参数来减少代码。

总的来说,这意味着查询字符串的代码密集型构造要少得多。

我没有的部分是分配参数数组,调用过程,所以我在这里包含一个示例,希望其他人觉得它有用:

Dim query As String = "Select Phone from Employees where EmpNo = @EmployeeNumber and Age = @Age"
Dim params As SqlParameter() = {
New SqlParameter("@EmployeeNumber", txtEmployeeNumber.Value),
New SqlParameter("@Age", txtAge.Value)
}
Dim Phone as String = GetData(query, params).Rows.Item(0)

【讨论】:

    猜你喜欢
    • 2018-09-04
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    • 2016-11-18
    • 2016-02-08
    • 1970-01-01
    • 1970-01-01
    • 2013-08-15
    相关资源
    最近更新 更多