diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index dc4f5a36..2569a938 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -155,6 +155,12 @@ namespace Dapper /// public bool RemoveUnused { get; set; } + internal static bool ShouldSetDbType(DbType? dbType) + => dbType.HasValue && dbType.GetValueOrDefault() != EnumerableMultiParameter; + + internal static bool ShouldSetDbType(DbType dbType) + => dbType != EnumerableMultiParameter; // just in case called with non-nullable + /// /// Add all the parameters needed to the command just before it executes /// @@ -262,9 +268,9 @@ namespace Dapper #pragma warning disable 0618 p.Value = SqlMapper.SanitizeParameterValue(val); #pragma warning restore 0618 - if (dbType != null && p.DbType != dbType) + if (ShouldSetDbType(dbType) && p.DbType != dbType.GetValueOrDefault()) { - p.DbType = dbType.Value; + p.DbType = dbType.GetValueOrDefault(); } var s = val as string; if (s?.Length <= DbString.DefaultLength) @@ -277,7 +283,7 @@ namespace Dapper } else { - if (dbType != null) p.DbType = dbType.Value; + if (ShouldSetDbType(dbType)) p.DbType = dbType.GetValueOrDefault(); if (param.Size != null) p.Size = param.Size.Value; if (param.Precision != null) p.Precision = param.Precision.Value; if (param.Scale != null) p.Scale = param.Scale.Value; @@ -468,15 +474,9 @@ namespace Dapper } else { - dbType = (!dbType.HasValue) -#pragma warning disable 618 - ? SqlMapper.LookupDbType(targetMemberType, targetMemberType?.Name, true, out SqlMapper.ITypeHandler handler) -#pragma warning restore 618 - : dbType; - // CameFromTemplate property would not apply here because this new param // Still needs to be added to the command - Add(dynamicParamName, expression.Compile().Invoke(target), null, ParameterDirection.InputOutput, sizeToSet); + Add(dynamicParamName, expression.Compile().Invoke(target), dbType, ParameterDirection.InputOutput, sizeToSet); } parameter = parameters[dynamicParamName]; diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 5ace2cbe..ee49c2ab 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -163,11 +163,11 @@ namespace Dapper select Tuple.Create(pair.Key, pair.Value); } - private static Dictionary typeMap; + private static Dictionary typeMap; static SqlMapper() { - typeMap = new Dictionary(37) + typeMap = new Dictionary(37) { [typeof(byte)] = DbType.Byte, [typeof(sbyte)] = DbType.SByte, @@ -184,9 +184,9 @@ namespace Dapper [typeof(string)] = DbType.String, [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, - [typeof(DateTime)] = DbType.DateTime, + [typeof(DateTime)] = null, [typeof(DateTimeOffset)] = DbType.DateTimeOffset, - [typeof(TimeSpan)] = DbType.Time, + [typeof(TimeSpan)] = null, [typeof(byte[])] = DbType.Binary, [typeof(byte?)] = DbType.Byte, [typeof(sbyte?)] = DbType.SByte, @@ -202,9 +202,9 @@ namespace Dapper [typeof(bool?)] = DbType.Boolean, [typeof(char?)] = DbType.StringFixedLength, [typeof(Guid?)] = DbType.Guid, - [typeof(DateTime?)] = DbType.DateTime, + [typeof(DateTime?)] = null, [typeof(DateTimeOffset?)] = DbType.DateTimeOffset, - [typeof(TimeSpan?)] = DbType.Time, + [typeof(TimeSpan?)] = null, [typeof(object)] = DbType.Object }; ResetTypeHandlers(false); @@ -234,9 +234,9 @@ namespace Dapper // use clone, mutate, replace to avoid threading issues var snapshot = typeMap; - if (snapshot.TryGetValue(type, out DbType oldValue) && oldValue == dbType) return; // nothing to do + if (snapshot.TryGetValue(type, out var oldValue) && oldValue == dbType) return; // nothing to do - typeMap = new Dictionary(snapshot) { [type] = dbType }; + typeMap = new Dictionary(snapshot) { [type] = dbType }; } /// @@ -250,7 +250,7 @@ namespace Dapper if (!snapshot.ContainsKey(type)) return; // nothing to do - var newCopy = new Dictionary(snapshot); + var newCopy = new Dictionary(snapshot); newCopy.Remove(type); typeMap = newCopy; @@ -336,15 +336,20 @@ namespace Dapper /// /// Get the DbType that maps to a given value. /// + /// The parameter to configure the value for. /// The object to get a corresponding database type for. [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] - public static DbType GetDbType(object value) + public static void SetDbType(IDataParameter parameter, object value) { - if (value == null || value is DBNull) return DbType.Object; + if (value == null || value is DBNull) return; - return LookupDbType(value.GetType(), "n/a", false, out ITypeHandler _); + var dbType = LookupDbType(value.GetType(), "n/a", false, out ITypeHandler _); + if (DynamicParameters.ShouldSetDbType(dbType)) + { + parameter.DbType = dbType.GetValueOrDefault(); + } } /// @@ -357,7 +362,7 @@ namespace Dapper [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] - public static DbType LookupDbType(Type type, string name, bool demand, out ITypeHandler handler) + public static DbType? LookupDbType(Type type, string name, bool demand, out ITypeHandler handler) { handler = null; var nullUnderlyingType = Nullable.GetUnderlyingType(type); @@ -366,7 +371,7 @@ namespace Dapper { type = Enum.GetUnderlyingType(type); } - if (typeMap.TryGetValue(type, out DbType dbType)) + if (typeMap.TryGetValue(type, out var dbType)) { return dbType; } @@ -2031,7 +2036,7 @@ namespace Dapper var count = 0; bool isString = value is IEnumerable; bool isDbString = value is IEnumerable; - DbType dbType = 0; + DbType? dbType = null; int splitAt = SqlMapper.Settings.InListStringSplitCount; bool viaSplit = splitAt >= 0 @@ -2076,9 +2081,9 @@ namespace Dapper if (tmp != null && !(tmp is DBNull)) lastValue = tmp; // only interested in non-trivial values for padding - if (listParam.DbType != dbType) + if (DynamicParameters.ShouldSetDbType(dbType) && listParam.DbType != dbType.GetValueOrDefault()) { - listParam.DbType = dbType; + listParam.DbType = dbType.GetValueOrDefault(); } command.Parameters.Add(listParam); } @@ -2092,7 +2097,10 @@ namespace Dapper var padParam = command.CreateParameter(); padParam.ParameterName = namePrefix + count.ToString(); if (isString) padParam.Size = DbString.DefaultLength; - padParam.DbType = dbType; + if (DynamicParameters.ShouldSetDbType(dbType)) + { + padParam.DbType = dbType.GetValueOrDefault(); + } padParam.Value = lastValue; command.Parameters.Add(padParam); } @@ -2528,7 +2536,7 @@ namespace Dapper continue; } #pragma warning disable 618 - DbType dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler handler); + DbType? dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler handler); #pragma warning restore 618 if (dbType == DynamicParameters.EnumerableMultiParameter) { @@ -2563,22 +2571,22 @@ namespace Dapper il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name] il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.ParameterName)).GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] } - 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 + 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 { il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] - if (dbType == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic + if (dbType.GetValueOrDefault() == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic { // look it up from the param value il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value] - il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.GetDbType), BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] + il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.SetDbType), BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] } else { // constant value; nice and simple - EmitInt32(il, (int)dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] + EmitInt32(il, (int)dbType.GetValueOrDefault());// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] + il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.DbType)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] } - il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.DbType)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] } il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] diff --git a/nuget.config b/nuget.config index b004e5cc..00f52b7e 100644 --- a/nuget.config +++ b/nuget.config @@ -2,6 +2,7 @@ + \ No newline at end of file diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index f8402fca..15a6d39e 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index 0f1c4e45..1fc5cb33 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; @@ -44,7 +45,7 @@ namespace Dapper.Tests { IDbTransaction transaction = conn.BeginTransaction(); conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); - conn.Execute("insert into tcat(breed, name) values(:breed, :name) ", Cats); + conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", Cats); var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new[] { 1, 3, 5 } }); Assert.Equal(3, r.Count()); @@ -55,6 +56,24 @@ namespace Dapper.Tests } } + [FactPostgresql] + public void TestPostgresqlListParameters() + { + using (var conn = GetOpenNpgsqlConnection()) + { + IDbTransaction transaction = conn.BeginTransaction(); + conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); + conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", new List(Cats)); + + var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new List { 1, 3, 5 } }); + Assert.Equal(3, r.Count()); + Assert.Equal(1, r.Count(c => c.Id == 1)); + Assert.Equal(1, r.Count(c => c.Id == 3)); + Assert.Equal(1, r.Count(c => c.Id == 5)); + transaction.Rollback(); + } + } + private class CharTable { public int Id { get; set; } @@ -88,6 +107,15 @@ namespace Dapper.Tests } } + [FactPostgresql] + public void TestPostgresqlDateTimeUsage() + { + using (var conn = GetOpenNpgsqlConnection()) + { + _ = conn.ExecuteScalar("SELECT @Now", new { Now = DateTime.UtcNow }); + } + } + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactPostgresqlAttribute : FactAttribute {