Skip to content

Commit

Permalink
Switch to Microsoft.Data.SqlClient in http/worker host and Dibix.Test…
Browse files Browse the repository at this point in the history
…ing [Pt. II]
  • Loading branch information
C0nquistadore committed Jan 18, 2025
1 parent e233b73 commit 4c2a5c6
Show file tree
Hide file tree
Showing 21 changed files with 258 additions and 178 deletions.
85 changes: 85 additions & 0 deletions shared/Data/MicrosoftSqlClientAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.Server;

namespace Dibix
{
public sealed class MicrosoftSqlClientAdapter : SqlClientAdapter
{
private readonly SqlConnection? _sqlConnection;

public override bool IsSqlClient => _sqlConnection != null;

public MicrosoftSqlClientAdapter(DbConnection connection)
{
if (connection is not SqlConnection sqlConnection)
return;

_sqlConnection = sqlConnection;
sqlConnection.InfoMessage += OnInfoMessage;
}

public override void DetachInfoMessageHandler()
{
if (_sqlConnection != null)
_sqlConnection.InfoMessage -= OnInfoMessage;
}

public override int? TryGetSqlExceptionNumber(Exception exception) => exception is SqlException sqlException ? sqlException.Number : null;

protected override object GetStructuredTypeParameterValue(StructuredType type)
{
SqlDataRecord[] records = MapRecords(type).ToArray();
return records;
}

protected override void SetProviderSpecificParameterProperties(IDbDataParameter parameter, SqlDbType sqlDbType, string typeName)
{
if (parameter is not SqlParameter sqlParam)
return;

sqlParam.SqlDbType = sqlDbType;
sqlParam.TypeName = typeName;
}

private static IEnumerable<SqlDataRecord> MapRecords(StructuredType type)
{
SqlMetaData[] metadata = MapMetadata(type).ToArray();

foreach (Microsoft.SqlServer.Server.SqlDataRecord oldRecord in type.GetRecords())
{
SqlDataRecord newRecord = new SqlDataRecord(metadata);
for (int i = 0; i < oldRecord.FieldCount; i++)
newRecord.SetValue(i, oldRecord.GetValue(i));

yield return newRecord;
}
}

private void OnInfoMessage(object sender, SqlInfoMessageEventArgs e) => OnInfoMessage(e.Message);

private static IEnumerable<SqlMetaData> MapMetadata(StructuredType type)
{
foreach (Microsoft.SqlServer.Server.SqlMetaData oldMetadata in type.GetMetadata())
{
yield return new SqlMetaData
(
name: oldMetadata.Name
, dbType: oldMetadata.SqlDbType
, maxLength: oldMetadata.MaxLength
, precision: oldMetadata.Precision
, scale: oldMetadata.Scale
, locale: default
, compareOptions: default
, userDefinedType: default
);
}
}
}
}
#nullable restore
56 changes: 0 additions & 56 deletions shared/Data/MicrosoftSqlDataRecordAdapter.cs

This file was deleted.

