SqlTransaction需要调用Dispose吗?

11

在SqlTransaction的finally块中需要调用dispose吗?假设开发人员没有使用USING任何地方,只是使用try/catch。

SqlTransaction sqlTrans = con.BeginTransaction();

try
{
     //Do Work
sqlTrans.Commit()
}
catch (Exception ex)
        {

           sqlTrans.Rollback();
        }

 finally
        {
            sqlTrans.Dispose();
            con.Dispose();
        }

最好有它。 - Brian
1
如果您在sqlTrans周围没有使用using,那么明确调用Dispose()不会有任何坏处。 - Cᴏʀʏ
@Cory 在调用 sqlTrans.Dispose() 之前,需要检查 sqlTrans 是否已经被处理? - LCJ
1
@Lijo:不,Dispose()方法应该检查是否已经被处理,如果是,则什么也不做。 - Cᴏʀʏ
@Cory在以下问题的回答中说了相反的话https://dev59.com/IW3Xa4cB1Zd3GeqPe3eS#14438880 - LCJ
3个回答

21
我需要使用 try-finallyusing 语句来处理 SqlTransaction 吗? 这并没有什么坏处。对于每个实现 IDisposable 的类都是如此,否则它不会实现此接口。

通常情况下,垃圾回收器会处理未引用的对象(这并不意味着GC调用dispose,这是不正确的),因此您只需要为非托管资源使用它。但是,因为我也不想在每个其他变量上调用dispose或者到处使用using语句,所以值得查看类的Dispose方法的实际实现。

SqlTransaction.Dispose:

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        SNIHandle target = null;
        RuntimeHelpers.PrepareConstrainedRegions();
        try
        {
            target = SqlInternalConnection.GetBestEffortCleanupTarget(this._connection);
            if (!this.IsZombied && !this.IsYukonPartialZombie)
            {
                this._internalTransaction.Dispose();
            }
        }
        catch (OutOfMemoryException e)
        {
            this._connection.Abort(e);
            throw;
        }
        catch (StackOverflowException e2)
        {
            this._connection.Abort(e2);
            throw;
        }
        catch (ThreadAbortException e3)
        {
            this._connection.Abort(e3);
            SqlInternalConnection.BestEffortCleanup(target);
            throw;
        }
    }
    base.Dispose(disposing);
}
        

我不完全理解这里发生了什么,但我可以说这不仅仅是一个简单的base.Dispose(disposing)。因此,确保SqlTransaction被处理可能是一个好主意。

但是因为SqlConnection.BeginTransaction创建事务,所以也有必要反映这一点:

public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionName)
{
    SqlStatistics statistics = null;
    string a = ADP.IsEmpty(transactionName) ? "None" : transactionName;
    IntPtr intPtr;
    Bid.ScopeEnter(out intPtr, "<sc.SqlConnection.BeginTransaction|API> %d#, iso=%d{ds.IsolationLevel}, transactionName='%ls'\n", this.ObjectID, (int)iso, a);
    SqlTransaction result;
    try
    {
        statistics = SqlStatistics.StartTimer(this.Statistics);
        SqlTransaction sqlTransaction = this.GetOpenConnection().BeginSqlTransaction(iso, transactionName);
        GC.KeepAlive(this);
        result = sqlTransaction;
    }
    finally
    {
        Bid.ScopeLeave(ref intPtr);
        SqlStatistics.StopTimer(statistics);
    }
    return result;
}

作为您可以看到的,当创建一个事务时,GC也会保持连接处于活动状态。它也不持有对事务的引用,因为它只返回它。因此,即使连接已被释放,它可能仍未被处理。这是释放事务的另一个理由。
您还可以查看TransactionScope,它比BeginTransaction更加安全可靠。请查看此问题获取更多信息。

1
我对“每个实现IDisposable接口的类都是如此”的评论持有不同意见。那TableCell控件和其他HTML控件呢?建议不要在它们上面应用“using”。 - LCJ
1
@Lijo:明白了,那个说法太笼统了。只有在需要处理一些非托管资源(比如数据库连接)时,你才需要重写实现或使用“using”语句。控件实现“IDisposable”是因为该控件(例如“UserControl”)可能包含非托管资源,并且将在生命周期结束时进行处理(递归地进入所有子控件)。 - Tim Schmelter
1
一旦 SqlConnection 被释放,它不会自动释放 SqlTransaction 吗? - zig
@Tim,我非常确定该连接保留对创建的Transaction对象的引用,并且我在我的项目中使用该引用。https://dev59.com/anRC5IYBdhLWcg3wFM_W - zig
@Richardissimo: Done :) - Tim Schmelter
显示剩余5条评论

9
通常情况下,每个你创建或获取并拥有的IDisposable对象都需要进行处理。
在特定情况下,有一些例外,但是SqlTransaction不是其中之一。
根据 SqlTransaction.Dispose文档的说明:
释放DbTransaction使用的非托管资源,并可选择释放托管资源。
(我的强调)
由于该文档未说明在提交或回滚时是否会释放这些非托管资源,因此您需要处理此对象。

1

我认为你可以直接关闭连接。我检查了BCL的代码--似乎连接会自动处理其事务--不需要显式地关闭它。


更多细节,例如代码片段,将有助于提高信心。 - AnthonyVO

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