@@ -30,7 +30,7 @@ namespace Microsoft.Data.SqlClient
3030 /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/SqlDataReader/*' />
3131 public class SqlDataReader : DbDataReader , IDataReader , IDbColumnSchemaGenerator
3232 {
33- private enum ALTROWSTATUS
33+ internal enum ALTROWSTATUS
3434 {
3535 Null = 0 , // default and after Done
3636 AltRow , // after calling NextResult and the first AltRow is available for read
@@ -97,9 +97,6 @@ internal class SharedState
9797 private SqlSequentialStream _currentStream ;
9898 private SqlSequentialTextReader _currentTextReader ;
9999
100- private IsDBNullAsyncCallContext _cachedIsDBNullContext ;
101- private ReadAsyncCallContext _cachedReadAsyncContext ;
102-
103100 internal SqlDataReader ( SqlCommand command , CommandBehavior behavior )
104101 {
105102 SqlConnection . VerifyExecutePermission ( ) ;
@@ -4387,6 +4384,10 @@ internal TdsOperationStatus TryReadColumnInternal(int i, bool readHeaderOnly/* =
43874384 {
43884385 // reset snapshot to save memory use. We can safely do that here because all SqlDataReader values are stable.
43894386 // The retry logic can use the current values to get back to the right state.
4387+ if ( _connection ? . InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection . CachedDataReaderSnapshot is null )
4388+ {
4389+ sqlInternalConnection . CachedDataReaderSnapshot = _snapshot ;
4390+ }
43904391 _snapshot = null ;
43914392 PrepareAsyncInvocation ( useSnapshot : true ) ;
43924393 }
@@ -5318,7 +5319,15 @@ public override Task<bool> ReadAsync(CancellationToken cancellationToken)
53185319 return source . Task ;
53195320 }
53205321
5321- var context = Interlocked . Exchange ( ref _cachedReadAsyncContext , null ) ?? new ReadAsyncCallContext ( ) ;
5322+ ReadAsyncCallContext context = null ;
5323+ if ( _connection ? . InnerConnection is SqlInternalConnection sqlInternalConnection )
5324+ {
5325+ context = Interlocked . Exchange ( ref sqlInternalConnection . CachedDataReaderReadAsyncContext , null ) ;
5326+ }
5327+ if ( context is null )
5328+ {
5329+ context = new ReadAsyncCallContext ( ) ;
5330+ }
53225331
53235332 Debug . Assert ( context . Reader == null && context . Source == null && context . Disposable == default , "cached ReadAsyncCallContext was not properly disposed" ) ;
53245333
@@ -5358,6 +5367,10 @@ private static Task<bool> ReadAsyncExecute(Task task, object state)
53585367 if ( ! hasReadRowToken )
53595368 {
53605369 hasReadRowToken = true ;
5370+ if ( reader . Connection ? . InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection . CachedDataReaderSnapshot is null )
5371+ {
5372+ sqlInternalConnection . CachedDataReaderSnapshot = reader . _snapshot ;
5373+ }
53615374 reader . _snapshot = null ;
53625375 reader . PrepareAsyncInvocation ( useSnapshot : true ) ;
53635376 }
@@ -5377,7 +5390,10 @@ private static Task<bool> ReadAsyncExecute(Task task, object state)
53775390
53785391 private void SetCachedReadAsyncCallContext ( ReadAsyncCallContext instance )
53795392 {
5380- Interlocked . CompareExchange ( ref _cachedReadAsyncContext , instance , null ) ;
5393+ if ( _connection ? . InnerConnection is SqlInternalConnection sqlInternalConnection )
5394+ {
5395+ Interlocked . CompareExchange ( ref sqlInternalConnection . CachedDataReaderReadAsyncContext , instance , null ) ;
5396+ }
53815397 }
53825398
53835399 /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/IsDBNullAsync/*' />
@@ -5479,7 +5495,15 @@ override public Task<bool> IsDBNullAsync(int i, CancellationToken cancellationTo
54795495 registrationHolder . Set ( cancellationToken . Register ( SqlCommand . s_cancelIgnoreFailure , _command ) ) ;
54805496 }
54815497
5482- IsDBNullAsyncCallContext context = Interlocked . Exchange ( ref _cachedIsDBNullContext , null ) ?? new IsDBNullAsyncCallContext ( ) ;
5498+ IsDBNullAsyncCallContext context = null ;
5499+ if ( _connection ? . InnerConnection is SqlInternalConnection sqlInternalConnection )
5500+ {
5501+ context = Interlocked . Exchange ( ref sqlInternalConnection . CachedDataReaderIsDBNullContext , null ) ;
5502+ }
5503+ if ( context is null )
5504+ {
5505+ context = new IsDBNullAsyncCallContext ( ) ;
5506+ }
54835507
54845508 Debug . Assert ( context . Reader == null && context . Source == null && context . Disposable == default , "cached ISDBNullAsync context not properly disposed" ) ;
54855509
@@ -5517,7 +5541,10 @@ private static Task<bool> IsDBNullAsyncExecute(Task task, object state)
55175541
55185542 private void SetCachedIDBNullAsyncCallContext ( IsDBNullAsyncCallContext instance )
55195543 {
5520- Interlocked . CompareExchange ( ref _cachedIsDBNullContext , instance , null ) ;
5544+ if ( _connection ? . InnerConnection is SqlInternalConnection sqlInternalConnection )
5545+ {
5546+ Interlocked . CompareExchange ( ref sqlInternalConnection . CachedDataReaderIsDBNullContext , instance , null ) ;
5547+ }
55215548 }
55225549
55235550 /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/GetFieldValueAsync/*' />
@@ -6019,7 +6046,7 @@ private void CompleteAsyncCall<T>(Task<T> task, SqlDataReaderBaseAsyncCallContex
60196046 }
60206047 }
60216048
6022- private sealed class Snapshot
6049+ internal sealed class Snapshot
60236050 {
60246051 public bool _dataReady ;
60256052 public bool _haltRead ;
@@ -6051,7 +6078,14 @@ private void PrepareAsyncInvocation(bool useSnapshot)
60516078
60526079 if ( _snapshot == null )
60536080 {
6054- _snapshot = new Snapshot ( ) ;
6081+ if ( _connection ? . InnerConnection is SqlInternalConnection sqlInternalConnection )
6082+ {
6083+ _snapshot = Interlocked . Exchange ( ref sqlInternalConnection . CachedDataReaderSnapshot , null ) ?? new Snapshot ( ) ;
6084+ }
6085+ else
6086+ {
6087+ _snapshot = new Snapshot ( ) ;
6088+ }
60556089
60566090 _snapshot . _dataReady = _sharedState . _dataReady ;
60576091 _snapshot . _haltRead = _haltRead ;
@@ -6124,6 +6158,10 @@ private void CleanupAfterAsyncInvocationInternal(TdsParserStateObject stateObj,
61246158 stateObj . _permitReplayStackTraceToDiffer = false ;
61256159#endif
61266160
6161+ if ( _connection ? . InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection . CachedDataReaderSnapshot is null )
6162+ {
6163+ sqlInternalConnection . CachedDataReaderSnapshot = _snapshot ;
6164+ }
61276165 // We are setting this to null inside the if-statement because stateObj==null means that the reader hasn't been initialized or has been closed (either way _snapshot should already be null)
61286166 _snapshot = null ;
61296167 }
@@ -6162,6 +6200,10 @@ private void SwitchToAsyncWithoutSnapshot()
61626200 Debug . Assert ( _snapshot != null , "Should currently have a snapshot" ) ;
61636201 Debug . Assert ( _stateObj != null && ! _stateObj . _asyncReadWithoutSnapshot , "Already in async without snapshot" ) ;
61646202
6203+ if ( _connection ? . InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection . CachedDataReaderSnapshot is null )
6204+ {
6205+ sqlInternalConnection . CachedDataReaderSnapshot = _snapshot ;
6206+ }
61656207 _snapshot = null ;
61666208 _stateObj . ResetSnapshot ( ) ;
61676209 _stateObj . _asyncReadWithoutSnapshot = true ;
0 commit comments