diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index 7784c0c..7d2786a 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -14648,13 +14648,17 @@ namespace DynamORM payload[item.Key] = item.Value == DBNull.Value ? null : item.Value; DynamicTypeMap mapper = DynamicMapperCache.GetMapper(resultType); + object instance = existing; + if (mapper != null) - return mapper.Map(payload.ToDynamic(), existing ?? mapper.Creator()); + instance = mapper.Map(payload.ToDynamic(), existing ?? mapper.Creator()); + else if (instance == null) + instance = payload.ToDynamic(); - if (existing != null) - return existing; + if (instance != null && resultType.IsInstanceOfType(instance)) + BindMainResultMembers(instance, mainResult); - return payload.ToDynamic(); + return instance; } private static IList GetResultMemberBindings(Type resultType) { @@ -14680,10 +14684,39 @@ namespace DynamORM return properties.Concat(fields) .Where(x => x.Attribute != null) + .Where(x => !IsMainResultBinding(x.Attribute)) .OrderBy(x => x.Attribute.ResultIndex) .ThenBy(x => x.SortOrder) .ToList(); } + private static IList GetMainResultBindings(Type resultType) + { + var properties = resultType.GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(x => x.CanWrite && x.GetIndexParameters().Length == 0) + .Select(x => new ResultMemberBinding + { + Member = x, + MemberType = x.PropertyType, + SortOrder = x.MetadataToken, + Attribute = x.GetCustomAttributes(typeof(ProcedureResultAttribute), true).Cast().FirstOrDefault() + }); + + var fields = resultType.GetFields(BindingFlags.Instance | BindingFlags.Public) + .Where(x => !x.IsInitOnly && !x.IsLiteral) + .Select(x => new ResultMemberBinding + { + Member = x, + MemberType = x.FieldType, + SortOrder = x.MetadataToken, + Attribute = x.GetCustomAttributes(typeof(ProcedureResultAttribute), true).Cast().FirstOrDefault() + }); + + return properties.Concat(fields) + .Where(x => x.Attribute != null) + .Where(x => IsMainResultBinding(x.Attribute)) + .OrderBy(x => x.SortOrder) + .ToList(); + } private static void BindResultMembers(object instance, IDataReader reader, IList bindings) { ValidateBindings(instance.GetType(), bindings); @@ -14718,6 +14751,21 @@ namespace DynamORM if (duplicates != null) throw new InvalidOperationException(string.Format("Type '{0}' defines multiple ProcedureResultAttribute bindings for result index {1}.", resultType.FullName, duplicates.Key)); } + private static void BindMainResultMembers(object instance, object mainResult) + { + if (instance == null) + return; + + IList bindings = GetMainResultBindings(instance.GetType()); + if (bindings.Count == 0) + return; + if (bindings.Count > 1) + throw new InvalidOperationException(string.Format("Type '{0}' defines multiple ProcedureResultAttribute bindings for the main procedure result.", instance.GetType().FullName)); + + ResultMemberBinding binding = bindings[0]; + object value = ConvertScalarValue(binding.MemberType, mainResult == DBNull.Value ? null : mainResult); + binding.SetValue(instance, value); + } private static object ReadResultValue(Type propertyType, ProcedureResultAttribute attr, IDataReader reader) { Type elementType; @@ -14742,7 +14790,6 @@ namespace DynamORM } private static object ReadSimpleValue(Type propertyType, ProcedureResultAttribute attr, IDataReader reader) { - Type targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; object value = null; int ordinal = -1; bool haveRow = false; @@ -14759,17 +14806,7 @@ namespace DynamORM if (!haveRow || value == null) return propertyType.GetDefaultValue(); - if (targetType == typeof(Guid)) - { - Guid g; - if (Guid.TryParse(value.ToString(), out g)) - return propertyType == typeof(Guid) ? (object)g : new Guid?(g); - return propertyType.GetDefaultValue(); - } - if (targetType.IsEnum) - return Enum.ToObject(targetType, value); - - return targetType == propertyType ? propertyType.CastObject(value) : targetType.CastObject(value); + return ConvertScalarValue(propertyType, value); } private static object ReadSimpleList(Type propertyType, Type elementType, ProcedureResultAttribute attr, IDataReader reader) { @@ -14879,6 +14916,29 @@ namespace DynamORM return 0; return reader.GetOrdinal(attr.ColumnName); } + private static bool IsMainResultBinding(ProcedureResultAttribute attribute) + { + return attribute != null && attribute.ResultIndex < 0; + } + private static object ConvertScalarValue(Type targetType, object value) + { + if (value == null) + return targetType.GetDefaultValue(); + + Type underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; + + if (underlyingType == typeof(Guid)) + { + Guid g; + if (Guid.TryParse(value.ToString(), out g)) + return targetType == typeof(Guid) ? (object)g : new Guid?(g); + return targetType.GetDefaultValue(); + } + if (underlyingType.IsEnum) + return Enum.ToObject(underlyingType, value); + + return underlyingType == targetType ? targetType.CastObject(value) : underlyingType.CastObject(value); + } } /// Framework detection and specific implementations. public static class FrameworkTools @@ -17927,6 +17987,14 @@ namespace DynamORM [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class ProcedureResultAttribute : Attribute { + /// Main procedure result marker. + public const int MainResultIndex = -1; + + /// Initializes a new instance of the class. + public ProcedureResultAttribute() + : this(MainResultIndex) + { + } /// Initializes a new instance of the class. public ProcedureResultAttribute(int resultIndex) { diff --git a/DynamORM.Tests/Helpers/ProcedureParameterModels.cs b/DynamORM.Tests/Helpers/ProcedureParameterModels.cs index e86e8e6..504c98f 100644 --- a/DynamORM.Tests/Helpers/ProcedureParameterModels.cs +++ b/DynamORM.Tests/Helpers/ProcedureParameterModels.cs @@ -46,6 +46,21 @@ namespace DynamORM.Tests.Helpers public int Status { get; set; } } + public class ProcedureParameterAttributeMainResult + { + [ProcedureResult] + public int MainResult { get; set; } + + [DynamORM.Mapper.Column("status")] + public int Status { get; set; } + } + + public class ProcedureParameterAttributeMainResultField + { + [ProcedureResult(-1)] + public int MainResult; + } + public class ProcedureMultiResult : IProcedureResultReader { [DynamORM.Mapper.Column("sp_Multi")] diff --git a/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs b/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs index 648abe5..df49905 100644 --- a/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs +++ b/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs @@ -143,6 +143,36 @@ namespace DynamORM.Tests.Procedure Assert.AreEqual(3, result.Status); } + [Test] + public void TestDeclaredResultPayloadBindingSupportsProcedureResultMainResultProperty() + { + var result = DynamicProcedureResultBinder.BindPayload( + typeof(ProcedureParameterAttributeMainResult), + "sp_Test", + 27, + new System.Collections.Generic.Dictionary + { + { "status", 6 } + }) as ProcedureParameterAttributeMainResult; + + Assert.NotNull(result); + Assert.AreEqual(27, result.MainResult); + Assert.AreEqual(6, result.Status); + } + + [Test] + public void TestDeclaredResultPayloadBindingSupportsProcedureResultMainResultField() + { + var result = DynamicProcedureResultBinder.BindPayload( + typeof(ProcedureParameterAttributeMainResultField), + "sp_Test", + 33, + null) as ProcedureParameterAttributeMainResultField; + + Assert.NotNull(result); + Assert.AreEqual(33, result.MainResult); + } + [Test] public void TestDeclaredResultReaderCanConsumeMultipleResultSets() { diff --git a/DynamORM/Helpers/DynamicProcedureResultBinder.cs b/DynamORM/Helpers/DynamicProcedureResultBinder.cs index 4759935..96e415c 100644 --- a/DynamORM/Helpers/DynamicProcedureResultBinder.cs +++ b/DynamORM/Helpers/DynamicProcedureResultBinder.cs @@ -130,13 +130,17 @@ namespace DynamORM.Helpers payload[item.Key] = item.Value == DBNull.Value ? null : item.Value; DynamicTypeMap mapper = DynamicMapperCache.GetMapper(resultType); + object instance = existing; + if (mapper != null) - return mapper.Map(payload.ToDynamic(), existing ?? mapper.Creator()); + instance = mapper.Map(payload.ToDynamic(), existing ?? mapper.Creator()); + else if (instance == null) + instance = payload.ToDynamic(); - if (existing != null) - return existing; + if (instance != null && resultType.IsInstanceOfType(instance)) + BindMainResultMembers(instance, mainResult); - return payload.ToDynamic(); + return instance; } private static IList GetResultMemberBindings(Type resultType) @@ -163,11 +167,41 @@ namespace DynamORM.Helpers return properties.Concat(fields) .Where(x => x.Attribute != null) + .Where(x => !IsMainResultBinding(x.Attribute)) .OrderBy(x => x.Attribute.ResultIndex) .ThenBy(x => x.SortOrder) .ToList(); } + private static IList GetMainResultBindings(Type resultType) + { + var properties = resultType.GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(x => x.CanWrite && x.GetIndexParameters().Length == 0) + .Select(x => new ResultMemberBinding + { + Member = x, + MemberType = x.PropertyType, + SortOrder = x.MetadataToken, + Attribute = x.GetCustomAttributes(typeof(ProcedureResultAttribute), true).Cast().FirstOrDefault() + }); + + var fields = resultType.GetFields(BindingFlags.Instance | BindingFlags.Public) + .Where(x => !x.IsInitOnly && !x.IsLiteral) + .Select(x => new ResultMemberBinding + { + Member = x, + MemberType = x.FieldType, + SortOrder = x.MetadataToken, + Attribute = x.GetCustomAttributes(typeof(ProcedureResultAttribute), true).Cast().FirstOrDefault() + }); + + return properties.Concat(fields) + .Where(x => x.Attribute != null) + .Where(x => IsMainResultBinding(x.Attribute)) + .OrderBy(x => x.SortOrder) + .ToList(); + } + private static void BindResultMembers(object instance, IDataReader reader, IList bindings) { ValidateBindings(instance.GetType(), bindings); @@ -205,6 +239,22 @@ namespace DynamORM.Helpers throw new InvalidOperationException(string.Format("Type '{0}' defines multiple ProcedureResultAttribute bindings for result index {1}.", resultType.FullName, duplicates.Key)); } + private static void BindMainResultMembers(object instance, object mainResult) + { + if (instance == null) + return; + + IList bindings = GetMainResultBindings(instance.GetType()); + if (bindings.Count == 0) + return; + if (bindings.Count > 1) + throw new InvalidOperationException(string.Format("Type '{0}' defines multiple ProcedureResultAttribute bindings for the main procedure result.", instance.GetType().FullName)); + + ResultMemberBinding binding = bindings[0]; + object value = ConvertScalarValue(binding.MemberType, mainResult == DBNull.Value ? null : mainResult); + binding.SetValue(instance, value); + } + private static object ReadResultValue(Type propertyType, ProcedureResultAttribute attr, IDataReader reader) { Type elementType; @@ -231,7 +281,6 @@ namespace DynamORM.Helpers private static object ReadSimpleValue(Type propertyType, ProcedureResultAttribute attr, IDataReader reader) { - Type targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; object value = null; int ordinal = -1; bool haveRow = false; @@ -249,18 +298,7 @@ namespace DynamORM.Helpers if (!haveRow || value == null) return propertyType.GetDefaultValue(); - if (targetType == typeof(Guid)) - { - Guid g; - if (Guid.TryParse(value.ToString(), out g)) - return propertyType == typeof(Guid) ? (object)g : new Guid?(g); - return propertyType.GetDefaultValue(); - } - - if (targetType.IsEnum) - return Enum.ToObject(targetType, value); - - return targetType == propertyType ? propertyType.CastObject(value) : targetType.CastObject(value); + return ConvertScalarValue(propertyType, value); } private static object ReadSimpleList(Type propertyType, Type elementType, ProcedureResultAttribute attr, IDataReader reader) @@ -384,5 +422,31 @@ namespace DynamORM.Helpers return 0; return reader.GetOrdinal(attr.ColumnName); } + + private static bool IsMainResultBinding(ProcedureResultAttribute attribute) + { + return attribute != null && attribute.ResultIndex < 0; + } + + private static object ConvertScalarValue(Type targetType, object value) + { + if (value == null) + return targetType.GetDefaultValue(); + + Type underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; + + if (underlyingType == typeof(Guid)) + { + Guid g; + if (Guid.TryParse(value.ToString(), out g)) + return targetType == typeof(Guid) ? (object)g : new Guid?(g); + return targetType.GetDefaultValue(); + } + + if (underlyingType.IsEnum) + return Enum.ToObject(underlyingType, value); + + return underlyingType == targetType ? targetType.CastObject(value) : underlyingType.CastObject(value); + } } } diff --git a/DynamORM/Mapper/ProcedureResultAttribute.cs b/DynamORM/Mapper/ProcedureResultAttribute.cs index a060834..b90a338 100644 --- a/DynamORM/Mapper/ProcedureResultAttribute.cs +++ b/DynamORM/Mapper/ProcedureResultAttribute.cs @@ -34,6 +34,15 @@ namespace DynamORM.Mapper [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class ProcedureResultAttribute : Attribute { + /// Main procedure result marker. + public const int MainResultIndex = -1; + + /// Initializes a new instance of the class. + public ProcedureResultAttribute() + : this(MainResultIndex) + { + } + /// Initializes a new instance of the class. public ProcedureResultAttribute(int resultIndex) {