diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs index 0f71ee82087..eae042a7133 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs @@ -73,10 +73,8 @@ public async Task ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransac new DummyEnlistment(), EnlistmentOptions.None); - if (Sfi.ConnectionProvider.Driver.GetType() == typeof(OdbcDriver)) - connection = new OdbcConnection(connectionString); - else - connection = new SqlConnection(connectionString); + connection = Sfi.ConnectionProvider.Driver.CreateConnection(); + connection.ConnectionString = connectionString; await (connection.OpenAsync()); using (s = Sfi.WithOptions().Connection(connection).OpenSession()) diff --git a/src/NHibernate.Test/ExceptionsTest/MSSQLExceptionConverterExample.cs b/src/NHibernate.Test/ExceptionsTest/MSSQLExceptionConverterExample.cs index 5203c7c737f..3f51f902fad 100644 --- a/src/NHibernate.Test/ExceptionsTest/MSSQLExceptionConverterExample.cs +++ b/src/NHibernate.Test/ExceptionsTest/MSSQLExceptionConverterExample.cs @@ -12,14 +12,22 @@ public class MSSQLExceptionConverterExample : ISQLExceptionConverter public Exception Convert(AdoExceptionContextInfo exInfo) { - SqlException sqle = ADOExceptionHelper.ExtractDbException(exInfo.SqlException) as SqlException; - if(sqle != null) + var dbEx = ADOExceptionHelper.ExtractDbException(exInfo.SqlException); + if (dbEx is SqlException sqle) { if (sqle.Number == 547) return new ConstraintViolationException(exInfo.Message, sqle.InnerException, exInfo.Sql, null); if (sqle.Number == 208) return new SQLGrammarException(exInfo.Message, sqle.InnerException, exInfo.Sql); } + + if(dbEx is Microsoft.Data.SqlClient.SqlException msSqle) + { + if (msSqle.Number == 547) + return new ConstraintViolationException(exInfo.Message, msSqle.InnerException, exInfo.Sql, null); + if (msSqle.Number == 208) + return new SQLGrammarException(exInfo.Message, msSqle.InnerException, exInfo.Sql); + } return SQLStateConverter.HandledNonSpecificException(exInfo.SqlException, exInfo.Message, exInfo.Sql); } diff --git a/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs index 1c80edf6de7..74563346add 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs @@ -62,10 +62,8 @@ public void ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransaction() new DummyEnlistment(), EnlistmentOptions.None); - if (Sfi.ConnectionProvider.Driver.GetType() == typeof(OdbcDriver)) - connection = new OdbcConnection(connectionString); - else - connection = new SqlConnection(connectionString); + connection = Sfi.ConnectionProvider.Driver.CreateConnection(); + connection.ConnectionString = connectionString; connection.Open(); using (s = Sfi.WithOptions().Connection(connection).OpenSession()) diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index c88f2913de6..365e1dcbf99 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -46,6 +46,7 @@ + diff --git a/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs new file mode 100644 index 00000000000..1c4ad3d9fc0 --- /dev/null +++ b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using NHibernate.AdoNet; +using NHibernate.Dialect; +using NHibernate.Engine; +using NHibernate.SqlTypes; +using NHibernate.Util; + +namespace NHibernate.Driver +{ + /// + /// A NHibernate Driver for using the SqlClient DataProvider + /// + public class MicrosoftDataSqlClientDriver : ReflectionBasedDriver, IEmbeddedBatcherFactoryProvider, IParameterAdjuster + { + const byte MaxTime = 5; + + private static readonly Action SetSqlDbType = DelegateHelper.BuildPropertySetter(System.Type.GetType("Microsoft.Data.SqlClient.SqlParameter, Microsoft.Data.SqlClient", true), "SqlDbType"); + + private Dialect.Dialect _dialect; + + public MicrosoftDataSqlClientDriver() + : base( + "Microsoft.Data.SqlClient", + "Microsoft.Data.SqlClient.SqlConnection", + "Microsoft.Data.SqlClient.SqlCommand") + { + } + + /// + /// MsSql requires the use of a Named Prefix in the SQL statement. + /// + /// + /// because MsSql uses "@". + /// + public override bool UseNamedPrefixInSql => true; + + /// + /// MsSql requires the use of a Named Prefix in the Parameter. + /// + /// + /// because MsSql uses "@". + /// + public override bool UseNamedPrefixInParameter => true; + + /// + /// The Named Prefix for parameters. + /// + /// + /// Sql Server uses "@". + /// + public override string NamedPrefix => "@"; + + /// + public override bool SupportsMultipleOpenReaders => false; + + /// + public override bool SupportsMultipleQueries => true; + + /// + /// With read committed snapshot or lower, SQL Server may have not actually already committed the transaction + /// right after the scope disposal. + /// + public override bool HasDelayedDistributedTransactionCompletion => true; + + public override bool RequiresTimeSpanForTime => true; + + /// + public override DateTime MinDate => DateTime.MinValue; + + System.Type IEmbeddedBatcherFactoryProvider.BatcherFactoryClass => typeof(GenericBatchingBatcherFactory); + + /// + public virtual void AdjustParameterForValue(DbParameter parameter, SqlType sqlType, object value) + { + if (value is string stringVal) + switch (parameter.DbType) + { + case DbType.AnsiString: + case DbType.AnsiStringFixedLength: + parameter.Size = IsAnsiText(parameter, sqlType) + ? MsSql2000Dialect.MaxSizeForAnsiClob + : Math.Max(stringVal.Length, sqlType.LengthDefined ? sqlType.Length : parameter.Size); + break; + case DbType.String: + case DbType.StringFixedLength: + parameter.Size = IsText(parameter, sqlType) + ? MsSql2000Dialect.MaxSizeForClob + : Math.Max(stringVal.Length, sqlType.LengthDefined ? sqlType.Length : parameter.Size); + break; + } + } + + public override void Configure(IDictionary settings) + { + base.Configure(settings); + + _dialect = Dialect.Dialect.GetDialect(settings); + } + + /// + protected override void InitializeParameter(DbParameter dbParam, string name, SqlType sqlType) + { + base.InitializeParameter(dbParam, name, sqlType); + + // Defaults size/precision/scale + switch (dbParam.DbType) + { + case DbType.AnsiString: + case DbType.AnsiStringFixedLength: + dbParam.Size = IsAnsiText(dbParam, sqlType) + ? MsSql2000Dialect.MaxSizeForAnsiClob + : MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString; + break; + case DbType.Binary: + dbParam.Size = IsBlob(dbParam, sqlType) + ? MsSql2000Dialect.MaxSizeForBlob + : MsSql2000Dialect.MaxSizeForLengthLimitedBinary; + break; + case DbType.Decimal: + if (_dialect == null) + throw new InvalidOperationException( + "Dialect not available, is this driver used without having been configured?"); + dbParam.Precision = _dialect.DefaultCastPrecision; + dbParam.Scale = _dialect.DefaultCastScale; + break; + case DbType.String: + case DbType.StringFixedLength: + dbParam.Size = IsText(dbParam, sqlType) + ? MsSql2000Dialect.MaxSizeForClob + : MsSql2000Dialect.MaxSizeForLengthLimitedString; + break; + case DbType.DateTime2: + dbParam.Size = MsSql2000Dialect.MaxDateTime2; + break; + case DbType.DateTimeOffset: + dbParam.Size = MsSql2000Dialect.MaxDateTimeOffset; + break; + case DbType.Xml: + dbParam.Size = MsSql2005Dialect.MaxSizeForXml; + break; + } + switch (sqlType.DbType) + { + case DbType.Time: + SetSqlDbType(dbParam, SqlDbType.Time); + dbParam.Size = MaxTime; + break; + case DbType.Date: + SetSqlDbType(dbParam, SqlDbType.Date); + break; + } + + // Do not override the default length for string using data from SqlType, since LIKE expressions needs + // larger columns. https://nhibernate.jira.com/browse/NH-3036 + + if (sqlType.PrecisionDefined) + { + dbParam.Precision = sqlType.Precision; + dbParam.Scale = sqlType.Scale; + } + } + + /// + /// Interprets if a parameter is a Clob (for the purposes of setting its default size) + /// + /// The parameter + /// The of the parameter + /// True, if the parameter should be interpreted as a Clob, otherwise False + protected static bool IsAnsiText(DbParameter dbParam, SqlType sqlType) + { + return (DbType.AnsiString == dbParam.DbType || DbType.AnsiStringFixedLength == dbParam.DbType) && + sqlType.LengthDefined && sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString; + } + + /// + /// Interprets if a parameter is a Clob (for the purposes of setting its default size) + /// + /// The parameter + /// The of the parameter + /// True, if the parameter should be interpreted as a Clob, otherwise False + protected static bool IsText(DbParameter dbParam, SqlType sqlType) + { + return sqlType is StringClobSqlType || + (DbType.String == dbParam.DbType || DbType.StringFixedLength == dbParam.DbType) && + sqlType.LengthDefined && sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedString; + } + + /// + /// Interprets if a parameter is a Blob (for the purposes of setting its default size) + /// + /// The parameter + /// The of the parameter + /// True, if the parameter should be interpreted as a Blob, otherwise False + protected static bool IsBlob(DbParameter dbParam, SqlType sqlType) + { + return sqlType is BinaryBlobSqlType || DbType.Binary == dbParam.DbType && sqlType.LengthDefined && + sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedBinary; + } + + /// + public override IResultSetsCommand GetResultSetsCommand(ISessionImplementor session) + { + return new BasicResultSetsCommand(session); + } + } +}