Skip to content

Commit c8c52aa

Browse files
Add a driver to support Microsoft.Data.SqlClient provider (#2216)
Co-authored-by: Frédéric Delaporte <12201973+fredericDelaporte@users.noreply.github.com>
1 parent 3eb16b0 commit c8c52aa

File tree

5 files changed

+224
-10
lines changed

5 files changed

+224
-10
lines changed

src/NHibernate.Test/Async/NHSpecificTest/NH2420/Fixture.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,8 @@ public async Task ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransac
7171
new DummyEnlistment(),
7272
EnlistmentOptions.None);
7373

74-
if (Sfi.ConnectionProvider.Driver.GetType() == typeof(OdbcDriver))
75-
connection = new OdbcConnection(connectionString);
76-
else
77-
connection = new SqlConnection(connectionString);
74+
connection = Sfi.ConnectionProvider.Driver.CreateConnection();
75+
connection.ConnectionString = connectionString;
7876

7977
await (connection.OpenAsync());
8078
using (s = Sfi.WithOptions().Connection(connection).OpenSession())

src/NHibernate.Test/ExceptionsTest/MSSQLExceptionConverterExample.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,22 @@ public class MSSQLExceptionConverterExample : ISQLExceptionConverter
1212

1313
public Exception Convert(AdoExceptionContextInfo exInfo)
1414
{
15-
SqlException sqle = ADOExceptionHelper.ExtractDbException(exInfo.SqlException) as SqlException;
16-
if(sqle != null)
15+
var dbEx = ADOExceptionHelper.ExtractDbException(exInfo.SqlException);
16+
if (dbEx is SqlException sqle)
1717
{
1818
if (sqle.Number == 547)
1919
return new ConstraintViolationException(exInfo.Message, sqle.InnerException, exInfo.Sql, null);
2020
if (sqle.Number == 208)
2121
return new SQLGrammarException(exInfo.Message, sqle.InnerException, exInfo.Sql);
2222
}
23+
24+
if(dbEx is Microsoft.Data.SqlClient.SqlException msSqle)
25+
{
26+
if (msSqle.Number == 547)
27+
return new ConstraintViolationException(exInfo.Message, msSqle.InnerException, exInfo.Sql, null);
28+
if (msSqle.Number == 208)
29+
return new SQLGrammarException(exInfo.Message, msSqle.InnerException, exInfo.Sql);
30+
}
2331
return SQLStateConverter.HandledNonSpecificException(exInfo.SqlException, exInfo.Message, exInfo.Sql);
2432
}
2533

src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,8 @@ public void ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransaction()
6060
new DummyEnlistment(),
6161
EnlistmentOptions.None);
6262

63-
if (Sfi.ConnectionProvider.Driver.GetType() == typeof(OdbcDriver))
64-
connection = new OdbcConnection(connectionString);
65-
else
66-
connection = new SqlConnection(connectionString);
63+
connection = Sfi.ConnectionProvider.Driver.CreateConnection();
64+
connection.ConnectionString = connectionString;
6765

