Commit 5917143e authored by Marc Gravell's avatar Marc Gravell

Issue 182: richer DBType support when using object/dynamic parameters

parent 66ad5438
...@@ -846,7 +846,20 @@ internal static void SetHandler(ITypeHandler handler) ...@@ -846,7 +846,20 @@ internal static void SetHandler(ITypeHandler handler)
private static Dictionary<Type, ITypeHandler> typeHandlers = new Dictionary<Type, ITypeHandler>(); private static Dictionary<Type, ITypeHandler> typeHandlers = new Dictionary<Type, ITypeHandler>();
internal const string LinqBinary = "System.Data.Linq.Binary"; internal const string LinqBinary = "System.Data.Linq.Binary";
internal static DbType LookupDbType(Type type, string name, out ITypeHandler handler)
/// <summary>
/// Get the DbType that maps to a given value
/// </summary>
[Obsolete("This method is for internal use only"), Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public static DbType GetDbType(object value)
{
if (value == null || value is DBNull) return DbType.Object;
ITypeHandler handler;
return LookupDbType(value.GetType(), "n/a", false, out handler);
}
internal static DbType LookupDbType(Type type, string name, bool demand, out ITypeHandler handler)
{ {
DbType dbType; DbType dbType;
handler = null; handler = null;
...@@ -885,7 +898,10 @@ internal static DbType LookupDbType(Type type, string name, out ITypeHandler han ...@@ -885,7 +898,10 @@ internal static DbType LookupDbType(Type type, string name, out ITypeHandler han
AddTypeHandler(type, handler = new UdtTypeHandler("HIERARCHYID")); AddTypeHandler(type, handler = new UdtTypeHandler("HIERARCHYID"));
return DbType.Object; return DbType.Object;
} }
throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type)); if(demand)
throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type.FullName));
return DbType.Object;
} }
/// <summary> /// <summary>
...@@ -1999,7 +2015,7 @@ private static int GetNextSplitDynamic(int startIdx, string splitOn, IDataReader ...@@ -1999,7 +2015,7 @@ private static int GetNextSplitDynamic(int startIdx, string splitOn, IDataReader
{ {
if (startIdx == reader.FieldCount) if (startIdx == reader.FieldCount)
{ {
throw new ArgumentException(MultiMapSplitExceptionMessage); throw MultiMapException(reader);
} }
if (splitOn == "*") if (splitOn == "*")
...@@ -2033,7 +2049,7 @@ private static int GetNextSplit(int startIdx, string splitOn, IDataReader reader ...@@ -2033,7 +2049,7 @@ private static int GetNextSplit(int startIdx, string splitOn, IDataReader reader
} }
} }
throw new ArgumentException(MultiMapSplitExceptionMessage); throw MultiMapException(reader);
} }
private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters, bool addToCache) private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters, bool addToCache)
...@@ -2489,7 +2505,18 @@ private object SetValue(string key, object value, bool isAdd) ...@@ -2489,7 +2505,18 @@ private object SetValue(string key, object value, bool isAdd)
#endregion #endregion
} }
#endif #endif
private const string MultiMapSplitExceptionMessage = "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id"; private static Exception MultiMapException(IDataRecord reader)
{
bool hasFields = false;
try {
hasFields = reader != null && reader.FieldCount != 0;
} catch { }
if (hasFields)
return new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
else
return new InvalidOperationException("No columns were selected");
}
#if !CSHARP30 #if !CSHARP30
internal static Func<IDataReader, object> GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing) internal static Func<IDataReader, object> GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
{ {
...@@ -2501,7 +2528,7 @@ private object SetValue(string key, object value, bool isAdd) ...@@ -2501,7 +2528,7 @@ private object SetValue(string key, object value, bool isAdd)
if (fieldCount <= startBound) if (fieldCount <= startBound)
{ {
throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn"); throw MultiMapException(reader);
} }
var effectiveFieldCount = Math.Min(fieldCount - startBound, length); var effectiveFieldCount = Math.Min(fieldCount - startBound, length);
...@@ -2561,7 +2588,7 @@ private object SetValue(string key, object value, bool isAdd) ...@@ -2561,7 +2588,7 @@ private object SetValue(string key, object value, bool isAdd)
if (fieldCount <= startBound) if (fieldCount <= startBound)
{ {
throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn"); throw MultiMapException(reader);
} }
return return
...@@ -2974,7 +3001,7 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql) ...@@ -2974,7 +3001,7 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql)
continue; continue;
} }
ITypeHandler handler; ITypeHandler handler;
DbType dbType = LookupDbType(prop.PropertyType, prop.Name, out handler); DbType dbType = LookupDbType(prop.PropertyType, prop.Name, true, out handler);
if (dbType == DynamicParameters.EnumerableMultiParameter) if (dbType == DynamicParameters.EnumerableMultiParameter)
{ {
// this actually represents special handling for list types; // this actually represents special handling for list types;
...@@ -3011,8 +3038,18 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql) ...@@ -3011,8 +3038,18 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql)
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 (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] il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
EmitInt32(il, (int)dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] if (dbType == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic
{
// look it up from the param value
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param]
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value]
il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod("GetDbType", BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
}
else
{
// constant value; nice and simple
EmitInt32(il, (int)dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
}
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
} }
...@@ -3473,7 +3510,7 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3473,7 +3510,7 @@ public static void SetTypeMap(Type type, ITypeMap map)
if (reader.FieldCount <= startBound) if (reader.FieldCount <= startBound)
{ {
throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn"); throw MultiMapException(reader);
} }
var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray(); var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray();
...@@ -4492,7 +4529,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) ...@@ -4492,7 +4529,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter; var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter;
SqlMapper.ITypeHandler handler = null; SqlMapper.ITypeHandler handler = null;
if (dbType == null && val != null && !isCustomQueryParameter) dbType = SqlMapper.LookupDbType(val.GetType(), name, out handler); if (dbType == null && val != null && !isCustomQueryParameter) dbType = SqlMapper.LookupDbType(val.GetType(), name, true, out handler);
if (dbType == DynamicParameters.EnumerableMultiParameter) if (dbType == DynamicParameters.EnumerableMultiParameter)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
...@@ -4739,7 +4776,7 @@ public DynamicParameters Output<T>(T target, Expression<Func<T, object>> express ...@@ -4739,7 +4776,7 @@ public DynamicParameters Output<T>(T target, Expression<Func<T, object>> express
else else
{ {
SqlMapper.ITypeHandler handler; SqlMapper.ITypeHandler handler;
dbType = (!dbType.HasValue) ? SqlMapper.LookupDbType(targetMemberType, targetMemberType.Name, out handler) : dbType; dbType = (!dbType.HasValue) ? SqlMapper.LookupDbType(targetMemberType, targetMemberType.Name, true, out handler) : dbType;
// CameFromTemplate property would not apply here because this new param // CameFromTemplate property would not apply here because this new param
// Still needs to be added to the command // Still needs to be added to the command
......
...@@ -3984,6 +3984,40 @@ public void PseudoPositionalParameters_ExecMulti() ...@@ -3984,6 +3984,40 @@ public void PseudoPositionalParameters_ExecMulti()
} }
} }
public void QueryBasicWithoutQuery()
{
int? i = connection.Query<int?>("print 'not a query'").FirstOrDefault();
i.IsNull();
}
public void QueryComplexWithoutQuery()
{
var obj = connection.Query<Foo1>("print 'not a query'").FirstOrDefault();
obj.IsNull();
}
public void Issue182_BindDynamicObjectParametersAndColumns()
{
connection.Execute("create table #Dyno ([Id] uniqueidentifier primary key, [Name] nvarchar(50) not null, [Foo] bigint not null);");
var guid = Guid.NewGuid();
var orig = new Dyno { Name = "T Rex", Id = guid, Foo = 123L };
var result = connection.Execute("insert into #Dyno ([Id], [Name], [Foo]) values (@Id, @Name, @Foo);", orig);
var fromDb = connection.Query<Dyno>("select * from #Dyno where Id=@Id", orig).Single();
((Guid)fromDb.Id).IsEqualTo(guid);
fromDb.Name.IsEqualTo("T Rex");
((long)fromDb.Foo).IsEqualTo(123L);
}
public class Dyno
{
public dynamic Id { get; set; }
public string Name { get; set; }
public object Foo { get;set; }
}
#if POSTGRESQL #if POSTGRESQL
class Cat class Cat
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment