首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为SqlClient创建包装器

为SqlClient创建包装器
EN

Code Review用户
提问于 2015-05-07 17:12:49
回答 2查看 266关注 0票数 3

我试图找出创建类的最佳方法,这样我就可以使用它连接到Server,并在存在单个错误的情况下回滚所有内容。

现在,我正在使用一个连接作为一个过程。这有时会导致我的代码抛出There is already an open DataReader associated with this Command which must be closed first.

我想为每个事务建立一个新的连接,但是如果我这样做,我不知道我是否可以回滚所有的东西。

如果存在单个错误,则调用sBDCloseConnect(True)并进行回滚并关闭连接。这很有用,因为我通常使用多个sql查询进行大量迭代。

下面是我使用的相关代码,我想知道是否有更好的方法来做到这一点,并避免抛出我前面提到的错误。

最后一点,我不想要像火星这样的解决方案。

测试用例

代码语言:javascript
复制
'' Initialize connection to database
sBDInitConnect()
Dim test1 As Integer = Val(sBDReturnQuery("SELECT 1"))
If test1 < 0 Then
    '' Has thrown an error
    sBDCloseConnect(True)
ElseIf test1 > 1 Then
    Using dt As New DataTable
        Dim test2 As Integer = sBDSelectSQL("SELECT name FROM users WHERE idUser=1", dt)
        If test2 < 0 Then
            '' Has thrown an error
            sBDCloseConnect(True)
        ElseIf test2 > 0 Then
            If Not sBDNonQuery("UPDATE users SET name='only a test' WHERE idUser=1") Then
                sBDCloseConnect(True)
            Else
                If Not sBDNonQuery("UPDATE users SET name2='this field does not exist' WHERE idUser=1") Then
                    sBDCloseConnect(True)
                End If
            End If
        End If
    End Using
End If
'' No error
sBDCloseConnect(False)

代码语言:javascript
复制
Public Partial Class componenteDEFAULT
    Inherits System.Web.UI.UserControl

    Private bdConection As Data.SqlClient.sqlConnection
    Private bdTransaction As Data.SqlClient.sqlTransaction
    Public bdCommand As Data.SqlClient.SqlCommand

    Sub sBDInitConnect()
        Try : bdConection.Close() : Catch : End Try
        Try
            bdConection = Nothing
            bdConection = New Data.SqlClient.SqlConnection(bd.ConnectString)
            bdConection.Open()

            bdTransaction = Nothing
            bdTransaction = bdConection.BeginTransaction(IsolationLevel.ReadCommitted)

            bdCommand = New Data.SqlClient.SqlCommand()
            bdCommand.Transaction = bdTransaction
            bdCommand.Connection = bdConection
            bdCommand.CommandType = CommandType.Text
            bdCommand.CommandTimeout = 80
        Catch ex As Exception
            sErro(ex.Message)
        End Try
    End Sub

    Function sBDSelectSQL(ByVal comando As String, ByRef rs As Data.DataTable) As Integer
        If rs Is Nothing Then : rs = New DataTable : Else : rs.Clear() : End If
        Try
            bdCommand.Parameters.Clear()
            bdCommand.CommandText = PrepareStringToDB(comando)
            Using dr As Data.SqlClient.SqlDataReader = bdCommand.ExecuteReader(CommandBehavior.Default)
                rs.Load(dr)
            End Using
            Return rs.Rows.Count
        Catch ex As Exception
            sErro(PrepareStringToDB(comando) & " - " & ex.Message)
            Return -1
        End Try
    End Function

    Function sBDNonQuery(ByVal comando As String) As Boolean
        Try
            bdCommand.Parameters.Clear()
            bdCommand.CommandText = PrepareStringToDB(comando)
            bdCommand.ExecuteNonQuery()
            Return True
        Catch ex As Exception
            sErro(PrepareStringToDB(comando) & " - " & ex.Message)
            Return False
        End Try
    End Function

    Function sBDReturnQuery(ByVal comando As String) As String
        Try
            bdCommand.Parameters.Clear()
            bdCommand.CommandText = PrepareStringToDB(comando)
            Return bdCommand.ExecuteScalar()
        Catch ex As Exception
            sErro(PrepareStringToDB(comando) & " - " & ex.Message)
            Return "-1"
        End Try
    End Function

    Sub sBDCloseConnect(ByVal erro As Boolean)
        If erro Then
            Try : bdTransaction.Rollback() : Catch : End Try
            Try : bdConection.Close() : Catch : End Try
        Else
            Try : bdTransaction.Commit() : Catch : End Try
            Try : bdConection.Close() : Catch : End Try
        End If
    End Sub
End Class
EN

回答 2

Code Review用户

发布于 2015-09-02 14:00:06

我读了你问题的正文部分,听起来你真的是在为它做数据库的工作,如果可能的话,这总是效率低下的,如果可能的话,不要。特别是当涉及回滚事务时。

在需要执行一系列不同的查询和回滚(如果出了问题)的情况下,创建一个调用所有这些查询的存储过程。

使用SQL数据库来实现它的用途。

