- 
                Notifications
    
You must be signed in to change notification settings  - Fork 317
 
Merge | SqlCommand The Mop Up #3738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…lbak, CancelIgnoreFailure
…cachedMetaData, _reconnectionCompletionSource, _intrnalEndExecuteInitiated, CachingQueryMetadataPostponed, IsProviderRetriable, ProcParamsColIndex, PreSql2008ProcParamsNames, Sql2008ProcParamsNames, MetaData
…d renamed synchronous isSynchronous)
…ctionClosed (rewritten as ?.), NotifyDependency (rewritten as ?.)
…ion with Span<string>)
…and GetParameterCount
…g), GetResetOptionsString (made static, renamed to GetOptionsResetString), GetCommandText
… remove boolean "found" flag and just return directly)
…Notification, and netfx-only SqlNotificationContext
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR consolidates SqlCommand platform-specific code by removing separate SqlCommand.netfx.cs and SqlCommand.netcore.cs files and moving shared implementation into the main SqlCommand.cs file. The changes also create a new SqlCommand.Batch.cs partial file for batch-related functionality and update method signatures for consistency.
Key Changes:
- Eliminated platform-specific SqlCommand partial files (netfx and netcore)
 - Consolidated shared implementation into main SqlCommand.cs
 - Created SqlCommand.Batch.cs for batch RPC operations
 - Renamed parameter 
synchronoustoisSynchronousin WriteEndExecuteEvent calls 
Reviewed Changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description | 
|---|---|
| SqlCommand.cs | Added consolidated implementation including AsyncState, batch fields, and helper methods previously split across platform files | 
| SqlCommand.Batch.cs | New partial containing batch RPC mode methods | 
| SqlCommand.Xml.cs | Updated parameter name from synchronous to isSynchronous | 
| SqlCommand.Scalar.cs | Updated parameter name from synchronous to isSynchronous | 
| SqlCommand.Reader.cs | Updated parameter name and added explicit parameter for GetRPCObject and BuildParamList calls | 
| SqlCommand.NonQuery.cs | Updated parameter name from synchronous to isSynchronous | 
| SqlCommand.Encryption.cs | Added explicit forSpDescribeParameterEncryption parameter to GetRPCObject call | 
| SqlCommand.netfx.cs | File deleted - content moved to shared files | 
| SqlCommand.netcore.cs | File deleted - content moved to shared files | 
| Microsoft.Data.SqlClient.csproj (both) | Removed references to deleted platform files, added reference to SqlCommand.Batch.cs | 
| SqlUtil.cs | Added TODO comment for PendingBeginXXXExists method | 
| return; | ||
| } | ||
| 
               | 
          ||
| // Remove the entry from the caceh since it was inconsistent | 
    
      
    
      Copilot
AI
    
    
    
      Oct 30, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected spelling of 'cache' to 'cache'.
| // Remove the entry from the caceh since it was inconsistent | |
| // Remove the entry from the cache since it was inconsistent | 
| options |= TdsEnums.RPC_PARAM_DEFAULT; | ||
| } | ||
| 
               | 
          ||
| // Detect incorrectly derived type names unchanged yb the caller and fix | 
    
      
    
      Copilot
AI
    
    
    
      Oct 30, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected spelling of 'by' to 'by'.
| // Detect incorrectly derived type names unchanged yb the caller and fix | |
| // Detect incorrectly derived type names unchanged by the caller and fix | 
| sqlParam.Size = paramList.Length; | ||
| sqlParam.Value = paramList; | 
    
      
    
      Copilot
AI
    
    
    
      Oct 30, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines 529-530 swap the order of setting Size and Value properties. This change appears to be incidental to the refactoring and should be called out explicitly if intentional, or reverted if unintentional. The original order was Value then Size.
