mirror of
https://github.com/DapperLib/Dapper.git
synced 2026-10-17 05:12:13 +08:00
Don't specify dbtype for DateTime et al; fixed npgsql 6 issue (#1723)
* context: #1716 https://github.com/DapperLib/Dapper/issues/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
This commit is contained in:
+10
-10
@@ -155,6 +155,12 @@ namespace Dapper
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Add all the parameters needed to the command just before it executes
|
||||
/// </summary>
|
||||
@@ -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];
|
||||
|
||||
+32
-24
@@ -163,11 +163,11 @@ namespace Dapper
|
||||
select Tuple.Create(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
private static Dictionary<Type, DbType> typeMap;
|
||||
private static Dictionary<Type, DbType?> typeMap;
|
||||
|
||||
static SqlMapper()
|
||||
{
|
||||
typeMap = new Dictionary<Type, DbType>(37)
|
||||
typeMap = new Dictionary<Type, DbType?>(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<Type, DbType>(snapshot) { [type] = dbType };
|
||||
typeMap = new Dictionary<Type, DbType?>(snapshot) { [type] = dbType };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -250,7 +250,7 @@ namespace Dapper
|
||||
|
||||
if (!snapshot.ContainsKey(type)) return; // nothing to do
|
||||
|
||||
var newCopy = new Dictionary<Type, DbType>(snapshot);
|
||||
var newCopy = new Dictionary<Type, DbType?>(snapshot);
|
||||
newCopy.Remove(type);
|
||||
|
||||
typeMap = newCopy;
|
||||
@@ -336,15 +336,20 @@ namespace Dapper
|
||||
/// <summary>
|
||||
/// Get the DbType that maps to a given value.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The parameter to configure the value for.</param>
|
||||
/// <param name="value">The object to get a corresponding database type for.</param>
|
||||
[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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<string>;
|
||||
bool isDbString = value is IEnumerable<DbString>;
|
||||
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]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="npgsql" value="https://www.myget.org/F/npgsql-unstable/api/v3/index.json" />
|
||||
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
@@ -19,7 +19,7 @@
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="MySqlConnector" Version="1.1.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="6.0.0-rtm-ci.20211103T101701" />
|
||||
<PackageReference Include="Snowflake.Data" Version="2.0.3" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
|
||||
@@ -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<Cat>("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<Cat>(Cats));
|
||||
|
||||
var r = conn.Query<Cat>("select * from tcat where id=any(:catids)", new { catids = new List<int> { 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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user