Improve declarative procedure result-set binding

This commit is contained in:
2026-02-27 15:57:06 +01:00
parent 99ff6b3d29
commit 6ebda34a04
6 changed files with 194 additions and 67 deletions

View File

@@ -5542,7 +5542,7 @@ namespace DynamORM
object mainResult = null;
if (types.Count == 0 && DynamicProcedureResultBinder.HasDeclaredResultBinding(declaredResultType))
if (types.Count == 0 && DynamicProcedureResultBinder.CanReadResults(declaredResultType))
{
using (IDataReader rdr = cmd.ExecuteReader())
using (IDataReader cache = rdr.CachedReader())
@@ -14564,10 +14564,25 @@ namespace DynamORM
}
internal static class DynamicProcedureResultBinder
{
private sealed class ResultPropertyBinding
private sealed class ResultMemberBinding
{
public ProcedureResultAttribute Attribute { get; set; }
public PropertyInfo Property { get; set; }
public MemberInfo Member { get; set; }
public Type MemberType { get; set; }
public int SortOrder { get; set; }
public void SetValue(object instance, object value)
{
PropertyInfo property = Member as PropertyInfo;
if (property != null)
{
property.SetValue(instance, value, null);
return;
}
FieldInfo field = Member as FieldInfo;
if (field != null)
field.SetValue(instance, value);
}
}
internal static bool IsProcedureContract(object item)
{
@@ -14583,10 +14598,14 @@ namespace DynamORM
return iface == null ? null : iface.GetGenericArguments()[0];
}
internal static bool HasDeclaredResultBinding(Type resultType)
internal static bool CanReadResults(Type resultType)
{
return resultType != null &&
(typeof(IProcedureResultReader).IsAssignableFrom(resultType) || GetResultPropertyBindings(resultType).Count > 0);
(typeof(IProcedureResultReader).IsAssignableFrom(resultType) || GetResultMemberBindings(resultType).Count > 0);
}
internal static bool HasDeclaredResultBinding(Type resultType)
{
return CanReadResults(resultType);
}
internal static object CreateDeclaredResult(Type resultType)
{
@@ -14601,14 +14620,14 @@ namespace DynamORM
}
internal static object ReadDeclaredResult(Type resultType, IDataReader reader)
{
if (!HasDeclaredResultBinding(resultType))
if (!CanReadResults(resultType))
throw new InvalidOperationException(string.Format("Type '{0}' does not declare a supported procedure result binding.", resultType == null ? "<null>" : resultType.FullName));
object instance = CreateDeclaredResult(resultType);
IList<ResultPropertyBinding> bindings = GetResultPropertyBindings(resultType);
IList<ResultMemberBinding> bindings = GetResultMemberBindings(resultType);
if (bindings.Count > 0)
BindResultProperties(instance, reader, bindings);
BindResultMembers(instance, reader, bindings);
else
((IProcedureResultReader)instance).ReadResults(reader);
@@ -14637,21 +14656,35 @@ namespace DynamORM
return payload.ToDynamic();
}
private static IList<ResultPropertyBinding> GetResultPropertyBindings(Type resultType)
private static IList<ResultMemberBinding> GetResultMemberBindings(Type resultType)
{
return resultType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
var properties = resultType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.CanWrite && x.GetIndexParameters().Length == 0)
.Select(x => new ResultPropertyBinding
.Select(x => new ResultMemberBinding
{
Property = x,
Member = x,
MemberType = x.PropertyType,
SortOrder = x.MetadataToken,
Attribute = x.GetCustomAttributes(typeof(ProcedureResultAttribute), true).Cast<ProcedureResultAttribute>().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<ProcedureResultAttribute>().FirstOrDefault()
});
return properties.Concat(fields)
.Where(x => x.Attribute != null)
.OrderBy(x => x.Attribute.ResultIndex)
.ThenBy(x => x.Property.MetadataToken)
.ThenBy(x => x.SortOrder)
.ToList();
}
private static void BindResultProperties(object instance, IDataReader reader, IList<ResultPropertyBinding> bindings)
private static void BindResultMembers(object instance, IDataReader reader, IList<ResultMemberBinding> bindings)
{
ValidateBindings(instance.GetType(), bindings);
@@ -14660,7 +14693,7 @@ namespace DynamORM
for (int i = 0; i < bindings.Count; i++)
{
ResultPropertyBinding binding = bindings[i];
ResultMemberBinding binding = bindings[i];
while (hasCurrent && currentIndex < binding.Attribute.ResultIndex)
{
hasCurrent = reader.NextResult();
@@ -14669,8 +14702,8 @@ namespace DynamORM
if (!hasCurrent || currentIndex != binding.Attribute.ResultIndex)
break;
object value = ReadResultValue(binding.Property.PropertyType, binding.Attribute, reader);
binding.Property.SetValue(instance, value, null);
object value = ReadResultValue(binding.MemberType, binding.Attribute, reader);
binding.SetValue(instance, value);
if (i + 1 < bindings.Count)
{
@@ -14679,7 +14712,7 @@ namespace DynamORM
}
}
}
private static void ValidateBindings(Type resultType, IList<ResultPropertyBinding> bindings)
private static void ValidateBindings(Type resultType, IList<ResultMemberBinding> bindings)
{
var duplicates = bindings.GroupBy(x => x.Attribute.ResultIndex).FirstOrDefault(x => x.Count() > 1);
if (duplicates != null)
@@ -14709,6 +14742,7 @@ 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;
@@ -14725,17 +14759,21 @@ namespace DynamORM
if (!haveRow || value == null)
return propertyType.GetDefaultValue();
if (propertyType == typeof(Guid))
if (targetType == typeof(Guid))
{
Guid g;
if (Guid.TryParse(value.ToString(), out g))
return g;
return propertyType == typeof(Guid) ? (object)g : new Guid?(g);
return propertyType.GetDefaultValue();
}
return propertyType.CastObject(value);
if (targetType.IsEnum)
return Enum.ToObject(targetType, value);
return targetType == propertyType ? propertyType.CastObject(value) : targetType.CastObject(value);
}
private static object ReadSimpleList(Type propertyType, Type elementType, ProcedureResultAttribute attr, IDataReader reader)
{
Type targetElementType = Nullable.GetUnderlyingType(elementType) ?? elementType;
Type listType = typeof(List<>).MakeGenericType(elementType);
var list = (System.Collections.IList)Activator.CreateInstance(listType);
int ordinal = -1;
@@ -14754,13 +14792,18 @@ namespace DynamORM
continue;
}
object value = reader[ordinal];
if (elementType == typeof(Guid))
if (targetElementType == typeof(Guid))
{
Guid g;
list.Add(Guid.TryParse(value.ToString(), out g) ? (object)g : elementType.GetDefaultValue());
if (Guid.TryParse(value.ToString(), out g))
list.Add(elementType == typeof(Guid) ? (object)g : new Guid?(g));
else
list.Add(elementType.GetDefaultValue());
}
else if (targetElementType.IsEnum)
list.Add(Enum.ToObject(targetElementType, value));
else
list.Add(elementType.CastObject(value));
list.Add(targetElementType == elementType ? elementType.CastObject(value) : targetElementType.CastObject(value));
}
if (propertyType.IsArray)
{
@@ -14834,12 +14877,7 @@ namespace DynamORM
{
if (attr == null || string.IsNullOrEmpty(attr.ColumnName))
return 0;
int ordinal = reader.GetOrdinal(attr.ColumnName);
if (ordinal < 0)
throw new IndexOutOfRangeException(attr.ColumnName);
return ordinal;
return reader.GetOrdinal(attr.ColumnName);
}
}
/// <summary>Framework detection and specific implementations.</summary>
@@ -17886,7 +17924,7 @@ namespace DynamORM
}
}
/// <summary>Declares mapping of a typed procedure result property to a specific result set.</summary>
[AttributeUsage(AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class ProcedureResultAttribute : Attribute
{
/// <summary>Initializes a new instance of the <see cref="ProcedureResultAttribute"/> class.</summary>