Skip to content

Commit b272cc6

Browse files
authored
Don't specify dbtype for DateTime et al; fixed npgsql 6 issue (#1723)
* context: #1716 #1716 - change type-map to allow nullable - change DateTime/TimeSpan to null - do not specify DbType for null - semi-breaking change: swap GetDbType to SetDbType, noting that it is marked [Obsolete] and internal-use-only - semi-breaking change: make LookupDbType return nullable (same internal-use/[Obsolete]) * add test for Npgsql 6.0.0-rc.1 * try list parameters (like arrays) * never assign -1 (sentinel) as the .DbType * postgres tests
1 parent 80719d0 commit b272cc6

File tree

5 files changed

+73
-36
lines changed

5 files changed

+73
-36
lines changed

Dapper/DynamicParameters.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id
155155
/// </summary>
156156
public bool RemoveUnused { get; set; }
157157

158+
internal static bool ShouldSetDbType(DbType? dbType)
159+
=> dbType.HasValue && dbType.GetValueOrDefault() != EnumerableMultiParameter;
160+
161+
internal static bool ShouldSetDbType(DbType dbType)
162+
=> dbType != EnumerableMultiParameter; // just in case called with non-nullable
163+
158164
/// <summary>
159165
/// Add all the parameters needed to the command just before it executes
160166
/// </summary>
@@ -262,9 +268,9 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
262268
#pragma warning disable 0618
263269
p.Value = SqlMapper.SanitizeParameterValue(val);
264270
#pragma warning restore 0618
265-
if (dbType != null && p.DbType != dbType)
271+
if (ShouldSetDbType(dbType) && p.DbType != dbType.GetValueOrDefault())
266272
{
267-
p.DbType = dbType.Value;
273+
p.DbType = dbType.GetValueOrDefault();
268274
}
269275
var s = val as string;
270276
if (s?.Length <= DbString.DefaultLength)
@@ -277,7 +283,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
277283
}
278284
else
279285
{
280-
if (dbType != null) p.DbType = dbType.Value;
286+
if (ShouldSetDbType(dbType)) p.DbType = dbType.GetValueOrDefault();
281287
if (param.Size != null) p.Size = param.Size.Value;
282288
if (param.Precision != null) p.Precision = param.Precision.Value;
283289
if (param.Scale != null) p.Scale = param.Scale.Value;
@@ -468,15 +474,9 @@ static void ThrowInvalidChain()
468474
}
469475
else
470476
{
471-
dbType = (!dbType.HasValue)
472-
#pragma warning disable 618
473-
? SqlMapper.LookupDbType(targetMemberType, targetMemberType?.Name, true, out SqlMapper.ITypeHandler handler)
474-
#pragma warning restore 618
475-
: dbType;
476-
477477
// CameFromTemplate property would not apply here because this new param
478478
// Still needs to be added to the command
479-
Add(dynamicParamName, expression.Compile().Invoke(target), null, ParameterDirection.InputOutput, sizeToSet);
479+
Add(dynamicParamName, expression.Compile().Invoke(target), dbType, ParameterDirection.InputOutput, sizeToSet);
480480
}
481481

482482
parameter = parameters[dynamicParamName];

Dapper/SqlMapper.cs

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ where pair.Value > 1
163163
select Tuple.Create(pair.Key, pair.Value);
164164
}
165165

166-
private static Dictionary<Type, DbType> typeMap;
166+
private static Dictionary<Type, DbType?> typeMap;
167167

168168
static SqlMapper()
169169
{
170-
typeMap = new Dictionary<Type, DbType>(37)
170+
typeMap = new Dictionary<Type, DbType?>(37)
171171
{
172172
[typeof(byte)] = DbType.Byte,
173173
[typeof(sbyte)] = DbType.SByte,
@@ -184,9 +184,9 @@ static SqlMapper()
184184
[typeof(string)] = DbType.String,
185185
[typeof(char)] = DbType.StringFixedLength,
186186
[typeof(Guid)] = DbType.Guid,
187-
[typeof(DateTime)] = DbType.DateTime,
187+
[typeof(DateTime)] = null,
188188
[typeof(DateTimeOffset)] = DbType.DateTimeOffset,
189-
[typeof(TimeSpan)] = DbType.Time,
189+
[typeof(TimeSpan)] = null,
190190
[typeof(byte[])] = DbType.Binary,
191191
[typeof(byte?)] = DbType.Byte,
192192
[typeof(sbyte?)] = DbType.SByte,
@@ -202,9 +202,9 @@ static SqlMapper()
202202
[typeof(bool?)] = DbType.Boolean,
203203
[typeof(char?)] = DbType.StringFixedLength,
204204
[typeof(Guid?)] = DbType.Guid,
205-
[typeof(DateTime?)] = DbType.DateTime,
205+
[typeof(DateTime?)] = null,
206206
[typeof(DateTimeOffset?)] = DbType.DateTimeOffset,
207-
[typeof(TimeSpan?)] = DbType.Time,
207+
[typeof(TimeSpan?)] = null,
208208
[typeof(object)] = DbType.Object
209209
};
210210
ResetTypeHandlers(false);
@@ -234,9 +234,9 @@ public static void AddTypeMap(Type type, DbType dbType)
234234
// use clone, mutate, replace to avoid threading issues
235235
var snapshot = typeMap;
236236

237-
if (snapshot.TryGetValue(type, out DbType oldValue) && oldValue == dbType) return; // nothing to do
237+
if (snapshot.TryGetValue(type, out var oldValue) && oldValue == dbType) return; // nothing to do
238238

239-
typeMap = new Dictionary<Type, DbType>(snapshot) { [type] = dbType };
239+
typeMap = new Dictionary<Type, DbType?>(snapshot) { [type] = dbType };
240240
}
241241

