From 9846b9af59beeed86c95c8e4827afff87bc7f44e Mon Sep 17 00:00:00 2001 From: kevin-montrose Date: Tue, 28 Oct 2014 14:08:10 -0400 Subject: [PATCH] whack at an explicit constructor switch --- Dapper NET40/SqlMapper.cs | 104 ++++++++++++++++++++++++++++++++++---- Tests/Tests.cs | 37 ++++++++++++++ 2 files changed, 131 insertions(+), 10 deletions(-) diff --git a/Dapper NET40/SqlMapper.cs b/Dapper NET40/SqlMapper.cs index 67c7165..26e9095 100644 --- a/Dapper NET40/SqlMapper.cs +++ b/Dapper NET40/SqlMapper.cs @@ -389,6 +389,15 @@ public interface ITypeMap /// Matching constructor or default one ConstructorInfo FindConstructor(string[] names, Type[] types); + /// + /// Returns a constructor which should *always* be used. + /// + /// Parameters will be default values, nulls for reference types and zero'd for value types. + /// + /// Use this class to force object creation away from parameterless constructors you dn't control. + /// + ConstructorInfo FindExplicitConstructor(); + /// /// Gets mapping for constructor parameter /// @@ -3557,26 +3566,67 @@ public static void SetTypeMap(Type type, ITypeMap map) types[i - startBound] = reader.GetFieldType(i); } - var ctor = typeMap.FindConstructor(names, types); - if (ctor == null) + var explicitConstr = typeMap.FindExplicitConstructor(); + if (explicitConstr != null) { - 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)); - } + var structLocals = new Dictionary(); - if (ctor.GetParameters().Length == 0) - { - il.Emit(OpCodes.Newobj, ctor); + var consPs = explicitConstr.GetParameters(); + foreach(var p in consPs) + { + if(!p.ParameterType.IsValueType) + { + il.Emit(OpCodes.Ldnull); + } + else + { + LocalBuilder loc; + if(!structLocals.TryGetValue(p.ParameterType, out loc)) + { + structLocals[p.ParameterType] = loc = il.DeclareLocal(p.ParameterType); + } + + il.Emit(OpCodes.Ldloca, (short)loc.LocalIndex); + il.Emit(OpCodes.Initobj, p.ParameterType); + il.Emit(OpCodes.Ldloca, (short)loc.LocalIndex); + il.Emit(OpCodes.Ldobj, p.ParameterType); + } + } + + il.Emit(OpCodes.Newobj, explicitConstr); il.Emit(OpCodes.Stloc_1); supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); - if(supportInitialize) + if (supportInitialize) { il.Emit(OpCodes.Ldloc_1); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("BeginInit"), null); } } else - specializedConstructor = ctor; + { + var ctor = typeMap.FindConstructor(names, types); + if (ctor == null) + { + 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)); + } + + if (ctor.GetParameters().Length == 0) + { + il.Emit(OpCodes.Newobj, ctor); + il.Emit(OpCodes.Stloc_1); + supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); + if (supportInitialize) + { + il.Emit(OpCodes.Ldloc_1); + il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("BeginInit"), null); + } + } + else + { + specializedConstructor = ctor; + } + } } il.BeginExceptionBlock(); @@ -5216,6 +5266,22 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types) return null; } + /// + /// Returns the constructor, if any, that has the ExplicitConstructorAttribute on it. + /// + public ConstructorInfo FindExplicitConstructor() + { + var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var withAttr = constructors.Where(c => c.GetCustomAttributes(typeof(ExplicitConstructorAttribute), true).Length > 0).ToList(); + + if (withAttr.Count == 1) + { + return withAttr[0]; + } + + return null; + } + /// /// Gets mapping for constructor parameter /// @@ -5306,6 +5372,15 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types) return _type.GetConstructor(new Type[0]); } + /// + /// Always returns null + /// + /// + public ConstructorInfo FindExplicitConstructor() + { + return null; + } + /// /// Not impelmeneted as far as default constructor used for all cases /// @@ -5544,6 +5619,15 @@ public interface IWrappedDataReader : IDataReader IDbCommand Command { get; } } + /// + /// Tell Dapper to use an explicit constructor, passing nulls or 0s for all parameters + /// + [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false)] + public sealed class ExplicitConstructorAttribute : Attribute + { + + } + // Define DAPPER_MAKE_PRIVATE if you reference Dapper by source // and you like to make the Dapper types private (in order to avoid // conflicts with other projects that also reference Dapper by source) diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 4509592..928664b 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -4086,6 +4086,43 @@ public void Issue192_InParameterWorksWithSimilarNamesWithUnicode() ((int)rows.Field).IsEqualTo(2); ((int)rows.Field_1).IsEqualTo(2); } + + class _ExplicitConstructors + { + public int Field { get; set; } + public int Field_1 { get; set; } + + private bool WentThroughProperConstructor; + + public _ExplicitConstructors() { } + + [ExplicitConstructor] + public _ExplicitConstructors(string foo, int bar) + { + WentThroughProperConstructor = true; + } + + public bool GetWentThroughProperConstructor() + { + return WentThroughProperConstructor; + } + } + + public void ExplicitConstructors() + { + var rows = connection.Query<_ExplicitConstructors>(@" +declare @ExplicitConstructors table ( + Field INT NOT NULL PRIMARY KEY IDENTITY(1,1), + Field_1 INT NOT NULL); +insert @ExplicitConstructors(Field_1) values (1); +SELECT * FROM @ExplicitConstructors" +).ToList(); + + rows.Count.IsEqualTo(1); + rows[0].Field.IsEqualTo(1); + rows[0].Field_1.IsEqualTo(1); + rows[0].GetWentThroughProperConstructor().IsTrue(); + } #if POSTGRESQL class Cat -- GitLab