2 changes: 1 addition & 1 deletion shared/Hosting/Data/ScopedDatabaseAccessorFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ private sealed class LoggingDapperDatabaseAccessor : DapperDatabaseAccessor
{
private readonly ILogger _logger;

public LoggingDapperDatabaseAccessor(DbConnection connection, ILogger logger) : base(connection, sqlDataRecordAdapter: new MicrosoftSqlDataRecordAdapter())
public LoggingDapperDatabaseAccessor(DbConnection connection, ILogger logger) : base(connection, sqlClientAdapter: new MicrosoftSqlClientAdapter(connection))
{
_logger = logger;
}
Expand Down
10 changes: 4 additions & 6 deletions src/Dibix.Dapper/DapperDatabaseAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ public class DapperDatabaseAccessor : DatabaseAccessor, IDatabaseAccessor, IDisp
private readonly IDbTransaction _defaultTransaction;
private readonly int? _defaultCommandTimeout;
private readonly Action _onDispose;
private readonly SqlDataRecordAdapter _sqlDataRecordAdapter;
#endregion

#region Constructor
public DapperDatabaseAccessor(DbConnection connection, IDbTransaction defaultTransaction = null, int? defaultCommandTimeout = null, Action onDispose = null, SqlDataRecordAdapter sqlDataRecordAdapter = null) : base(connection)
public DapperDatabaseAccessor(DbConnection connection, IDbTransaction defaultTransaction = null, int? defaultCommandTimeout = null, Action onDispose = null, SqlClientAdapter sqlClientAdapter = null) : base(connection, sqlClientAdapter)
{
_defaultTransaction = defaultTransaction;
_defaultCommandTimeout = defaultCommandTimeout;
_onDispose = onDispose;
_sqlDataRecordAdapter = sqlDataRecordAdapter ?? new SystemSqlClientDataRecordAdapter();
ConfigureDapper();
}
#endregion
Expand Down Expand Up @@ -76,13 +74,13 @@ protected override Task<IEnumerable<TReturn>> QueryManyAsync<TReturn>(string com
protected override IMultipleResultReader QueryMultiple(string commandText, CommandType commandType, ParametersVisitor parameters)
{
SqlMapper.GridReader reader = base.Connection.QueryMultiple(commandText, CollectParameters(parameters), _defaultTransaction, commandTimeout: _defaultCommandTimeout, commandType: commandType);
return new DapperGridResultReader(reader, commandText, commandType, parameters);
return new DapperGridResultReader(reader, commandText, commandType, parameters, SqlClientAdapter);
}

protected override async Task<IMultipleResultReader> QueryMultipleAsync(string commandText, CommandType commandType, ParametersVisitor parameters, CancellationToken cancellationToken)
{
SqlMapper.GridReader reader = await base.Connection.QueryMultipleAsync(new CommandDefinition(commandText, CollectParameters(parameters), _defaultTransaction, _defaultCommandTimeout, commandType, cancellationToken: cancellationToken)).ConfigureAwait(false);
return new DapperGridResultReader(reader, commandText, commandType, parameters);
return new DapperGridResultReader(reader, commandText, commandType, parameters, SqlClientAdapter);
}

protected override void DisposeConnection()
Expand Down Expand Up @@ -115,7 +113,7 @@ protected object CollectParameters(ParametersVisitor parametersVisitor)
private object NormalizeParameterValue(object value)
{
if (value is StructuredType udt)
return new DapperStructuredTypeParameter(udt, _sqlDataRecordAdapter);
return new DapperStructuredTypeParameter(udt, SqlClientAdapter);

return value;
}
Expand Down
3 changes: 1 addition & 2 deletions src/Dibix.Dapper/DapperGridResultReader.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;
using Dapper;

Expand All @@ -18,7 +17,7 @@ internal sealed class DapperGridResultReader : MultipleResultReader, IMultipleRe
#endregion

#region Constructor
public DapperGridResultReader(SqlMapper.GridReader reader, string commandText, CommandType commandType, ParametersVisitor parameters) : base(commandText, commandType, parameters, isSqlClient: reader.Command is SqlCommand)
public DapperGridResultReader(SqlMapper.GridReader reader, string commandText, CommandType commandType, ParametersVisitor parameters, SqlClientAdapter sqlClientAdapter) : base(commandText, commandType, parameters, sqlClientAdapter)
{
_reader = reader;
}
Expand Down
8 changes: 4 additions & 4 deletions src/Dibix.Dapper/DapperStructuredTypeParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ internal sealed class DapperStructuredTypeParameter : SqlMapper.ICustomQueryPara
{
#region Fields
private readonly StructuredType _udt;
private readonly SqlDataRecordAdapter _sqlDataRecordAdapter;
private readonly SqlClientAdapter _sqlClientAdapter;
#endregion

#region Constructor
public DapperStructuredTypeParameter(StructuredType udt, SqlDataRecordAdapter sqlDataRecordAdapter)
public DapperStructuredTypeParameter(StructuredType udt, SqlClientAdapter sqlClientAdapter)
{
_udt = udt;
_sqlDataRecordAdapter = sqlDataRecordAdapter;
_sqlClientAdapter = sqlClientAdapter;
}
#endregion

Expand All @@ -31,7 +31,7 @@ private IDbDataParameter ToSqlParameter(IDbCommand command, string parameterName
{
IDbDataParameter param = command.CreateParameter();
param.ParameterName = parameterName;
_sqlDataRecordAdapter.MapStructuredTypeToParameter(param, _udt);
_sqlClientAdapter.MapStructuredTypeToParameter(param, _udt);

return param;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Dibix.Http.Host/Dibix.Http.Host.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="..\..\shared\Data\MicrosoftSqlDataRecordAdapter.cs" Link="Data\%(Filename)%(Extension)" />
<Compile Include="..\..\shared\Data\MicrosoftSqlClientAdapter.cs" Link="Data\%(Filename)%(Extension)" />
<Compile Include="..\..\shared\Diagnostics\Guard.cs" Link="Diagnostics\%(Filename)%(Extension)" />
<Compile Include="..\..\shared\Extensions\BindConfigurationExtensions.cs" Link="Extensions\%(Filename)%(Extension)" />
<Compile Include="..\..\shared\Extensions\EnumerableExtensions.cs" Link="Extensions\%(Filename)%(Extension)" />
Expand Down
15 changes: 5 additions & 10 deletions src/Dibix.Http.Server/Runtime/SqlHttpStatusCodeParser.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Net;
using System.Text.RegularExpressions;

Expand All @@ -12,19 +11,15 @@ public static bool TryParse(DatabaseAccessException databaseAccessException, out
{
return TryParse(databaseAccessException, action: null, arguments: new Dictionary<string, object>(), out httpException);
}
internal static bool TryParse(DatabaseAccessException databaseAccessException, HttpActionDefinition action, IDictionary<string, object> arguments, out HttpRequestExecutionException httpException)
internal static bool TryParse(DatabaseAccessException exception, HttpActionDefinition action, IDictionary<string, object> arguments, out HttpRequestExecutionException httpException)
{
return TryParse(databaseAccessException, databaseAccessException.InnerException as SqlException, action, arguments, out httpException);
}
private static bool TryParse(DatabaseAccessException originalException, SqlException rootException, HttpActionDefinition action, IDictionary<string, object> arguments, out HttpRequestExecutionException httpException)
{
if (rootException != null && HttpErrorResponseUtility.TryParseErrorResponse(rootException.Number, out int statusCode, out int errorCode, out bool isClientError))
if (exception.SqlErrorNumber != null && exception.InnerException != null && HttpErrorResponseUtility.TryParseErrorResponse(exception.SqlErrorNumber.Value, out int statusCode, out int errorCode, out bool isClientError))
{
httpException = new HttpRequestExecutionException((HttpStatusCode)statusCode, errorCode, rootException.Message, isClientError, originalException);
httpException = new HttpRequestExecutionException((HttpStatusCode)statusCode, errorCode, exception.InnerException.Message, isClientError, exception);
return true;
}

if (HttpStatusCodeDetectionMap.TryGetStatusCode(originalException.AdditionalErrorCode, out HttpErrorResponse defaultResponse))
if (HttpStatusCodeDetectionMap.TryGetStatusCode(exception.AdditionalErrorCode, out HttpErrorResponse defaultResponse))
{
HttpErrorResponse error = defaultResponse;
if (action != null && action.StatusCodeDetectionResponses.TryGetValue(error.StatusCode, out HttpErrorResponse userResponse))
Expand All @@ -37,7 +32,7 @@ private static bool TryParse(DatabaseAccessException originalException, SqlExcep
string parameterName = x.Groups["ParameterName"].Value;
return caseInsensitiveArguments.TryGetValue(parameterName, out object value) ? value?.ToString() : x.Value;
});
httpException = new HttpRequestExecutionException((HttpStatusCode)error.StatusCode, error.ErrorCode, formattedErrorMessage, isClientError, originalException);
httpException = new HttpRequestExecutionException((HttpStatusCode)error.StatusCode, error.ErrorCode, formattedErrorMessage, isClientError, exception);
return true;
}

Expand Down
35 changes: 27 additions & 8 deletions src/Dibix.Testing/Data/DatabaseTestUtility.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
using System;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Dapper;
using Microsoft.Data.SqlClient;

namespace Dibix.Testing.Data
{
Expand Down Expand Up @@ -37,35 +34,38 @@ private sealed class DapperDatabaseAccessorFactory : IDisposableDatabaseAccessor
{
private readonly RaiseErrorWithNoWaitBehavior _raiseErrorWithNoWaitBehavior;
private readonly int? _defaultCommandTimeout;
private readonly Lazy<SqlConnection> _connectionAccessor;
private readonly Lazy<DbConnection> _connectionAccessor;

public DapperDatabaseAccessorFactory(string connectionString, RaiseErrorWithNoWaitBehavior raiseErrorWithNoWaitBehavior, int? defaultCommandTimeout)
{
_raiseErrorWithNoWaitBehavior = raiseErrorWithNoWaitBehavior;
_defaultCommandTimeout = defaultCommandTimeout;
_connectionAccessor = new Lazy<SqlConnection>(() =>
_connectionAccessor = new Lazy<DbConnection>(() =>
{
SqlConnection connection = new SqlConnection(connectionString);
DbConnection connection = CreateConnection(connectionString);
connection.Open();
return connection;
});
}

public IDatabaseAccessor Create()
{
SqlConnection connection = _connectionAccessor.Value;
DbConnection connection = _connectionAccessor.Value;

/*
if (_raiseErrorWithNoWaitBehavior == RaiseErrorWithNoWaitBehavior.FireInfoMessageEventOnUserErrors)
{
connection.FireInfoMessageEventOnUserErrors = true;
connection.InfoMessage += OnInfoMessage;
}
*/

return new DapperDatabaseAccessor(connection, _raiseErrorWithNoWaitBehavior, _defaultCommandTimeout);
}

// When FireInfoMessageEventOnUserErrors is true, errors will trigger an info message event aswell, without throwing an exception.
// To restore the original behavior for errors, we have to throw ourselves.
/*
private static void OnInfoMessage(object sender, SqlInfoMessageEventArgs e)
{
bool isError = e.Errors.Cast<SqlError>().Aggregate(false, (current, sqlError) => current || sqlError.Class > 10);
Expand All @@ -85,6 +85,16 @@ private static void OnInfoMessage(object sender, SqlInfoMessageEventArgs e)
// Unless they are of a severe exception type.
throw new AccessViolationException(exception.Message, exception);
}
*/

private static DbConnection CreateConnection(string connectionString)
{
#if NETFRAMEWORK
return new System.Data.SqlClient.SqlConnection(connectionString);
#else
return new Microsoft.Data.SqlClient.SqlConnection(connectionString);
#endif
}

void IDisposable.Dispose()
{
Expand All @@ -98,7 +108,7 @@ private sealed class DapperDatabaseAccessor : Dapper.DapperDatabaseAccessor
private readonly RaiseErrorWithNoWaitBehavior _raiseErrorWithNoWaitBehavior;
private readonly int? _defaultCommandTimeout;

public DapperDatabaseAccessor(DbConnection connection, RaiseErrorWithNoWaitBehavior raiseErrorWithNoWaitBehavior, int? defaultCommandTimeout) : base(connection, defaultCommandTimeout: defaultCommandTimeout, sqlDataRecordAdapter: new MicrosoftSqlDataRecordAdapter())
public DapperDatabaseAccessor(DbConnection connection, RaiseErrorWithNoWaitBehavior raiseErrorWithNoWaitBehavior, int? defaultCommandTimeout) : base(connection, defaultCommandTimeout: defaultCommandTimeout, sqlClientAdapter: CreateSqlClientAdapter(connection))
{
_raiseErrorWithNoWaitBehavior = raiseErrorWithNoWaitBehavior;
_defaultCommandTimeout = defaultCommandTimeout;
Expand Down Expand Up @@ -128,6 +138,15 @@ protected override void DisposeConnection()
{
// Will be disposed at the end of the test
}

private static SqlClientAdapter CreateSqlClientAdapter(DbConnection connection)
{
#if NETFRAMEWORK
return new SystemSqlClientAdapter(connection);
#else
return new MicrosoftSqlClientAdapter(connection);
#endif
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Dibix.Testing/Dibix.Testing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="..\..\shared\Data\MicrosoftSqlDataRecordAdapter.cs" Link="Data\%(Filename)%(Extension)" />
<Compile Include="..\..\shared\Data\MicrosoftSqlClientAdapter.cs" Link="Data\%(Filename)%(Extension)" Condition="'$(TargetFramework)' != 'net48'" />
<Compile Include="..\..\shared\Diagnostics\Guard.cs" Link="Diagnostics\%(Filename)%(Extension)" />
<Compile Include="..\..\shared\Extensions\EnumerableExtensions.cs" Link="Utilities\%(Filename)%(Extension)" />
<Compile Include="..\..\shared\Extensions\ReflectionExtensions.cs" Link="Extensions\%(Filename)%(Extension)" />
Expand Down
Loading

0 comments on commit 4c2a5c6

Please sign in to comment.