242242
/// <summary>
@@ -250,7 +250,7 @@ public static void RemoveTypeMap(Type type)
250250

251251
if (!snapshot.ContainsKey(type)) return; // nothing to do
252252

253-
var newCopy = new Dictionary<Type, DbType>(snapshot);
253+
var newCopy = new Dictionary<Type, DbType?>(snapshot);
254254
newCopy.Remove(type);
255255

256256
typeMap = newCopy;
@@ -336,15 +336,20 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clon
336336
/// <summary>
337337
/// Get the DbType that maps to a given value.
338338
/// </summary>
339+
/// <param name="parameter">The parameter to configure the value for.</param>
339340
/// <param name="value">The object to get a corresponding database type for.</param>
340341
[Obsolete(ObsoleteInternalUsageOnly, false)]
341342
[Browsable(false)]
342343
[EditorBrowsable(EditorBrowsableState.Never)]
343-
public static DbType GetDbType(object value)
344+
public static void SetDbType(IDataParameter parameter, object value)
344345
{
345-
if (value == null || value is DBNull) return DbType.Object;
346+
if (value == null || value is DBNull) return;
346347

347-
return LookupDbType(value.GetType(), "n/a", false, out ITypeHandler _);
348+
var dbType = LookupDbType(value.GetType(), "n/a", false, out ITypeHandler _);
349+
if (DynamicParameters.ShouldSetDbType(dbType))
350+
{
351+
parameter.DbType = dbType.GetValueOrDefault();
352+
}
348353
}
349354

350355
/// <summary>
@@ -357,7 +362,7 @@ public static DbType GetDbType(object value)
357362
[Obsolete(ObsoleteInternalUsageOnly, false)]
358363
[Browsable(false)]
359364
[EditorBrowsable(EditorBrowsableState.Never)]
360-
public static DbType LookupDbType(Type type, string name, bool demand, out ITypeHandler handler)
365+
public static DbType? LookupDbType(Type type, string name, bool demand, out ITypeHandler handler)
361366
{
362367
handler = null;
363368
var nullUnderlyingType = Nullable.GetUnderlyingType(type);
@@ -366,7 +371,7 @@ public static DbType LookupDbType(Type type, string name, bool demand, out IType
366371
{
367372
type = Enum.GetUnderlyingType(type);
368373
}
369-
if (typeMap.TryGetValue(type, out DbType dbType))
374+
if (typeMap.TryGetValue(type, out var dbType))
370375
{
371376
return dbType;
372377
}
@@ -2031,7 +2036,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
20312036
var count = 0;
20322037
bool isString = value is IEnumerable<string>;
20332038
bool isDbString = value is IEnumerable<DbString>;
2034-
DbType dbType = 0;
2039+
DbType? dbType = null;
20352040

20362041
int splitAt = SqlMapper.Settings.InListStringSplitCount;
20372042
bool viaSplit = splitAt >= 0
@@ -2076,9 +2081,9 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
20762081
if (tmp != null && !(tmp is DBNull))
20772082
lastValue = tmp; // only interested in non-trivial values for padding
20782083

2079-
if (listParam.DbType != dbType)
2084+
if (DynamicParameters.ShouldSetDbType(dbType) && listParam.DbType != dbType.GetValueOrDefault())
20802085
{
2081-
listParam.DbType = dbType;
2086+
listParam.DbType = dbType.GetValueOrDefault();
20822087
}
20832088
command.Parameters.Add(listParam);
20842089
}
@@ -2092,7 +2097,10 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
20922097
var padParam = command.CreateParameter();
20932098
padParam.ParameterName = namePrefix + count.ToString();
20942099
if (isString) padParam.Size = DbString.DefaultLength;
2095-
padParam.DbType = dbType;
2100+
if (DynamicParameters.ShouldSetDbType(dbType))
2101+
{
2102+
padParam.DbType = dbType.GetValueOrDefault();
2103+
}
20962104
padParam.Value = lastValue;
20972105
command.Parameters.Add(padParam);
20982106
}
@@ -2528,7 +2536,7 @@ internal static Action<IDbCommand, object> CreateParamInfoGenerator(Identity ide
25282536
continue;
25292537
}
25302538
#pragma warning disable 618
2531-
DbType dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler handler);
2539+
DbType? dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler handler);
25322540
#pragma warning restore 618
25332541
if (dbType == DynamicParameters.EnumerableMultiParameter)
25342542
{
@@ -2563,22 +2571,22 @@ internal static Action<IDbCommand, object> CreateParamInfoGenerator(Identity ide
25632571
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name]
25642572
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.ParameterName)).GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
25652573
}
2566-
if (dbType != DbType.Time && handler == null) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time
2574+
if (DynamicParameters.ShouldSetDbType(dbType) && dbType != DbType.Time && handler == null) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time
25672575
{
25682576
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
2569-
if (dbType == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic
2577+
if (dbType.GetValueOrDefault() == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic
25702578
{
25712579
// look it up from the param value
25722580
il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param]
25732581
il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value]
2574-
il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.GetDbType), BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
2582+
il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.SetDbType), BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter]
25752583
}
25762584
else
25772585
{
25782586
// constant value; nice and simple
2579-
EmitInt32(il, (int)dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
2587+
EmitInt32(il, (int)dbType.GetValueOrDefault());// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
2588+
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.DbType)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
25802589
}
2581-
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.DbType)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
25822590
}
25832591

