| applyTo | **/ref/**,**/Sql*.cs |
|---|
Microsoft.Data.SqlClient follows strict API design guidelines to ensure:
- Backward compatibility
- Cross-platform consistency
- Industry-standard patterns (ADO.NET)
src/Microsoft.Data.SqlClient/
├── netcore/ref/ # .NET Core/.NET public APIs
│ └── Microsoft.Data.SqlClient.cs
└── netfx/ref/ # .NET Framework public APIs
└── Microsoft.Data.SqlClient.cs
Reference assemblies define the public contract:
- Classes, interfaces, enums
- Public and protected members
- Method signatures (no implementation)
The reference assemblies do not use C# nullable context (#nullable enable). All reference types in ref/ files must be declared as non-nullable (e.g., string not string?), even when the implementation uses nullable annotations. This is a project-wide convention — do not add ? nullable annotations to ref assembly signatures.
When adding or modifying public APIs:
- Update reference assembly in BOTH
netcore/ref/andnetfx/ref/ - Ensure signatures match across platforms
- Add XML documentation
- Consider backward compatibility
- Do not use nullable annotations (
?) in ref assemblies (see above)
// SqlConnection implements:
public class SqlConnection : DbConnection, ICloneable
{
// DbConnection abstract members
public override string ConnectionString { get; set; }
public override string Database { get; }
public override ConnectionState State { get; }
public override void Open();
public override void Close();
protected override DbCommand CreateDbCommand();
// ...
}
// SqlCommand implements:
public class SqlCommand : DbCommand, ICloneable
{
// DbCommand abstract members
public override string CommandText { get; set; }
public override int CommandTimeout { get; set; }
public override CommandType CommandType { get; set; }
protected override DbParameterCollection DbParameterCollection { get; }
// ...
}DbParameter (abstract)
└── SqlParameter
├── ParameterName
├── SqlDbType
├── Value
└── Size, Direction, etc.
// Prefix Sql for SqlClient types
public class SqlConnection { }
public class SqlCommand { }
public class SqlDataReader { }
// Standard suffixes
public class SqlException : Exception { }
public class SqlClientFactory : DbProviderFactory { }
public interface ISqlVector { }// Async methods end in Async
public Task<int> ExecuteNonQueryAsync(CancellationToken token);
public ValueTask<bool> ReadAsync(CancellationToken token);
// Try pattern for parsing
public static bool TryParse(string value, out SqlConnectionEncryptOption result);// Boolean properties use Is/Has prefix when appropriate
public bool IsOpen { get; }
public bool HasRows { get; }
// Collection properties are plural
public SqlParameterCollection Parameters { get; }A breaking change is any modification that causes existing code to:
- Fail to compile
- Behave differently at runtime
- Throw new exceptions
// DO: Add optional parameters with defaults
public void Execute(string sql, int timeout = 30);
// DON'T: Change existing parameter types
public void Execute(string sql); // Existing
public void Execute(ReadOnlySpan<char> sql); // Breaking!
// DO: Add new overloads
public void Execute(ReadOnlySpan<char> sql); // New overload is OK// Step 1: Mark obsolete with warning
[Obsolete("Use NewMethod instead. This will be removed in version X.")]
public void OldMethod() { }
// Step 2: In next major version, mark as error
[Obsolete("Use NewMethod instead.", error: true)]
public void OldMethod() { }
// Step 3: Remove in subsequent major version- Define in
SqlConnectionStringBuilder - Add to connection string parser
- Default to backward-compatible value
- Document in release notes
// Use clear, descriptive names
"Encrypt=Mandatory"
"Trust Server Certificate=True"
"Application Intent=ReadOnly"
// Support common aliases
"Data Source" = "Server" = "Address"
"Initial Catalog" = "Database"Exception
└── SystemException
└── DbException
└── SqlException
├── Errors (SqlErrorCollection)
├── Server
├── Number
└── Class (severity)
// Include meaningful information
throw new ArgumentNullException(nameof(connectionString));
// Use specific exception types
throw new InvalidOperationException("Connection is not open.");
// Include inner exception when wrapping
throw new SqlException("Connection failed.", innerException);All public APIs MUST have XML documentation:
/// <summary>
/// Opens a database connection.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The connection is already open.
/// </exception>
/// <exception cref="SqlException">
/// A connection-level error occurred.
/// </exception>
public override void Open() { }<summary>: Brief description<param>: Parameter descriptions<returns>: Return value description<exception>: Possible exceptions<remarks>: Additional details (optional)
// Define event arguments
public class SqlInfoMessageEventArgs : EventArgs
{
public SqlErrorCollection Errors { get; }
public string Message { get; }
}
// Define delegate (or use EventHandler<T>)
public delegate void SqlInfoMessageEventHandler(
object sender, SqlInfoMessageEventArgs e);
// Define event
public event SqlInfoMessageEventHandler InfoMessage;// Safe event invocation
protected virtual void OnInfoMessage(SqlInfoMessageEventArgs e)
{
InfoMessage?.Invoke(this, e);
}// Provide CancellationToken overload
public Task<int> ExecuteNonQueryAsync();
public Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken);
// Use ValueTask for frequently-synchronous operations
public ValueTask<bool> ReadAsync(CancellationToken token);// Use ConfigureAwait(false) in library code
var result = await command.ExecuteReaderAsync(token)
.ConfigureAwait(false);// Use explicit values for persistence
public enum SqlDbType
{
BigInt = 0,
Binary = 1,
Bit = 2,
// ...
}
// Use [Flags] for bitwise combinations
[Flags]
public enum SqlBulkCopyOptions
{
Default = 0,
KeepIdentity = 1,
CheckConstraints = 2,
// ...
}- Use
structfor small, immutable value types - Use
classfor reference semantics and inheritance
New public APIs should include samples in doc/samples/:
// doc/samples/SqlConnection_Open.cs
public static void Main()
{
using var connection = new SqlConnection(connectionString);
connection.Open();
Console.WriteLine("Connected to: " + connection.Database);
}