| sqlParam.Size = paramList.Length; | |
| sqlParam.Value = paramList; | |
| sqlParam.Value = paramList; | |
| sqlParam.Size = paramList.Length; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Theoretically should make no difference what order they go in, but ok
| if (part is not null && 2 <= part.Length) | ||
| { | ||
| if (part[0] == '[' && part[part.Length - 1] == ']') | ||
| { | ||
| // strip outer '[' & ']' | ||
| part = part.Substring(1, part.Length - 2); | ||
| 
               | 
          ||
| // undo quoted "]" from "]]" to "]" | ||
| part = part.Replace("]]", "]"); | ||
| } | 
    
      
    
      Copilot
AI
    
    
    
      Oct 30, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 'if' statements can be combined.
| if (part is not null && 2 <= part.Length) | |
| { | |
| if (part[0] == '[' && part[part.Length - 1] == ']') | |
| { | |
| // strip outer '[' & ']' | |
| part = part.Substring(1, part.Length - 2); | |
| // undo quoted "]" from "]]" to "]" | |
| part = part.Replace("]]", "]"); | |
| } | |
| if (part is not null && part.Length >= 2 && part[0] == '[' && part[part.Length - 1] == ']') | |
| { | |
| // strip outer '[' & ']' | |
| part = part.Substring(1, part.Length - 2); | |
| // undo quoted "]" from "]]" to "]" | |
| part = part.Replace("]]", "]"); | 
| if (!parser.MARSOn) | ||
| { | ||
| if (activeConnection.AsyncCommandInProgress) | ||
| throw SQL.MARSUnsupportedOnConnection(); | ||
| } | 
    
      
    
      Copilot
AI
    
    
    
      Oct 30, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 'if' statements can be combined.
| if (!parser.MARSOn) | |
| { | |
| if (activeConnection.AsyncCommandInProgress) | |
| throw SQL.MARSUnsupportedOnConnection(); | |
| } | |
| if (!parser.MARSOn && activeConnection.AsyncCommandInProgress) | |
| throw SQL.MARSUnsupportedOnConnection(); | 
| { | ||
| Cancel(); | ||
| } | ||
| catch | 
    
      
    
      Copilot
AI
    
    
    
      Oct 30, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic catch clause.
| catch | |
| catch (Exception) | 
| catch (Exception e) | ||
| { | ||
| // Put the state object back in the cache. | ||
| // Do not reset the async state, since this is managed by the user Begin/End, | ||
| // not internally. | ||
| if (ADP.IsCatchableExceptionType(e)) | ||
| { | ||
| ReliablePutStateObject(); | ||
| } | ||
| 
               | 
          ||
| // Check if we have an error we can retry | ||
| bool shouldRetry = e is EnclaveDelegate.RetryableEnclaveQueryExecutionException; | ||
| if (e is SqlException sqlException) | ||
| { | ||
| for (int i = 0; i < sqlException.Errors.Count; i++) | ||
| { | ||
| bool isConversionError = | ||
| sqlException.Errors[i].Number == TdsEnums.TCE_CONVERSION_ERROR_CLIENT_RETRY; | ||
| bool isEnclaveSessionError = | ||
| sqlException.Errors[i].Number == TdsEnums.TCE_ENCLAVE_INVALID_SESSION_HANDLE; | ||
| 
               | 
          ||
| if ((usedCache && isConversionError) || | ||
| (ShouldUseEnclaveBasedWorkflow && isEnclaveSessionError)) | ||
| { | ||
| shouldRetry = true; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| 
               | 
          ||
| if (!shouldRetry) | ||
| { | ||
| // If we cannot retry, reset the async state to make sure we leave is clean. | ||
| CachedAsyncState?.ResetAsyncState(); | ||
| 
               | 
          ||
| try | ||
| { | ||
| _activeConnection.GetOpenTdsConnection().DecrementAsyncCount(); | ||
| globalCompletion.TrySetException(e); | ||
| } | ||
| catch (Exception e2) | ||
| { | ||
| globalCompletion.TrySetException(e2); | ||
| } | ||
| 
               | 
          ||
| return; | ||
| } | ||
| 
               | 
          ||
| // Remove the entry from the caceh since it was inconsistent | ||
| SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); | ||
| InvalidateEnclaveSession(); | ||
| 
               | 
          ||
| // Kick off the retry | ||
| try | ||
| { | ||
| _internalEndExecuteInitiated = false; | ||
| Task<object> retryTask = (Task<object>)retryFunc( | ||
| this, | ||
| behavior, | ||
| /*asyncCallback:*/ null, // @TODO: Is this ever not null? | ||
| stateObject, | ||
| TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), | ||
| /*isRetry:*/ true, | ||
| asyncWrite); | ||
| 
               | 
          ||
| retryTask.ContinueWith( | ||
| static (retryTask, state) => | ||
| { | ||
| TaskCompletionSource<object> completionSource = (TaskCompletionSource<object>)state; | ||
| if (retryTask.IsFaulted) | ||
| { | ||
| completionSource.TrySetException(retryTask.Exception.InnerException); | ||
| } | ||
| else if (retryTask.IsCanceled) | ||
| { | ||
| completionSource.TrySetCanceled(); | ||
| } | ||
| else | ||
| { | ||
| completionSource.TrySetResult(retryTask.Result); | ||
| } | ||
| }, | ||
| state: globalCompletion, | ||
| TaskScheduler.Default); | ||
| } | ||
| catch (Exception e2) | ||
| { | ||
| globalCompletion.TrySetException(e2); | ||
| } | ||
| } | 
    
      
    
      Copilot
AI
    
    
    
      Oct 30, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic catch clause.
| _activeConnection.GetOpenTdsConnection().DecrementAsyncCount(); | ||
| globalCompletion.TrySetException(e); | ||
| } | ||
| catch (Exception e2) | 
    
      
    
      Copilot
AI
    
    
    
      Oct 30, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic catch clause.
| catch (Exception e2) | |
| catch (Exception e2) when (!(e2 is OutOfMemoryException || e2 is StackOverflowException || e2 is ThreadAbortException)) | 
| catch (Exception e2) | ||
| { | ||
| globalCompletion.TrySetException(e2); | ||
| } | 
    
      
    
      Copilot
AI
    
    
    
      Oct 30, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic catch clause.
* fAddSeparation => fAddSeparator * revert order or scale/precision * value => val * FIX BUG numChars GetActualScale => GetActualSize
…e made - will annotate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding annotations for bug fixes
| int actualBytes = parser.GetEncodingCharLength( | ||
| value: s, | ||
| numChars: sqlParam.GetActualScale(), | ||
| numChars: sqlParam.GetActualSize(), | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug was here - number of characters should be Size not Scale
| string s = null; | ||
| if (behavior is CommandBehavior.SchemaOnly or CommandBehavior.KeyInfo) | ||
| 
               | 
          ||
| bool hasKeyInfo = (behavior & CommandBehavior.KeyInfo) == CommandBehavior.KeyInfo; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't confident in using is here since CommandBehavior is a [Flags] enum. And didn't like how the checks were so long and used >once, so I factored them out.
| string s = null; | ||
| 
               | 
          ||
| if (behavior is CommandBehavior.SchemaOnly) | ||
| if ((behavior & CommandBehavior.SchemaOnly) == CommandBehavior.SchemaOnly) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not confident on using is on a [Flags] enum.
| // Ensure that the connection is open and that the parser is in the correct state | ||
| // @TODO: Remove cast when possible. | ||
| SqlInternalConnectionTds tdsConnection = (SqlInternalConnectionTds)_activeConnection.InnerConnection; | ||
| SqlInternalConnectionTds tdsConnection = _activeConnection.InnerConnection as SqlInternalConnectionTds; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Further down we check for null. as operator will give null if it's not the right type, but a straight cast will throw an invalid cast exception. Changed back to as to preserve original behavior.
Description
In the final installment of merging the SqlCommand class, I've mopped up the remaining methods and whatnot into the main SqlCommand partial and a new partial for batch RPC methods (not convinced this is the right idea, but otherwise the main partial becomes way too big, imo). This PR completes the SqlCommand merge (and leaves one class left to merge). As with the other PRs as part of this series, each commit merges a small collection of members. It is recommended to step through the commits one at a time to review.
This PR merges the following members:
Issues
More work towards #1261
Testing
CI should validate 😬