Add typed procedure descriptors and Exec invoker
This commit is contained in:
@@ -5361,22 +5361,44 @@ namespace DynamORM
|
||||
// Get generic types
|
||||
IList<Type> types = binder.GetGenericTypeArguments() ?? new List<Type>();
|
||||
Type declaredResultType = null;
|
||||
string procedureName = binder.Name;
|
||||
string resultName = binder.Name;
|
||||
Type execArgumentsType = null;
|
||||
|
||||
if (binder.Name == "Exec")
|
||||
{
|
||||
if (types.Count != 1)
|
||||
throw new InvalidOperationException("Exec<TProcedure>(args) requires exactly one generic procedure descriptor type.");
|
||||
|
||||
DynamicProcedureDescriptor descriptor = DynamicProcedureDescriptor.Resolve(types[0]);
|
||||
procedureName = descriptor.ProcedureName;
|
||||
resultName = descriptor.ResultName;
|
||||
execArgumentsType = descriptor.ArgumentsType;
|
||||
declaredResultType = descriptor.ResultType;
|
||||
types = new List<Type>();
|
||||
|
||||
if (args.Length > 1)
|
||||
throw new InvalidOperationException("Exec<TProcedure>(args) accepts at most one arguments contract instance.");
|
||||
|
||||
if (args.Length == 1 && args[0] != null && !execArgumentsType.IsAssignableFrom(args[0].GetType()))
|
||||
throw new InvalidOperationException(string.Format("Exec<{0}>(args) expects argument of type '{1}', received '{2}'.", descriptor.ProcedureType.FullName, execArgumentsType.FullName, args[0].GetType().FullName));
|
||||
}
|
||||
Dictionary<string, int> retParams = null;
|
||||
|
||||
using (IDbConnection con = _db.Open())
|
||||
using (IDbCommand cmd = con.CreateCommand())
|
||||
{
|
||||
if (_prefixes == null || _prefixes.Count == 0)
|
||||
cmd.SetCommand(CommandType.StoredProcedure, binder.Name);
|
||||
cmd.SetCommand(CommandType.StoredProcedure, procedureName);
|
||||
else
|
||||
cmd.SetCommand(CommandType.StoredProcedure, string.Format("{0}.{1}", string.Join(".", _prefixes), binder.Name));
|
||||
cmd.SetCommand(CommandType.StoredProcedure, string.Format("{0}.{1}", string.Join(".", _prefixes), procedureName));
|
||||
|
||||
#region Prepare arguments
|
||||
|
||||
int alen = args.Length;
|
||||
bool retIsAdded = false;
|
||||
declaredResultType = alen == 1 ? DynamicProcedureResultBinder.GetDeclaredResultType(args[0]) : null;
|
||||
if (declaredResultType == null)
|
||||
declaredResultType = alen == 1 ? DynamicProcedureResultBinder.GetDeclaredResultType(args[0]) : null;
|
||||
|
||||
if (alen > 0)
|
||||
{
|
||||
@@ -5561,7 +5583,7 @@ namespace DynamORM
|
||||
{
|
||||
using (IDataReader rdr = cmd.ExecuteReader())
|
||||
using (IDataReader cache = rdr.CachedReader())
|
||||
mainResult = cache.ToDataTable(binder.Name);
|
||||
mainResult = cache.ToDataTable(resultName);
|
||||
}
|
||||
else if (types[0].IsGenericEnumerable())
|
||||
{
|
||||
@@ -5688,9 +5710,9 @@ namespace DynamORM
|
||||
if (mainResult != null)
|
||||
{
|
||||
if (mainResult == DBNull.Value)
|
||||
res.Add(binder.Name, null);
|
||||
res.Add(resultName, null);
|
||||
else
|
||||
res.Add(binder.Name, mainResult);
|
||||
res.Add(resultName, mainResult);
|
||||
}
|
||||
foreach (KeyValuePair<string, int> pos in retParams)
|
||||
res.Add(pos.Key, ((IDbDataParameter)cmd.Parameters[pos.Value]).Value);
|
||||
@@ -5705,14 +5727,14 @@ namespace DynamORM
|
||||
result = res.ToDynamic();
|
||||
}
|
||||
else if (declaredResultType != null)
|
||||
result = DynamicProcedureResultBinder.BindPayload(declaredResultType, binder.Name, mainResult, res.Where(x => x.Key != binder.Name).ToDictionary(x => x.Key, x => x.Value), mainResult != null && declaredResultType.IsInstanceOfType(mainResult) ? mainResult : null);
|
||||
result = DynamicProcedureResultBinder.BindPayload(declaredResultType, resultName, mainResult, res.Where(x => x.Key != resultName).ToDictionary(x => x.Key, x => x.Value), mainResult != null && declaredResultType.IsInstanceOfType(mainResult) ? mainResult : null);
|
||||
else
|
||||
result = res.ToDynamic();
|
||||
}
|
||||
else if (declaredResultType != null && mainResult != null && declaredResultType.IsInstanceOfType(mainResult))
|
||||
result = mainResult;
|
||||
else if (declaredResultType != null)
|
||||
result = DynamicProcedureResultBinder.BindPayload(declaredResultType, binder.Name, mainResult, null);
|
||||
result = DynamicProcedureResultBinder.BindPayload(declaredResultType, resultName, mainResult, null);
|
||||
else
|
||||
result = mainResult;
|
||||
|
||||
@@ -14440,6 +14462,67 @@ namespace DynamORM
|
||||
return resultTable;
|
||||
}
|
||||
}
|
||||
internal sealed class DynamicProcedureDescriptor
|
||||
{
|
||||
public Type ProcedureType { get; set; }
|
||||
public Type ArgumentsType { get; set; }
|
||||
public Type ResultType { get; set; }
|
||||
public string ProcedureName { get; set; }
|
||||
public string ResultName { get; set; }
|
||||
|
||||
internal static DynamicProcedureDescriptor Resolve(Type procedureType)
|
||||
{
|
||||
if (procedureType == null)
|
||||
throw new ArgumentNullException("procedureType");
|
||||
|
||||
Type current = procedureType;
|
||||
Type argumentsType = null;
|
||||
Type resultType = null;
|
||||
|
||||
while (current != null && current != typeof(object))
|
||||
{
|
||||
if (current.IsGenericType)
|
||||
{
|
||||
Type genericDefinition = current.GetGenericTypeDefinition();
|
||||
Type[] genericArguments = current.GetGenericArguments();
|
||||
|
||||
if (genericDefinition == typeof(Procedure<>) && genericArguments.Length == 1)
|
||||
{
|
||||
argumentsType = genericArguments[0];
|
||||
break;
|
||||
}
|
||||
if (genericDefinition == typeof(Procedure<,>) && genericArguments.Length == 2)
|
||||
{
|
||||
argumentsType = genericArguments[0];
|
||||
resultType = genericArguments[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
current = current.BaseType;
|
||||
}
|
||||
if (argumentsType == null)
|
||||
throw new InvalidOperationException(string.Format("Type '{0}' is not a typed procedure descriptor.", procedureType.FullName));
|
||||
|
||||
if (!typeof(IProcedureParameters).IsAssignableFrom(argumentsType))
|
||||
throw new InvalidOperationException(string.Format("Procedure descriptor '{0}' declares argument type '{1}' that does not implement IProcedureParameters.", procedureType.FullName, argumentsType.FullName));
|
||||
|
||||
ProcedureAttribute attr = procedureType.GetCustomAttributes(typeof(ProcedureAttribute), true)
|
||||
.Cast<ProcedureAttribute>()
|
||||
.FirstOrDefault();
|
||||
|
||||
string name = attr != null && !string.IsNullOrEmpty(attr.Name) ? attr.Name : procedureType.Name;
|
||||
string owner = attr != null ? attr.Owner : null;
|
||||
|
||||
return new DynamicProcedureDescriptor
|
||||
{
|
||||
ProcedureType = procedureType,
|
||||
ArgumentsType = argumentsType,
|
||||
ResultType = resultType,
|
||||
ProcedureName = string.IsNullOrEmpty(owner) ? name : string.Format("{0}.{1}", owner, name),
|
||||
ResultName = name
|
||||
};
|
||||
}
|
||||
}
|
||||
internal static class DynamicProcedureParameterBinder
|
||||
{
|
||||
internal sealed class BindingResult
|
||||
@@ -17935,6 +18018,19 @@ namespace DynamORM
|
||||
public class IgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
||||
/// <summary>Allows to add stored procedure metadata to class.</summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ProcedureAttribute : Attribute
|
||||
{
|
||||
/// <summary>Gets or sets procedure owner name.</summary>
|
||||
public string Owner { get; set; }
|
||||
|
||||
/// <summary>Gets or sets procedure name.</summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether metadata overrides other defaults.</summary>
|
||||
public bool Override { get; set; }
|
||||
}
|
||||
/// <summary>Declares metadata for object-based stored procedure parameters.</summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ProcedureParameterAttribute : ColumnAttribute
|
||||
@@ -18456,6 +18552,19 @@ namespace DynamORM
|
||||
_database = null;
|
||||
}
|
||||
}
|
||||
/// <summary>Base class for typed stored procedure descriptors.</summary>
|
||||
/// <typeparam name="TArgs">Procedure arguments contract.</typeparam>
|
||||
public abstract class Procedure<TArgs>
|
||||
where TArgs : IProcedureParameters
|
||||
{
|
||||
}
|
||||
/// <summary>Base class for typed stored procedure descriptors with explicit result model.</summary>
|
||||
/// <typeparam name="TArgs">Procedure arguments contract.</typeparam>
|
||||
/// <typeparam name="TResult">Procedure result model.</typeparam>
|
||||
public abstract class Procedure<TArgs, TResult> : Procedure<TArgs>
|
||||
where TArgs : IProcedureParameters
|
||||
{
|
||||
}
|
||||
/// <summary>Marks an object as an explicit stored procedure parameter contract.</summary>
|
||||
public interface IProcedureParameters
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user