SQL Server擅长查询数据和对数据执行操作,它有捕获错误和回滚整个事件的方法。去做吧,从长远来看,这会容易得多。

票数 2
EN

Code Review用户

发布于 2015-09-02 17:11:19

我试图找出创建类的最佳方法,这样我就可以使用它连接到Server,并在存在单个错误的情况下回滚所有内容。

这听起来很像事务的用途。看代码,你已经把那部分写好了.多少有点。

抽象是错误的。我首先想到的是:

代码语言:javascript
复制
Inherits System.Web.UI.UserControl

为什么数据访问逻辑需要与UI控件纠缠在一起?您需要的是封装一个事务--一个“普通”类可以做到这一点,并且它的实现可以避免UI层的代码。

然后是这样的:

代码语言:javascript
复制
Private bdConection As Data.SqlClient.sqlConnection
Private bdTransaction As Data.SqlClient.sqlTransaction
Public bdCommand As Data.SqlClient.SqlCommand

如果第三个字段不像大拇指一样突出:字段不应该是Public;公开的字段会破坏封装,打开代码以解决各种不必要的问题和边缘情况(例如,当事务的中间由外部代码重新分配bdCommand引用时,会发生什么情况?)

一个问题是,SqlConnectionSqlTransactionSqlCommand类型都实现了IDisposable接口,因此应该尽可能短时间,并正确地处理:将一次性引用设置为NOTHING并不会释放它。你的类拥有这些实例,应该对它们的处理负责.它应该发生在Dispose()方法中,它似乎不会被覆盖。

现在,如果你去适当的处理那里的可处理的东西,你会遇到一些新的问题,比如ObjectDisposedException被扔遍了整个地方。其原因是,您正在重用不应该重用的对象。

封装事务的正确抽象是一个工作单元--一个简单的抽象可以如下所示:

代码语言:javascript
复制
Public Interface IUnitOfWork
    Sub Commit()
    Sub Execute(command As SqlCommand)
    Function Select(command As SqlCommand) As DataTable
End Interface

您不需要在那里使用Rollback()方法,因为如果在提交之前释放工作单元,那么就有效地取消了所有挂起的更改。我在这里添加了一个接受Execute对象的SqlCommand方法,因为我不想进入存储库,所以我的想法是公开一个可以在事务的范围内运行命令的方法。

代码语言:javascript
复制
Public Class UnitOfWork Implements IUnitOfWork, IDisposable
    
    Private ReadOnly connection As SqlConnection
    Private ReadOnly transaction As SqlTransaction
    
    Public Sub New(connection As SqlConnection)
        '' assume connection owner already opened the connection (document that!)
        Me.connection = connection
        Me.transaction = Me.connection.BeginTransaction(IsolationLevel.ReadCommitted)
    End Sub

    Public Sub Execute(command As SqlCommand)
        '' assume command owner already set up parameters and cmd text (document that!)
        command.Transaction = Me.Transaction
        command.Connection = Me.connection
        command.ExecuteNonQuery()
    End Sub
    
    Public Function Select(command As SqlCommand) As DataTable
        '' assume command owner already set up parameters and cmd text (document that!)
        command.Transaction = Me.Transaction
        command.Connection = Me.connection
        Using reader = command.ExecuteReader()
            Dim result As DataTable = New DataTable
            result.Load(reader)
            Return result
        End Using
    End Function

    Public Sub Commit()
        Me.Transaction.Commit()
    End Sub

    Public Sub Dispose()
        Me.Transaction.Dispose()
    End Sub

End Class

你可以用这样的方法:

代码语言:javascript
复制
Private Sub DoSomething(connection As SqlConnection)
    Using uow = New UnitOfWork(connection)
        Try
            uow.Execute(GetCommandForFoo())
            uow.Execute(GetCommandForBar())
            uow.Commit()
        Catch exception As SqlException
            '' message? log? whatever...
        End Try
    End Using
End Sub

如果您需要一个新事务,则需要一个新的UnitOfWork实例。但是,如果您做得对,那么无论如何,每个请求都不需要超过一次Commit

其他点

  • 命名并不遵循典型的VB.NET约定。对对象成员(方法等)使用PascalCase,并避免使用神秘前缀(我到处看到的毫无意义的s是什么?)
  • 我不会让sBDNonQuery返回一个Boolean。如果该命令失败,就会出现问题,您无论如何都要回滚:只需让异常冒泡起来即可。
  • Try : bdTransaction.Commit() : Catch : End Try非常、非常、非常糟糕:如果无法提交事务,您需要知道--至少要记录一些有关它的信息。通过吞咽任何可能发生的异常,您将为以后的一些恶梦般的调试做准备。
  • 4在单个方法中,尝试/捕捉块本身就是一种代码嗅觉。
  • 最后一次rerrosErro在哪里?
  • rs是我们用来在VB6/VBA中命名ADODB.Recordset对象实例的方法。这是.NET,它是DataTable。相应地说出名字。
  • 如果您的目标是.NET > 2.0,我强烈建议您研究新的数据访问技术,这些技术抽象出所有的SQL处理和SqlCommand模板。从LINQ开始,然后看看实体框架可以为您做什么.
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/90181

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档