Commit 63d37477 authored by Marc Gravell's avatar Marc Gravell

Merge branch 'fsharp' of https://github.com/vosen/dapper-dot-net into vosen-fsharp

Conflicts:
	Dapper/SqlMapper.cs
parents e39c4370 3151c6a6
......@@ -1573,7 +1573,6 @@ static List<FieldInfo> GetSettableFields(Type t)
var il = dm.GetILGenerator();
il.DeclareLocal(typeof(int));
il.DeclareLocal(type);
bool haveEnumLocal = false;
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_0);
var properties = GetSettableProps(type);
......@@ -1595,17 +1594,10 @@ static List<FieldInfo> GetSettableFields(Type t)
names.Add(reader.GetName(i));
}
var setters = (
from n in names
let prop = properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // property case sensitive first
?? properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase)) // property case insensitive second
let field = prop != null ? null : (fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // field case sensitive third
?? fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase))) // field case insensitive fourth
select new { Name = n, Property = prop, Field = field }
).ToList();
int index = startBound;
ConstructorInfo specializedConstructor = null;
ParameterInfo[] specializedParameters = null;
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca_S, (byte)1);
......@@ -1613,32 +1605,84 @@ static List<FieldInfo> GetSettableFields(Type t)
}
else
{
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (ctor == null)
var types = new Type[length];
for (int i = startBound; i < startBound + length; i++)
{
types[i - startBound] = reader.GetFieldType(i);
}
var constructors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
bool hasDefaultConstructor = false;
foreach (ConstructorInfo ctor in constructors.OrderBy(c => c.IsPublic ? 0 : (c.IsPrivate ? 2 : 1)).ThenBy(c => c.GetParameters().Length))
{
ParameterInfo[] ctorParameters = ctor.GetParameters();
if (ctorParameters.Length == 0)
{
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_1);
hasDefaultConstructor = true;
break;
}
if (ctorParameters.Length != types.Length)
continue;
int i = 0;
for (; i < ctorParameters.Length; i++)
{
if (!String.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase))
break;
if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == LinqBinary)
continue;
var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType;
if (unboxedType != types[i]
&& !(unboxedType.IsEnum && Enum.GetUnderlyingType(unboxedType) == types[i])
&& !(unboxedType == typeof(char) && types[i] == typeof(string)))
break;
}
if (i == ctorParameters.Length)
{
specializedConstructor = ctor;
specializedParameters = ctorParameters;
break;
}
}
if (!hasDefaultConstructor && specializedConstructor == null)
{
throw new InvalidOperationException("A parameterless default constructor is required to allow for dapper materialization");
string proposedTypes = "(" + String.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")";
throw new InvalidOperationException(String.Format("A parameterless default constructor or one matching signature {0} is required for {1} materialization", proposedTypes, type.FullName));
}
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_1);
}
il.BeginExceptionBlock();
if(type.IsValueType)
{
il.Emit(OpCodes.Ldloca_S, (byte)1);// [target]
} else
}
else if(specializedConstructor == null)
{
il.Emit(OpCodes.Ldloc_1);// [target]
}
var setters = specializedConstructor != null ?
names.Select((n, i) => new { Name = n, Property = new PropInfo() { Type = specializedParameters[i].ParameterType }, Field = (FieldInfo)null }).ToList()
:
(from n in names
let prop = properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // property case sensitive first
?? properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase)) // property case insensitive second
let field = prop != null ? null : (fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // field case sensitive third
?? fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase))) // field case insensitive fourth
select new { Name = n, Property = prop, Field = field }
).ToList();
// stack is now [target]
bool first = true;
var allDone = il.DefineLabel();
int enumDeclareLocal = -1;
foreach (var item in setters)
{
if (item.Property != null || item.Field != null)
{
il.Emit(OpCodes.Dup); // stack is now [target][target]
if(specializedConstructor == null)
il.Emit(OpCodes.Dup); // stack is now [target][target]
Label isDbNullLabel = il.DefineLabel();
Label finishLabel = il.DefineLabel();
......@@ -1668,17 +1712,16 @@ static List<FieldInfo> GetSettableFields(Type t)
if (unboxType.IsEnum)
{
if (!haveEnumLocal)
if (enumDeclareLocal == -1)
{
il.DeclareLocal(typeof(string));
haveEnumLocal = true;
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
}
Label isNotString = il.DefineLabel();
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
il.Emit(OpCodes.Stloc_2); // stack is now [target][target][value-as-object][string or null]
StoreLocal(il, enumDeclareLocal); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object]
il.Emit(OpCodes.Pop); // stack is now [target][target]
......@@ -1694,7 +1737,7 @@ static List<FieldInfo> GetSettableFields(Type t)
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
if (nullUnderlyingType != null)
{
{
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][enum-value]
}
}
......@@ -1707,31 +1750,50 @@ static List<FieldInfo> GetSettableFields(Type t)
{
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
}
if (specializedConstructor == null)
{
// Store the value in the property/field
if (item.Property != null)
{
if (type.IsValueType)
{
il.Emit(OpCodes.Call, item.Property.Setter); // stack is now [target]
}
else
{
il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target]
}
}
else
{
il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
}
}
il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target]
// Store the value in the property/field
if (item.Property != null)
il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
if (specializedConstructor != null)
{
if (type.IsValueType)
il.Emit(OpCodes.Pop);
if (item.Property.Type.IsValueType)
{
il.Emit(OpCodes.Call, item.Property.Setter); // stack is now [target]
int localIndex = il.DeclareLocal(item.Property.Type).LocalIndex;
LoadLocalAddress(il, localIndex);
il.Emit(OpCodes.Initobj, item.Property.Type);
LoadLocal(il, localIndex);
}
else
{
il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target]
il.Emit(OpCodes.Ldnull);
}
}
else
{
il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Pop); // stack is now [target]
}
il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target]
il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Pop); // stack is now [target]
if (first && returnNullIfFirstMissing)
{
......@@ -1752,6 +1814,10 @@ static List<FieldInfo> GetSettableFields(Type t)
}
else
{
if (specializedConstructor != null)
{
il.Emit(OpCodes.Newobj, specializedConstructor);
}
il.Emit(OpCodes.Stloc_1); // stack is empty
}
il.MarkLabel(allDone);
......@@ -1771,6 +1837,61 @@ static List<FieldInfo> GetSettableFields(Type t)
return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader,object>));
}
private static void LoadLocal(ILGenerator il, int index)
{
if(index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
switch(index)
{
case 0: il.Emit(OpCodes.Ldloc_0); break;
case 1: il.Emit(OpCodes.Ldloc_1); break;
case 2: il.Emit(OpCodes.Ldloc_2); break;
case 3: il.Emit(OpCodes.Ldloc_3); break;
default:
if (index <= 255)
{
il.Emit(OpCodes.Ldloc_S, (byte)index);
}
else
{
il.Emit(OpCodes.Ldloc, (short)index);
}
break;
}
}
private static void StoreLocal(ILGenerator il, int index)
{
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
switch (index)
{
case 0: il.Emit(OpCodes.Stloc_0); break;
case 1: il.Emit(OpCodes.Stloc_1); break;
case 2: il.Emit(OpCodes.Stloc_2); break;
case 3: il.Emit(OpCodes.Stloc_3); break;
default:
if (index <= 255)
{
il.Emit(OpCodes.Stloc_S, (byte)index);
}
else
{
il.Emit(OpCodes.Stloc, (short)index);
}
break;
}
}
private static void LoadLocalAddress(ILGenerator il, int index)
{
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
if (index <= 255)
{
il.Emit(OpCodes.Ldloca_S, (byte)index);
}
else
{
il.Emit(OpCodes.Ldloca, (short)index);
}
}
/// <summary>
/// Throws a data exception, only used internally
/// </summary>
......
......@@ -38,28 +38,194 @@ public class ConcreteOrder : Order
}
}
class NoDefualtConstructor
class UserWithConstructor
{
public NoDefualtConstructor(int a)
public UserWithConstructor(int id, string name)
{
A = a;
Ident = id;
FullName = name;
}
public int Ident { get; set; }
public string FullName { get; set; }
}
class PostWithConstructor
{
public PostWithConstructor(int id, int ownerid, string content)
{
Ident = id;
FullContent = content;
}
public int Ident { get; set; }
public UserWithConstructor Owner { get; set; }
public string FullContent { get; set; }
public Comment Comment { get; set; }
}
public void TestMultiMapWithConstructor()
{
var createSql = @"
create table #Users (Id int, Name varchar(20))
create table #Posts (Id int, OwnerId int, Content varchar(20))
insert #Users values(99, 'Sam')
insert #Users values(2, 'I am')
insert #Posts values(1, 99, 'Sams Post1')
insert #Posts values(2, 99, 'Sams Post2')
insert #Posts values(3, null, 'no ones post')";
connection.Execute(createSql);
string sql = @"select * from #Posts p
left join #Users u on u.Id = p.OwnerId
Order by p.Id";
PostWithConstructor[] data = connection.Query<PostWithConstructor, UserWithConstructor, PostWithConstructor>(sql, (post, user) => { post.Owner = user; return post;}).ToArray();
var p = data.First();
p.FullContent.IsEqualTo("Sams Post1");
p.Ident.IsEqualTo(1);
p.Owner.FullName.IsEqualTo("Sam");
p.Owner.Ident.IsEqualTo(99);
data[2].Owner.IsNull();
connection.Execute("drop table #Users drop table #Posts");
}
class MultipleConstructors
{
public MultipleConstructors()
{
}
public MultipleConstructors(int a, string b)
{
A = a + 1;
B = b + "!";
}
public int A { get; set; }
public string B { get; set; }
}
public void EnsureNoConstructorGivesNiceError()
public void TestMultipleConstructors()
{
try
MultipleConstructors mult = connection.Query<MultipleConstructors>("select 0 A, 'Dapper' b").First();
mult.A.IsEqualTo(0);
mult.B.IsEqualTo("Dapper");
}
class ConstructorsWithAccessModifiers
{
private ConstructorsWithAccessModifiers()
{
connection.Query<NoDefualtConstructor>("select 1 A").First();
}
catch(InvalidOperationException e)
public ConstructorsWithAccessModifiers(int a, string b)
{
e.Message.IsEqualTo("A parameterless default constructor is required to allow for dapper materialization");
A = a + 1;
B = b + "!";
}
public int A { get; set; }
public string B { get; set; }
}
public void TestConstructorsWithAccessModifiers()
{
ConstructorsWithAccessModifiers value = connection.Query<ConstructorsWithAccessModifiers>("select 0 A, 'Dapper' b").First();
value.A.IsEqualTo(1);
value.B.IsEqualTo("Dapper!");
}
class NoDefaultConstructor
{
public NoDefaultConstructor(int a1, int? b1, float f1, string s1, Guid G1)
{
A = a1;
B = b1;
F = f1;
S = s1;
G = G1;
}
public int A { get; set; }
public int? B { get; set; }
public float F { get; set; }
public string S { get; set; }
public Guid G { get; set; }
}
public void TestNoDefaultConstructor()
{
var guid = Guid.NewGuid();
NoDefaultConstructor nodef = connection.Query<NoDefaultConstructor>("select CAST(NULL AS integer) A1, CAST(NULL AS integer) b1, CAST(NULL AS real) f1, 'Dapper' s1, G1 = @id", new { Id = guid }).First();
nodef.A.IsEqualTo(0);
nodef.B.IsEqualTo(null);
nodef.F.IsEqualTo(0);
nodef.S.IsEqualTo("Dapper");
nodef.G.IsEqualTo(guid);
}
class NoDefaultConstructorWithChar
{
public NoDefaultConstructorWithChar(char c1, char? c2, char? c3)
{
Char1 = c1;
Char2 = c2;
Char3 = c3;
}
public char Char1 { get; set; }
public char? Char2 { get; set; }
public char? Char3 { get; set; }
}
public void TestNoDefaultConstructorWithChar()
{
const char c1 = 'ą';
const char c3 = 'ó';
NoDefaultConstructorWithChar nodef = connection.Query<NoDefaultConstructorWithChar>("select @c1 c1, @c2 c2, @c3 c3", new { c1 = c1, c2 = (char?)null, c3 = c3 }).First();
nodef.Char1.IsEqualTo(c1);
nodef.Char2.IsEqualTo(null);
nodef.Char3.IsEqualTo(c3);
}
class NoDefaultConstructorWithEnum
{
public NoDefaultConstructorWithEnum(ShortEnum e1, ShortEnum? n1, ShortEnum? n2)
{
E = e1;
NE1 = n1;
NE2 = n2;
}
public ShortEnum E { get; set; }
public ShortEnum? NE1 { get; set; }
public ShortEnum? NE2 { get; set; }
}
public void TestNoDefaultConstructorWithEnum()
{
NoDefaultConstructorWithEnum nodef = connection.Query<NoDefaultConstructorWithEnum>("select cast(2 as smallint) E1, cast(5 as smallint) n1, cast(null as smallint) n2").First();
nodef.E.IsEqualTo(ShortEnum.Two);
nodef.NE1.IsEqualTo(ShortEnum.Five);
nodef.NE2.IsEqualTo(null);
}
class NoDefaultConstructorWithBinary
{
public System.Data.Linq.Binary Value { get; set; }
public int Ynt { get; set; }
public NoDefaultConstructorWithBinary(System.Data.Linq.Binary val)
{
Value = val;
}
}
public void TestNoDefaultConstructorBinary()
{
byte[] orig = new byte[20];
new Random(123456).NextBytes(orig);
var input = new System.Data.Linq.Binary(orig);
var output = connection.Query<NoDefaultConstructorWithBinary>("select @input as val", new { input }).First().Value;
output.ToArray().IsSequenceEqualTo(orig);
}
// http://stackoverflow.com/q/8593871
public void TestAbstractInheritance()
......@@ -212,7 +378,13 @@ class EnumParamObject
public EnumParam? B { get; set; }
public EnumParam? C { get; set; }
}
public void TestEnumParams()
class EnumParamObjectNonNullable
{
public EnumParam A { get; set; }
public EnumParam? B { get; set; }
public EnumParam? C { get; set; }
}
public void TestEnumParamsWithNullable()
{
EnumParam a = EnumParam.A;
EnumParam? b = EnumParam.B, c = null;
......@@ -222,7 +394,16 @@ public void TestEnumParams()
obj.B.IsEqualTo(EnumParam.B);
obj.C.IsEqualTo(null);
}
public void TestEnumParamsWithoutNullable()
{
EnumParam a = EnumParam.A;
EnumParam b = EnumParam.B, c = 0;
var obj = connection.Query<EnumParamObjectNonNullable>("select @a as A, @b as B, @c as C",
new { a, b, c }).Single();
obj.A.IsEqualTo(EnumParam.A);
obj.B.IsEqualTo(EnumParam.B);
obj.C.IsEqualTo((EnumParam)0);
}
public class Dog
{
public int? Age { get; set; }
......
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