25842592
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]

nuget.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<configuration>
33
<packageSources>
44
<clear />
5+
<add key="npgsql" value="https://www.myget.org/F/npgsql-unstable/api/v3/index.json" />
56
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
67
</packageSources>
78
</configuration>

tests/Dapper.Tests/Dapper.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.0" />
2020
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
2121
<PackageReference Include="MySqlConnector" Version="1.1.0" />
22-
<PackageReference Include="Npgsql" Version="5.0.0" />
22+
<PackageReference Include="Npgsql" Version="6.0.0-rtm-ci.20211103T101701" />
2323
<PackageReference Include="Snowflake.Data" Version="2.0.3" />
2424
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
2525
<PackageReference Include="System.ValueTuple" Version="4.5.0" />

tests/Dapper.Tests/Providers/PostgresqlTests.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Data;
34
using System.Data.Common;
45
using System.Linq;
@@ -44,7 +45,7 @@ public void TestPostgresqlArrayParameters()
4445
{
4546
IDbTransaction transaction = conn.BeginTransaction();
4647
conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);");
47-
conn.Execute("insert into tcat(breed, name) values(:breed, :name) ", Cats);
48+
conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", Cats);
4849

4950
var r = conn.Query<Cat>("select * from tcat where id=any(:catids)", new { catids = new[] { 1, 3, 5 } });
5051
Assert.Equal(3, r.Count());
@@ -55,6 +56,24 @@ public void TestPostgresqlArrayParameters()
5556
}
5657
}
5758

59+
[FactPostgresql]
60+
public void TestPostgresqlListParameters()
61+
{
62+
using (var conn = GetOpenNpgsqlConnection())
63+
{
64+
IDbTransaction transaction = conn.BeginTransaction();
65+
conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);");
66+
conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", new List<Cat>(Cats));
67+
68+
var r = conn.Query<Cat>("select * from tcat where id=any(:catids)", new { catids = new List<int> { 1, 3, 5 } });
69+
Assert.Equal(3, r.Count());
70+
Assert.Equal(1, r.Count(c => c.Id == 1));
71+
Assert.Equal(1, r.Count(c => c.Id == 3));
72+
Assert.Equal(1, r.Count(c => c.Id == 5));
73+
transaction.Rollback();
74+
}
75+
}
76+
5877
private class CharTable
5978
{
6079
public int Id { get; set; }
@@ -88,6 +107,15 @@ public void TestPostgresqlSelectArray()
88107
}
89108
}
90109

110+
[FactPostgresql]
111+
public void TestPostgresqlDateTimeUsage()
112+
{
113+
using (var conn = GetOpenNpgsqlConnection())
114+
{
115+
_ = conn.ExecuteScalar("SELECT @Now", new { Now = DateTime.UtcNow });
116+
}
117+
}
118+
91119
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
92120
public class FactPostgresqlAttribute : FactAttribute
93121
{

0 commit comments

Comments
 (0)