Expose typed procedure execution as explicit APIs

This commit is contained in:
2026-02-27 16:34:16 +01:00
parent 416404f8d1
commit f8353e7488
4 changed files with 122 additions and 66 deletions

View File

@@ -2681,6 +2681,24 @@ namespace DynamORM
.ExecuteNonQuery();
}
}
/// <summary>Execute typed stored procedure descriptor.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <returns>Procedure result.</returns>
public virtual object Procedure<TProcedure>()
{
return Procedure<TProcedure>(null);
}
/// <summary>Execute typed stored procedure descriptor.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <param name="args">Procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual object Procedure<TProcedure>(object args)
{
if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
throw new InvalidOperationException("Database connection desn't support stored procedures.");
return new DynamicProcedureInvoker(this).Exec<TProcedure>(args);
}
#endregion Procedure
#region Execute
@@ -5331,6 +5349,23 @@ namespace DynamORM
_prefixes = prefixes;
_db = db;
}
/// <summary>Execute typed stored procedure descriptor.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <param name="args">Optional procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual object Exec<TProcedure>(object args = null)
{
DynamicProcedureDescriptor descriptor = DynamicProcedureDescriptor.Resolve(typeof(TProcedure));
return InvokeProcedure(
descriptor.ProcedureName,
descriptor.ResultName,
new List<Type>(),
args == null ? new object[0] : new[] { args },
new CallInfo(args == null ? 0 : 1),
descriptor.ResultType,
descriptor.ArgumentsType,
descriptor.ProcedureType);
}
/// <summary>This is where the magic begins.</summary>
/// <param name="binder">Binder to create owner.</param>
/// <param name="result">Binder invoke result.</param>
@@ -5360,31 +5395,23 @@ 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")
result = InvokeProcedure(binder.Name, binder.Name, types, args, info, null, null, null);
return true;
}
internal object InvokeProcedure(string procedureName, string resultName, IList<Type> types, object[] args, CallInfo info, Type declaredResultType, Type expectedArgumentsType, Type procedureType)
{
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>();
object result;
Dictionary<string, int> retParams = null;
if (expectedArgumentsType != null)
{
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));
if (args.Length == 1 && args[0] != null && !expectedArgumentsType.IsAssignableFrom(args[0].GetType()))
throw new InvalidOperationException(string.Format("Exec<{0}>(args) expects argument of type '{1}', received '{2}'.", procedureType == null ? expectedArgumentsType.FullName : procedureType.FullName, expectedArgumentsType.FullName, args[0].GetType().FullName));
}
Dictionary<string, int> retParams = null;
using (IDbConnection con = _db.Open())
using (IDbCommand cmd = con.CreateCommand())
{
@@ -5740,7 +5767,7 @@ namespace DynamORM
#endregion Handle out params
}
return true;
return result;
}
/// <summary>Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.</summary>

View File

@@ -151,9 +151,9 @@ namespace DynamORM.Tests.Procedure
}
[Test]
public void TestExecRejectsWrongArgumentsType()
public void TestExecMethodRejectsWrongArgumentsType()
{
dynamic procedures = new DynamicProcedureInvoker(null);
var procedures = new DynamicProcedureInvoker(null);
Assert.Throws<System.InvalidOperationException>(() =>
{
@@ -162,13 +162,11 @@ namespace DynamORM.Tests.Procedure
}
[Test]
public void TestExecRejectsMultipleArguments()
public void TestDynamicDatabaseTypedProcedureRejectsWrongArgumentsType()
{
dynamic procedures = new DynamicProcedureInvoker(null);
Assert.Throws<System.InvalidOperationException>(() =>
{
var ignored = procedures.Exec<ExecProcedureDescriptor>(new ProcedureParameterObject(), new ProcedureParameterObject());
var ignored = Database.Procedure<ExecProcedureDescriptor>(new ProcedureParameterColumnFallbackObject());
});
}

View File

@@ -1247,6 +1247,26 @@ namespace DynamORM
}
}
/// <summary>Execute typed stored procedure descriptor.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <returns>Procedure result.</returns>
public virtual object Procedure<TProcedure>()
{
return Procedure<TProcedure>(null);
}
/// <summary>Execute typed stored procedure descriptor.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <param name="args">Procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual object Procedure<TProcedure>(object args)
{
if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
throw new InvalidOperationException("Database connection desn't support stored procedures.");
return new DynamicProcedureInvoker(this).Exec<TProcedure>(args);
}
#endregion Procedure
#region Execute

View File

@@ -63,6 +63,24 @@ namespace DynamORM
_db = db;
}
/// <summary>Execute typed stored procedure descriptor.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <param name="args">Optional procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual object Exec<TProcedure>(object args = null)
{
DynamicProcedureDescriptor descriptor = DynamicProcedureDescriptor.Resolve(typeof(TProcedure));
return InvokeProcedure(
descriptor.ProcedureName,
descriptor.ResultName,
new List<Type>(),
args == null ? new object[0] : new[] { args },
new CallInfo(args == null ? 0 : 1),
descriptor.ResultType,
descriptor.ArgumentsType,
descriptor.ProcedureType);
}
/// <summary>This is where the magic begins.</summary>
/// <param name="binder">Binder to create owner.</param>
/// <param name="result">Binder invoke result.</param>
@@ -93,32 +111,25 @@ 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")
result = InvokeProcedure(binder.Name, binder.Name, types, args, info, null, null, null);
return true;
}
internal object InvokeProcedure(string procedureName, string resultName, IList<Type> types, object[] args, CallInfo info, Type declaredResultType, Type expectedArgumentsType, Type procedureType)
{
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>();
object result;
Dictionary<string, int> retParams = null;
if (expectedArgumentsType != null)
{
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));
if (args.Length == 1 && args[0] != null && !expectedArgumentsType.IsAssignableFrom(args[0].GetType()))
throw new InvalidOperationException(string.Format("Exec<{0}>(args) expects argument of type '{1}', received '{2}'.", procedureType == null ? expectedArgumentsType.FullName : procedureType.FullName, expectedArgumentsType.FullName, args[0].GetType().FullName));
}
Dictionary<string, int> retParams = null;
using (IDbConnection con = _db.Open())
using (IDbCommand cmd = con.CreateCommand())
{
@@ -488,7 +499,7 @@ namespace DynamORM
#endregion Handle out params
}
return true;
return result;
}
/// <summary>Performs application-defined tasks associated with