From 40c67e4396e55436eb3db0b830c76519ba2d9d0b Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Fri, 16 Aug 2019 16:23:41 +1200 Subject: [PATCH 1/5] Add a driver to support Microsoft.Data.SqlClient provider --- .../Driver/MicrosoftDataSqlClientDriver.cs | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs diff --git a/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs new file mode 100644 index 00000000000..7eff669e15c --- /dev/null +++ b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs @@ -0,0 +1,201 @@ +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; + +namespace NHibernate.Driver +{ + /// + /// A NHibernate Driver for using the SqlClient DataProvider + /// + public class MicrosoftDataSqlClientDriver : ReflectionBasedDriver, IEmbeddedBatcherFactoryProvider, IParameterAdjuster + { + 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 => "@"; + + /// + /// The SqlClient driver does NOT support more than 1 open DbDataReader + /// with only 1 DbConnection. + /// + /// - it is not supported. + /// + /// MS SQL Server 2000 (and 7) throws an exception when multiple DbDataReaders are + /// attempted to be opened. When SQL Server 2005 comes out a new driver will be + /// created for it because SQL Server 2005 is supposed to support it. + /// + 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 DateTime MinDate => new DateTime(1753, 1, 1); + + 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; + } + + // 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); + } + } +} From 1d921e3767be91ee5ef1e6e4201627d7c3081f58 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Mon, 30 Sep 2019 13:42:49 +1300 Subject: [PATCH 2/5] Address feedback --- .../Driver/MicrosoftDataSqlClientDriver.cs | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs index 7eff669e15c..49cdb1726a8 100644 --- a/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs +++ b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs @@ -6,6 +6,7 @@ using NHibernate.Dialect; using NHibernate.Engine; using NHibernate.SqlTypes; +using NHibernate.Util; namespace NHibernate.Driver { @@ -14,6 +15,10 @@ namespace NHibernate.Driver /// 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() @@ -48,17 +53,8 @@ public MicrosoftDataSqlClientDriver() /// public override string NamedPrefix => "@"; - /// - /// The SqlClient driver does NOT support more than 1 open DbDataReader - /// with only 1 DbConnection. - /// - /// - it is not supported. - /// - /// MS SQL Server 2000 (and 7) throws an exception when multiple DbDataReaders are - /// attempted to be opened. When SQL Server 2005 comes out a new driver will be - /// created for it because SQL Server 2005 is supposed to support it. - /// - public override bool SupportsMultipleOpenReaders => false; + /// + public override bool SupportsMultipleOpenReaders => true; /// public override bool SupportsMultipleQueries => true; @@ -69,8 +65,10 @@ public MicrosoftDataSqlClientDriver() /// public override bool HasDelayedDistributedTransactionCompletion => true; + public override bool RequiresTimeSpanForTime => true; + /// - public override DateTime MinDate => new DateTime(1753, 1, 1); + public override DateTime MinDate => DateTime.MinValue; System.Type IEmbeddedBatcherFactoryProvider.BatcherFactoryClass => typeof(GenericBatchingBatcherFactory); @@ -143,6 +141,13 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq case DbType.Xml: dbParam.Size = MsSql2005Dialect.MaxSizeForXml; break; + 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 From 84aabbf201112e41a644d189b0c30e7430b33dc8 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Tue, 15 Oct 2019 14:22:53 +1300 Subject: [PATCH 3/5] Fix SupportsMultipleOpenReaders & Time & DateTime --- src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs index 49cdb1726a8..1c4ad3d9fc0 100644 --- a/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs +++ b/src/NHibernate/Driver/MicrosoftDataSqlClientDriver.cs @@ -54,7 +54,7 @@ public MicrosoftDataSqlClientDriver() public override string NamedPrefix => "@"; /// - public override bool SupportsMultipleOpenReaders => true; + public override bool SupportsMultipleOpenReaders => false; /// public override bool SupportsMultipleQueries => true; @@ -141,6 +141,9 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq case DbType.Xml: dbParam.Size = MsSql2005Dialect.MaxSizeForXml; break; + } + switch (sqlType.DbType) + { case DbType.Time: SetSqlDbType(dbParam, SqlDbType.Time); dbParam.Size = MaxTime; From 159366aee5ebd7c6a731ec3b23dd181b24a188b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 20 Oct 2019 18:21:44 +0200 Subject: [PATCH 4/5] Fix tests failing with Microsoft.Data.SqlClient And allow testing it by adding its NuGet package in the test project. --- .../Async/ExceptionsTest/SQLExceptionConversionTest.cs | 3 ++- src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs | 6 ++---- .../ExceptionsTest/SQLExceptionConversionTest.cs | 3 ++- src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs | 6 ++---- src/NHibernate.Test/NHibernate.Test.csproj | 1 + 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs b/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs index 0ad016e2c01..108b5302788 100644 --- a/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs +++ b/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs @@ -43,7 +43,8 @@ protected override bool AppliesTo(Dialect.Dialect dialect) protected override bool AppliesTo(ISessionFactoryImplementor factory) { var driver = factory.ConnectionProvider.Driver; - return !(driver is OracleDataClientDriver) && !(driver is OracleManagedDataClientDriver) && !(driver is OracleLiteDataClientDriver) && !(driver is OdbcDriver) && !(driver is OleDbDriver); + return !(driver is OracleDataClientDriver) && !(driver is OracleManagedDataClientDriver) && !(driver is OracleLiteDataClientDriver) && + !(driver is OdbcDriver) && !(driver is OleDbDriver) && !(driver is MicrosoftDataSqlClientDriver); } protected override void Configure(Cfg.Configuration configuration) 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/SQLExceptionConversionTest.cs b/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs index b8c59551c07..8f7019fc452 100644 --- a/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs +++ b/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs @@ -32,7 +32,8 @@ protected override bool AppliesTo(Dialect.Dialect dialect) protected override bool AppliesTo(ISessionFactoryImplementor factory) { var driver = factory.ConnectionProvider.Driver; - return !(driver is OracleDataClientDriver) && !(driver is OracleManagedDataClientDriver) && !(driver is OracleLiteDataClientDriver) && !(driver is OdbcDriver) && !(driver is OleDbDriver); + return !(driver is OracleDataClientDriver) && !(driver is OracleManagedDataClientDriver) && !(driver is OracleLiteDataClientDriver) && + !(driver is OdbcDriver) && !(driver is OleDbDriver) && !(driver is MicrosoftDataSqlClientDriver); } protected override void Configure(Cfg.Configuration configuration) 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 @@ + From 9aebf9db3e3cd7f7280644f0d9c84cfb9d505fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 20 Oct 2019 18:30:35 +0200 Subject: [PATCH 5/5] Re-enable a test for Microsoft.Data.SqlClient --- .../ExceptionsTest/SQLExceptionConversionTest.cs | 3 +-- .../ExceptionsTest/MSSQLExceptionConverterExample.cs | 12 ++++++++++-- .../ExceptionsTest/SQLExceptionConversionTest.cs | 3 +-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs b/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs index 108b5302788..0ad016e2c01 100644 --- a/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs +++ b/src/NHibernate.Test/Async/ExceptionsTest/SQLExceptionConversionTest.cs @@ -43,8 +43,7 @@ protected override bool AppliesTo(Dialect.Dialect dialect) protected override bool AppliesTo(ISessionFactoryImplementor factory) { var driver = factory.ConnectionProvider.Driver; - return !(driver is OracleDataClientDriver) && !(driver is OracleManagedDataClientDriver) && !(driver is OracleLiteDataClientDriver) && - !(driver is OdbcDriver) && !(driver is OleDbDriver) && !(driver is MicrosoftDataSqlClientDriver); + return !(driver is OracleDataClientDriver) && !(driver is OracleManagedDataClientDriver) && !(driver is OracleLiteDataClientDriver) && !(driver is OdbcDriver) && !(driver is OleDbDriver); } protected override void Configure(Cfg.Configuration configuration) 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/ExceptionsTest/SQLExceptionConversionTest.cs b/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs index 8f7019fc452..b8c59551c07 100644 --- a/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs +++ b/src/NHibernate.Test/ExceptionsTest/SQLExceptionConversionTest.cs @@ -32,8 +32,7 @@ protected override bool AppliesTo(Dialect.Dialect dialect) protected override bool AppliesTo(ISessionFactoryImplementor factory) { var driver = factory.ConnectionProvider.Driver; - return !(driver is OracleDataClientDriver) && !(driver is OracleManagedDataClientDriver) && !(driver is OracleLiteDataClientDriver) && - !(driver is OdbcDriver) && !(driver is OleDbDriver) && !(driver is MicrosoftDataSqlClientDriver); + return !(driver is OracleDataClientDriver) && !(driver is OracleManagedDataClientDriver) && !(driver is OracleLiteDataClientDriver) && !(driver is OdbcDriver) && !(driver is OleDbDriver); } protected override void Configure(Cfg.Configuration configuration)