6866
connection.Open();
6967
using (s = Sfi.WithOptions().Connection(connection).OpenSession())

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
</ItemGroup>
4747
<ItemGroup>
4848
<PackageReference Include="log4net" Version="2.0.8" />
49+
<PackageReference Include="Microsoft.Data.SqlClient" Version="1.0.19269.1" />
4950
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.109.2" />
5051
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.8.11" />
5152
<PackageReference Include="NSubstitute" Version="3.1.0" />
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Data;
4+
using System.Data.Common;
5+
using NHibernate.AdoNet;
6+
using NHibernate.Dialect;
7+
using NHibernate.Engine;
8+
using NHibernate.SqlTypes;
9+
using NHibernate.Util;
10+
11+
namespace NHibernate.Driver
12+
{
13+
/// <summary>
14+
/// A NHibernate Driver for using the SqlClient DataProvider
15+
/// </summary>
16+
public class MicrosoftDataSqlClientDriver : ReflectionBasedDriver, IEmbeddedBatcherFactoryProvider, IParameterAdjuster
17+
{
18+
const byte MaxTime = 5;
19+
20+
private static readonly Action<object, SqlDbType> SetSqlDbType = DelegateHelper.BuildPropertySetter<SqlDbType>(System.Type.GetType("Microsoft.Data.SqlClient.SqlParameter, Microsoft.Data.SqlClient", true), "SqlDbType");
21+
22+
private Dialect.Dialect _dialect;
23+
24+
public MicrosoftDataSqlClientDriver()
25+
: base(
26+
"Microsoft.Data.SqlClient",
27+
"Microsoft.Data.SqlClient.SqlConnection",
28+
"Microsoft.Data.SqlClient.SqlCommand")
29+
{
30+
}
31+
32+
/// <summary>
33+
/// MsSql requires the use of a Named Prefix in the SQL statement.
34+
/// </summary>
35+
/// <remarks>
36+
/// <see langword="true" /> because MsSql uses "<c>@</c>".
37+
/// </remarks>
38+
public override bool UseNamedPrefixInSql => true;
39+
40+
/// <summary>
41+
/// MsSql requires the use of a Named Prefix in the Parameter.
42+
/// </summary>
43+
/// <remarks>
44+
/// <see langword="true" /> because MsSql uses "<c>@</c>".
45+
/// </remarks>
46+
public override bool UseNamedPrefixInParameter => true;
47+
48+
/// <summary>
49+
/// The Named Prefix for parameters.
50+
/// </summary>
51+
/// <value>
52+
/// Sql Server uses <c>"@"</c>.
53+
/// </value>
54+
public override string NamedPrefix => "@";
55+
56+
/// <inheritdoc/>
57+
public override bool SupportsMultipleOpenReaders => false;
58+
59+
/// <inheritdoc />
60+
public override bool SupportsMultipleQueries => true;
61+
62+
/// <summary>
63+
/// With read committed snapshot or lower, SQL Server may have not actually already committed the transaction
64+
/// right after the scope disposal.
65+
/// </summary>
66+
public override bool HasDelayedDistributedTransactionCompletion => true;
67+
68+
public override bool RequiresTimeSpanForTime => true;
69+
70+
/// <inheritdoc />
71+
public override DateTime MinDate => DateTime.MinValue;
72+
73+
System.Type IEmbeddedBatcherFactoryProvider.BatcherFactoryClass => typeof(GenericBatchingBatcherFactory);
74+
75+
/// <inheritdoc />
76+
public virtual void AdjustParameterForValue(DbParameter parameter, SqlType sqlType, object value)
77+
{
78+
if (value is string stringVal)
79+
switch (parameter.DbType)
80+
{
81+
case DbType.AnsiString:
82+
case DbType.AnsiStringFixedLength:
83+
parameter.Size = IsAnsiText(parameter, sqlType)
84+
? MsSql2000Dialect.MaxSizeForAnsiClob
85+
: Math.Max(stringVal.Length, sqlType.LengthDefined ? sqlType.Length : parameter.Size);
86+
break;
87+
case DbType.String:
88+
case DbType.StringFixedLength:
89+
parameter.Size = IsText(parameter, sqlType)
90+
? MsSql2000Dialect.MaxSizeForClob
91+
: Math.Max(stringVal.Length, sqlType.LengthDefined ? sqlType.Length : parameter.Size);
92+
break;
93+
}
94+
}
95+
96+
public override void Configure(IDictionary<string, string> settings)
97+
{
98+
base.Configure(settings);
99+
100+
_dialect = Dialect.Dialect.GetDialect(settings);
101+
}
102+
103+
/// <inheritdoc />
104+
protected override void InitializeParameter(DbParameter dbParam, string name, SqlType sqlType)
105+
{
106+
base.InitializeParameter(dbParam, name, sqlType);
107+
108+
// Defaults size/precision/scale
109+
switch (dbParam.DbType)
110+
{
111+
case DbType.AnsiString:
112+
case DbType.AnsiStringFixedLength:
113+
dbParam.Size = IsAnsiText(dbParam, sqlType)
114+
? MsSql2000Dialect.MaxSizeForAnsiClob
115+
: MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString;
116+
break;
117+
case DbType.Binary:
118+
dbParam.Size = IsBlob(dbParam, sqlType)
119+
? MsSql2000Dialect.MaxSizeForBlob
120+
: MsSql2000Dialect.MaxSizeForLengthLimitedBinary;
121+
break;
122+
case DbType.Decimal:
123+
if (_dialect == null)
124+
throw new InvalidOperationException(
125+
"Dialect not available, is this driver used without having been configured?");
126+
dbParam.Precision = _dialect.DefaultCastPrecision;
127+
dbParam.Scale = _dialect.DefaultCastScale;
128+
break;
129+
case DbType.String:
130+
case DbType.StringFixedLength:
131+
dbParam.Size = IsText(dbParam, sqlType)
132+
? MsSql2000Dialect.MaxSizeForClob
133+
: MsSql2000Dialect.MaxSizeForLengthLimitedString;
134+
break;
135+
case DbType.DateTime2:
136+
dbParam.Size = MsSql2000Dialect.MaxDateTime2;
137+
break;
138+
case DbType.DateTimeOffset:
139+
dbParam.Size = MsSql2000Dialect.MaxDateTimeOffset;
140+
break;
141+
case DbType.Xml:
142+
dbParam.Size = MsSql2005Dialect.MaxSizeForXml;
143+
break;
144+
}
145+
switch (sqlType.DbType)
146+
{
147+
case DbType.Time:
148+
SetSqlDbType(dbParam, SqlDbType.Time);
149+
dbParam.Size = MaxTime;
150+
break;
151+
case DbType.Date:
152+
SetSqlDbType(dbParam, SqlDbType.Date);
153+
break;
154+
}
155+
156+
// Do not override the default length for string using data from SqlType, since LIKE expressions needs
157+
// larger columns. https://nhibernate.jira.com/browse/NH-3036
158+
159+
if (sqlType.PrecisionDefined)
160+
{
161+
dbParam.Precision = sqlType.Precision;
162+
dbParam.Scale = sqlType.Scale;
163+
}
164+
}
165+
166+
/// <summary>
167+
/// Interprets if a parameter is a Clob (for the purposes of setting its default size)
168+
/// </summary>
169+
/// <param name="dbParam">The parameter</param>
170+
/// <param name="sqlType">The <see cref="SqlType" /> of the parameter</param>
171+
/// <returns>True, if the parameter should be interpreted as a Clob, otherwise False</returns>
172+
protected static bool IsAnsiText(DbParameter dbParam, SqlType sqlType)
173+
{
174+
return (DbType.AnsiString == dbParam.DbType || DbType.AnsiStringFixedLength == dbParam.DbType) &&
175+
sqlType.LengthDefined && sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString;
176+
}
177+
178+
/// <summary>
179+
/// Interprets if a parameter is a Clob (for the purposes of setting its default size)
180+
/// </summary>
181+
/// <param name="dbParam">The parameter</param>
182+
/// <param name="sqlType">The <see cref="SqlType" /> of the parameter</param>
183+
/// <returns>True, if the parameter should be interpreted as a Clob, otherwise False</returns>
184+
protected static bool IsText(DbParameter dbParam, SqlType sqlType)
185+
{
186+
return sqlType is StringClobSqlType ||
187+
(DbType.String == dbParam.DbType || DbType.StringFixedLength == dbParam.DbType) &&
188+
sqlType.LengthDefined && sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedString;
189+
}
190+
191+
/// <summary>
192+
/// Interprets if a parameter is a Blob (for the purposes of setting its default size)
193+
/// </summary>
194+
/// <param name="dbParam">The parameter</param>
195+
/// <param name="sqlType">The <see cref="SqlType" /> of the parameter</param>
196+
/// <returns>True, if the parameter should be interpreted as a Blob, otherwise False</returns>
197+
protected static bool IsBlob(DbParameter dbParam, SqlType sqlType)
198+
{
199+
return sqlType is BinaryBlobSqlType || DbType.Binary == dbParam.DbType && sqlType.LengthDefined &&
200+
sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedBinary;
201+
}
202+
203+
/// <inheritdoc />
204+
public override IResultSetsCommand GetResultSetsCommand(ISessionImplementor session)
205+
{
206+
return new BasicResultSetsCommand(session);
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)