考虑一下如下两个问题:
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”. 大功告成!