Improve declarative procedure result-set binding
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user