代码改变世界

Trick: 巧用.NET Reflector, SOS Debugging找出和某一个TransactionScope绑定的SqlConnection objects以及SqlConnection中开着的SqlDataReader objects (Find all SqlConnection objects associated with a TransactionScope)

2009-06-10 22:32  Jialiang  阅读(2016)  评论(2编辑  收藏  举报

考虑一下如下两个问题:

Q1: How to find the live SqlDataReader from a SqlConnection object.
Q2: How to find all SqlConnection objects associated with a TransactionScope, and then find the live SqlDataReader from the SqlConnection objects.

Q1 相对容易。你可以使用.NET Reflection轻松解决。具体参见我的这篇blog:
Trick: 巧用.NET Reflection从SqlConnection回溯到打开着的SqlDataReader。(Find the live SqlDataReader from SqlConnection)

Q2 难在TransactionScope 或Transaction中不含任何public/internal members 阐明和当前TransactionScope相关联的SqlConnection 对象集合。Q1中的解决方案在Q2就不起作用了。

要解决Q2,你需要司马缸砸光的思维:

基本的想法是使用SOS debugging dump出managed heap中所有的SqlConnection对象,然后逐一检查他们是否和某一个Transaction对象相关联。

首先,我们按照这篇文章在Visual Studio里配置并加载SOS。Configure完以后,我们打印出所有SqlConnection对象:

!DumpHeap -type System.Data.SqlClient.SqlConnection
Address       MT     Size
01e685c0 0447d75c       56    
01e68604 0447ebac       32    
01eabdf8 0447f3c4      112    
01eacf30 0447f6e0       28    
total 4 objects
Statistics:
      MT    Count    TotalSize Class Name
0447f6e0        1           28 System.Data.SqlClient.SqlConnectionPoolGroupProviderInfo
0447ebac        1           32 System.Data.SqlClient.SqlConnectionFactory
0447d75c        1           56 System.Data.SqlClient.SqlConnection
0447f3c4        1          112 System.Data.SqlClient.SqlConnectionString
Total 4 objects

From the output, we see that the method table of System.Data.SqlClient.SqlConnection is 044fd75c, and there is currently only one SqlConnection object in the managed heap (in your case, you may see a lot of SqlConnection objects). Then we get each SqlConnection object’s address:

Address       MT     Size
01e685c0 0447d75c       56
   

然后,打印每一个SqlConnection对象:

!do 01e685c0
Name: System.Data.SqlClient.SqlConnection
MethodTable: 0447d75c
EEClass: 0439c1e4
Size: 56(0x38) bytes
(C:\Windows\assembly\GAC_32\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
63160508  400018a        4        System.Object  0 instance 00000000 __identity
654829d8  40008c3        8 ...ponentModel.ISite  0 instance 00000000 site
6549ccac  40008c4        c ....EventHandlerList  0 instance 00000000 events
63160508  40008c2      108        System.Object  0   static 01eb07a4 EventDisposed
04899008  4000bd6       10 ...hangeEventHandler  0 instance 00000000 _stateChangeEventHandler
048bdc58  400171c       14 ...t.SqlDebugContext  0 instance 00000000 _sdc
631343b8  400171d       30       System.Boolean  1 instance        0 _AsycCommandInProgress
04480e84  400171e       18 ...ent.SqlStatistics  0 instance 00000000 _statistics
631343b8  400171f       31       System.Boolean  1 instance        0 _collectstats
631343b8  4001720       32       System.Boolean  1 instance        0 _fireInfoMessageEventOnUserErrors
0447f410  4001723       1c ...ConnectionOptions  0 instance 01eabdf8 _userConnectionOptions
0447ef50  4001724       20 ...nnectionPoolGroup  0 instance 01eacef4 _poolGroup
0447f54c  4001725       24 ...onnectionInternal  0 instance 01eb3654 _innerConnection
63162b38  4001726       28         System.Int32  1 instance        0 _closeCount
63162b38  4001728       2c         System.Int32  1 instance        1 ObjectID
63160508  400171b      798        System.Object  0   static 01e685f8 EventInfoMessage
0447edbc  4001721      79c ...ConnectionFactory  0   static 01e68604 _connectionFactory
631626b4  4001722      7a0 ...eAccessPermission  0   static 01e90d98 ExecutePermission
63162b38  4001727      880         System.Int32  1   static        1 _objectTypeCount

红颜色标记的是其中的 _innerConnection 对象。通过.NET Reflector,我们可以看到它的类型是DbConnectionInternal。继续dump这个对象:

!do 01eb3654
Name: System.Data.SqlClient.SqlInternalConnectionTds
MethodTable: 0448134c
EEClass: 043b0d04
Size: 140(0x8c) bytes
(C:\Windows\assembly\GAC_32\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
63162b38  4000f7b       1c         System.Int32  1 instance        4 _objectID
631343b8  4000f7e       28       System.Boolean  1 instance        0 _allowSetConnectionString
631343b8  4000f7f       29       System.Boolean  1 instance        1 _hidePassword
04897830  4000f80       20         System.Int32  1 instance        1 _state
6315a09c  4000f81        4 System.WeakReference  0 instance 01eb379c _owningObject
0447f54c  4000f82        8 ...onnectionInternal  0 instance 00000000 _nextPooledObject
0447f088  4000f83        c ....DbConnectionPool  0 instance 01eb12dc _connectionPool
0447ec80  4000f84       10 ...ctionPoolCounters  0 instance 01e68624 _performanceCounters
044821a4  4000f85       14 ...ferenceCollection  0 instance 01ebeda0 _referenceCollection
63162b38  4000f86       24         System.Int32  1 instance        0 _pooledCount
631343b8  4000f87       2a       System.Boolean  1 instance        0 _connectionIsDoomed
631343b8  4000f88       2b       System.Boolean  1 instance        0 _cannotBePooled
631343b8  4000f89       2c       System.Boolean  1 instance        0 _isInStasis
63137f4c  4000f8a       30      System.DateTime  1 instance 01eb3684 _createTime
677dcebc  4000f8b       18 ...tions.Transaction  0 instance 00000000 _enlistedTransaction ‘ In my demo, I did not associate the connection with any transaction, so here it is null. But if it’s associated, you can !do the transaction object, get its internalTransaction object and check whether it’s the same object as the TransactionScope’s transaction object.
63162b38  4000f7a      554         System.Int32  1   static        4 _objectTypeCount
0447f5d4  4000f7c      348 ...teChangeEventArgs  0   static 01eabd38 StateChangeClosed
0447f5d4  4000f7d      34c ...teChangeEventArgs  0   static 01eabd48 StateChangeOpen
0447f3c4  40018b0       38 ...lConnectionString  0 instance 01eabdf8 _connectionOptions
631608ec  40018b1       3c        System.String  0 instance 01eb8af8 _currentDatabase
631608ec  40018b2       40        System.String  0 instance 01eac1c4 _currentDataSource
631343b8  40018b3       54       System.Boolean  1 instance        0 _isEnlistedInTransaction
6316335c  40018b4       44        System.Byte[]  0 instance 00000000 _promotedDTCToken
04483a98  40018b5       48 ...egatedTransaction  0 instance 00000000 _delegatedTransaction
6316335c  40018b6       4c        System.Byte[]  0 instance 00000000 _whereAbouts
677dcebc  40018b7       50 ...tions.Transaction  0 instance 00000000 _contextTransaction
0447f6e0  40018c6       58 ...GroupProviderInfo  0 instance 01eacf30 _poolGroupProviderInfo
044814ac  40018c7       5c ...lClient.TdsParser  0 instance 01eb3fbc _parser
04481c8c  40018c8       60 ...lient.SqlLoginAck  0 instance 01eb968c _loginAck
631343b8  40018c9       55       System.Boolean  1 instance        1 _fConnectionOpen
631343b8  40018ca       56       System.Boolean  1 instance        1 _fResetConnection
631608ec  40018cb       64        System.String  0 instance 01eb8af8 _originalDatabase
631608ec  40018cc       68        System.String  0 instance 00000000 _currentFailoverPartner
631608ec  40018cd       6c        System.String  0 instance 01eb9578 _originalLanguage
631608ec  40018ce       70        System.String  0 instance 01eb9578 _currentLanguage
63162b38  40018cf       80         System.Int32  1 instance     8000 _currentPacketSize
63162b38  40018d0       84         System.Int32  1 instance        0 _asyncCommandCount
631608ec  40018d1       74        System.String  0 instance 01de1198 _instanceName
04480fec  40018d2       78 ...ctionPoolIdentity  0 instance 00000000 _identity
00000000  40018d3       7c                       0 instance 00000000 _preparedCommands

任然请注意上诉dump中红色加亮的部分。_enlistedTransaction 可以帮我们找到与当前SqlConnection相关联的Transaction对象。逆过来想的话,通过这种方法,我们已经可以确定和某一个Transaction相关联的所有SqlConnection对象。

下一步是如何通过这些SqlConnection对象找到他们打开着的SqlDataReader对象:

我们注意到innerConnection 对象里有一个_referenceCollection 成员:

044821a4  4000f85       14 ...ferenceCollection  0 instance 01ebeda0 _referenceCollection

我们把它打印下来

!do 01ebeda0
Name: System.Data.SqlClient.SqlReferenceCollection
MethodTable: 04482150
EEClass: 043b1840
Size: 16(0x10) bytes
(C:\Windows\assembly\GAC_32\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
044821f8  4001034        4 ...CollectionEntry[]  0 instance 01ebedb0 _items
63162b38  4001937        8         System.Int32  1 instance        1 _dataReaderCount

从中我们可以看出和这个connection 对象相关联的DataReader数量是1。打印_items这个成员,可以看到这个DataReader对象:

!da 01ebedb0
Name: System.Data.ProviderBase.DbReferenceCollection+CollectionEntry[]
MethodTable: 044821f8
EEClass: 043b1908
Size: 52(0x34) bytes
Array: Rank 1, Number of elements 5, Type VALUETYPE
Element Methodtable: 044822b4
[0] 01ebedb8
[1] 01ebedc0
[2] 01ebedc8
[3] 01ebedd0
[4] 01ebedd8

_items中总共有5个 CollectionEntries。每一个可能或可能没有指向一个打开着的DataReader。由于上诉地址(01ebedb8, 01ebedc0, etc)所指向的都是value type的值,!do对它们不起作用。我们需要借用Visual Studio的Memory dialog来查看这些value:

0x01ebedb8
01ebede4 00000001

0x01ebedc0
00000000 00000000

0x01ebedc8
00000000 00000000

0x01ebedd0
00000000 00000000

0x01ebedd8
00000000 00000000

很明显,只有第一个entry 是有效的。根据.NET Reflector的反编译结果,entry的struct 如下定义:

    [StructLayout(LayoutKind.Sequential)]
    private struct CollectionEntry
    {
        private int _tag;
        private WeakReference _weak;
    }

01ebede4 是 WeakReference的地址. Dump 这个week reference:

!do 01ebede4
Name: System.WeakReference
MethodTable: 6315a09c
EEClass: 62f1a20c
Size: 16(0x10) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
631631b4  40005b4        4        System.IntPtr  1 instance 002112B4 m_handle
631343b8  40005b5        8       System.Boolean  1 instance        0 m_IsLongReference

GCHandle 是002112B4。为了找到这个GCHandle的目标对象,我们首先得到被GCHandle指向的值:

0x002112B4
01ebed18

这个值就是目标对象的地址:

!do 01ebed18
Name: System.Data.SqlClient.SqlDataReader
MethodTable: 0447e1bc
EEClass: 0439c318
Size: 136(0x88) bytes
(C:\Windows\assembly\GAC_32\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
63160508  400018a        4        System.Object  0 instance 00000000 __identity
044814ac  40017d0       20 ...lClient.TdsParser  0 instance 01eb3fbc _parser
044817d0  40017d1       24 ...ParserStateObject  0 instance 01eb4140 _stateObj
0447db08  40017d2       28 ...Client.SqlCommand  0 instance 01ebe0cc _command
0447d75c  40017d3       2c ...ent.SqlConnection  0 instance 01e685c0 _connection
63162b38  40017d4       58         System.Int32  1 instance     1033 _defaultLCID
631343b8  40017d5       7c       System.Boolean  1 instance        0 _dataReady
631343b8  40017d6       7d       System.Boolean  1 instance        0 _haltRead
631343b8  40017d7       7e       System.Boolean  1 instance        1 _metaDataConsumed
631343b8  40017d8       7f       System.Boolean  1 instance        0 _browseModeInfoConsumed
631343b8  40017d9       80       System.Boolean  1 instance        0 _isClosed
631343b8  40017da       81       System.Boolean  1 instance        1 _isInitialized
631343b8  40017db       82       System.Boolean  1 instance        1 _hasRows
048a1740  40017dc       5c         System.Int32  1 instance        0 _altRowStatus
63162b38  40017dd       60         System.Int32  1 instance       -1 _recordsAffected
63162b38  40017de       64         System.Int32  1 instance       30 _timeoutSeconds
048a13e8  40017df       68         System.Int32  1 instance     2008 _typeSystem
04480e84  40017e0       30 ...ent.SqlStatistics  0 instance 00000000 _statistics
631340bc  40017e1       34      System.Object[]  0 instance 01ebfc80 _data
048bed04  40017e2       38 ...t.SqlStreamingXml  0 instance 00000000 _streamingXml
044822f8  40017e3       3c ...t._SqlMetaDataSet  0 instance 01ebedf4 _metaData
048be270  40017e4       40 ...DataSetCollection  0 instance 00000000 _altMetaDataSetCollection
044838e0  40017e5       44 ...e.FieldNameLookup  0 instance 00000000 _fieldNameLookup
04483bdc  40017e6       6c         System.Int32  1 instance        0 _commandBehavior
63162b38  40017e8       70         System.Int32  1 instance        1 ObjectID
048a0c68  40017e9       48 ...tiPartTableName[]  0 instance 00000000 _tableNames
631608ec  40017ea       4c        System.String  0 instance 00000000 _resetOptionsString
63162b38  40017eb       74         System.Int32  1 instance        0 _nextColumnDataToRead
63162b38  40017ec       78         System.Int32  1 instance        0 _nextColumnHeaderToRead
63162178  40017ed        8         System.Int64  1 instance 0 _columnDataBytesRead
63162178  40017ee       10         System.Int64  1 instance 0 _columnDataBytesRemaining
63162178  40017ef       18         System.Int64  1 instance 0 _columnDataCharsRead
6316151c  40017f0       50        System.Char[]  0 instance 00000000 _columnDataChars
63160a80  40017f1       54     System.Exception  0 instance 00000000 _rowException
63162b38  40017e7      884         System.Int32  1   static        1 _objectTypeCount

啊哈!我们找到了那个SqlDataReader 对象!

打印出这个SqlDataReader对象相关的Command:

0447db08  40017d2       28 ...Client.SqlCommand  0 instance 01ebe0cc _command

!do 01ebe0cc
Name: System.Data.SqlClient.SqlCommand
MethodTable: 0447db08
EEClass: 0439c250
Size: 132(0x84) bytes
(C:\Windows\assembly\GAC_32\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
63160508  400018a        4        System.Object  0 instance 00000000 __identity
654829d8  40008c3        8 ...ponentModel.ISite  0 instance 00000000 site
6549ccac  40008c4        c ....EventHandlerList  0 instance 00000000 events
63160508  40008c2      108        System.Object  0   static 01eb07a4 EventDisposed
63162b38  40016e3       58         System.Int32  1 instance        1 ObjectID
631608ec  40016e4       10        System.String  0 instance 01e6851c _commandText
0447e654  40016e5       5c         System.Int32  1 instance        0 _commandType
63162b38  40016e6       60         System.Int32  1 instance       30 _commandTimeout
04899120  40016e7       64         System.Int32  1 instance        3 _updatedRowSource
631343b8  40016e8       78       System.Boolean  1 instance        0 _designTimeInvisible
048be560  40016e9       14 ...ent.SqlDependency  0 instance 00000000 _sqlDep
631343b8  40016ea       79       System.Boolean  1 instance        0 _inPrepare
63162b38  40016eb       68         System.Int32  1 instance       -1 _prepareHandle
631343b8  40016ec       7a       System.Boolean  1 instance        0 _hiddenPrepare
0447e870  40016ed       18 ...rameterCollection  0 instance 00000000 _parameters
0447d75c  40016ee       1c ...ent.SqlConnection  0 instance 01e685c0 _activeConnection
631343b8  40016ef       7b       System.Boolean  1 instance        0 _dirty
048a0f28  40016f0       6c         System.Int32  1 instance        0 _execType
631340bc  40016f1       20      System.Object[]  0 instance 00000000 _rpcArrayOf1
044822f8  40016f2       24 ...t._SqlMetaDataSet  0 instance 01ebedf4 _cachedMetaData
04481e50  40016f3       28 ...+CachedAsyncState  0 instance 01ebe74c _cachedAsyncState
63162b38  40016f4       70         System.Int32  1 instance       -1 _rowsAffected
048bcd3c  40016f5       2c ...tificationRequest  0 instance 00000000 _notification
631343b8  40016f6       7c       System.Boolean  1 instance        1 _notificationAutoEnlist
048bd310  40016f7       30 ...nt.SqlTransaction  0 instance 00000000 _transaction
04898bac  40016f8       34 ...letedEventHandler  0 instance 00000000 _statementCompletedEventHandler
044817d0  40016f9       38 ...ParserStateObject  0 instance 00000000 _stateObj
631343b8  40016fa       7d       System.Boolean  1 instance        0 _pendingCancel
631343b8  40016fb       7e       System.Boolean  1 instance        0 _batchRPCMode
00000000  40016fc       3c                       0 instance 00000000 _RPCList
631340bc  40016fd       40      System.Object[]  0 instance 00000000 _SqlRPCBatchArray
00000000  40016fe       44                       0 instance 00000000 _parameterCollectionList
63162b38  40016ff       74         System.Int32  1 instance        0 _currentlyExecutingBatch
048b2b34  4001700       48 ...miRequestExecutor  0 instance 00000000 _smiRequest
048b267c  4001701       4c ...Server.SmiContext  0 instance 00000000 _smiRequestContext
048bd9c8  4001702       50 ...+CommandEventSink  0 instance 00000000 _smiEventSink
048b27d0  4001703       54 ...DeferedProcessing  0 instance 00000000 _outParamEventSink
63162b38  40016e2      878         System.Int32  1   static        1 _objectTypeCount
631340bc  4001704      78c      System.Object[]  0   static 01ebe48c PreKatmaiProcParamsNames
631340bc  4001705      790      System.Object[]  0   static 01ebe4d8 KatmaiProcParamsNames

得到command text:

!do 01e6851c
Name: System.String
MethodTable: 631608ec
EEClass: 62f1d64c
Size: 92(0x5c) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: SELECT PersonID, LastName FROM Person
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
63162b38  4000096        4         System.Int32  1 instance       38 m_arrayLength
63162b38  4000097        8         System.Int32  1 instance       37 m_stringLength
631615cc  4000098        c          System.Char  1 instance       53 m_firstChar
631608ec  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  003210d0:01de1198 <<
6316151c  400009a       14        System.Char[]  0   shared   static WhitespaceChars
>> Domain:Value  003210d0:01de1758 <<

Command text 是“SELECT PersonID, LastName FROM Person”. 大功告成!