Add strongly typed procedure execution helpers

This commit is contained in:
2026-02-27 16:43:21 +01:00
parent f8353e7488
commit 7c084490d8
6 changed files with 351 additions and 4 deletions

View File

@@ -2699,6 +2699,51 @@ namespace DynamORM
return new DynamicProcedureInvoker(this).Exec<TProcedure>(args); return new DynamicProcedureInvoker(this).Exec<TProcedure>(args);
} }
/// <summary>Execute typed stored procedure descriptor with strong result type.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <returns>Procedure result.</returns>
public virtual TResult Procedure<TProcedure, TResult>()
where TProcedure : IProcedureDescriptor<TResult>
{
return Procedure<TProcedure, TResult>(null);
}
/// <summary>Execute typed stored procedure descriptor with strong result type.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <param name="args">Procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual TResult Procedure<TProcedure, TResult>(object args)
where TProcedure : IProcedureDescriptor<TResult>
{
if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
throw new InvalidOperationException("Database connection desn't support stored procedures.");
return new DynamicProcedureInvoker(this).Exec<TProcedure, TResult>(args);
}
/// <summary>Create typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <returns>Typed execution handle.</returns>
public virtual TypedProcedureCall<TProcedure> TypedProcedure<TProcedure>()
where TProcedure : IProcedureDescriptor
{
if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
throw new InvalidOperationException("Database connection desn't support stored procedures.");
return new TypedProcedureCall<TProcedure>(new DynamicProcedureInvoker(this));
}
/// <summary>Create typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <returns>Typed execution handle.</returns>
public virtual TypedProcedureCall<TProcedure, TResult> TypedProcedure<TProcedure, TResult>()
where TProcedure : IProcedureDescriptor<TResult>
{
if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
throw new InvalidOperationException("Database connection desn't support stored procedures.");
return new TypedProcedureCall<TProcedure, TResult>(new DynamicProcedureInvoker(this));
}
#endregion Procedure #endregion Procedure
#region Execute #region Execute
@@ -5366,6 +5411,33 @@ namespace DynamORM
descriptor.ArgumentsType, descriptor.ArgumentsType,
descriptor.ProcedureType); descriptor.ProcedureType);
} }
/// <summary>Execute typed stored procedure descriptor with strong result type.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <param name="args">Optional procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual TResult Exec<TProcedure, TResult>(object args = null)
where TProcedure : IProcedureDescriptor<TResult>
{
return ConvertProcedureResult<TResult>(Exec<TProcedure>(args));
}
/// <summary>Create typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <returns>Typed execution handle.</returns>
public virtual TypedProcedureCall<TProcedure> Typed<TProcedure>()
where TProcedure : IProcedureDescriptor
{
return new TypedProcedureCall<TProcedure>(this);
}
/// <summary>Create typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <returns>Typed execution handle.</returns>
public virtual TypedProcedureCall<TProcedure, TResult> Typed<TProcedure, TResult>()
where TProcedure : IProcedureDescriptor<TResult>
{
return new TypedProcedureCall<TProcedure, TResult>(this);
}
/// <summary>This is where the magic begins.</summary> /// <summary>This is where the magic begins.</summary>
/// <param name="binder">Binder to create owner.</param> /// <param name="binder">Binder to create owner.</param>
/// <param name="result">Binder invoke result.</param> /// <param name="result">Binder invoke result.</param>
@@ -5769,6 +5841,20 @@ namespace DynamORM
} }
return result; return result;
} }
private static TResult ConvertProcedureResult<TResult>(object result)
{
if (result == null || result == DBNull.Value)
return default(TResult);
if (result is TResult)
return (TResult)result;
DynamicTypeMap mapper = DynamicMapperCache.GetMapper(typeof(TResult));
if (mapper != null)
return (TResult)DynamicExtensions.Map(result, typeof(TResult));
return (TResult)typeof(TResult).CastObject(result);
}
/// <summary>Performs application-defined tasks associated with /// <summary>Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.</summary> /// freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose() public void Dispose()
@@ -6899,6 +6985,43 @@ namespace DynamORM
#endregion IExtendedDisposable Members #endregion IExtendedDisposable Members
} }
/// <summary>Typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
public class TypedProcedureCall<TProcedure>
where TProcedure : IProcedureDescriptor
{
protected readonly DynamicProcedureInvoker Invoker;
internal TypedProcedureCall(DynamicProcedureInvoker invoker)
{
Invoker = invoker;
}
/// <summary>Execute stored procedure descriptor.</summary>
/// <param name="args">Optional procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual object Exec(object args = null)
{
return Invoker.Exec<TProcedure>(args);
}
}
/// <summary>Typed stored procedure execution handle with strong result type.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
public class TypedProcedureCall<TProcedure, TResult> : TypedProcedureCall<TProcedure>
where TProcedure : IProcedureDescriptor<TResult>
{
internal TypedProcedureCall(DynamicProcedureInvoker invoker)
: base(invoker)
{
}
/// <summary>Execute stored procedure descriptor.</summary>
/// <param name="args">Optional procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public new virtual TResult Exec(object args = null)
{
return Invoker.Exec<TProcedure, TResult>(args);
}
}
namespace Builders namespace Builders
{ {
/// <summary>Typed join kind used by typed fluent builder APIs.</summary> /// <summary>Typed join kind used by typed fluent builder APIs.</summary>
@@ -18579,16 +18702,26 @@ namespace DynamORM
_database = null; _database = null;
} }
} }
/// <summary>Exposes typed stored procedure descriptor metadata.</summary>
public interface IProcedureDescriptor
{
}
/// <summary>Exposes typed stored procedure descriptor metadata with explicit result type.</summary>
/// <typeparam name="TResult">Procedure result type.</typeparam>
public interface IProcedureDescriptor<TResult> : IProcedureDescriptor
{
}
/// <summary>Base class for typed stored procedure descriptors.</summary> /// <summary>Base class for typed stored procedure descriptors.</summary>
/// <typeparam name="TArgs">Procedure arguments contract.</typeparam> /// <typeparam name="TArgs">Procedure arguments contract.</typeparam>
public abstract class Procedure<TArgs> public abstract class Procedure<TArgs>
: IProcedureDescriptor
where TArgs : IProcedureParameters where TArgs : IProcedureParameters
{ {
} }
/// <summary>Base class for typed stored procedure descriptors with explicit result model.</summary> /// <summary>Base class for typed stored procedure descriptors with explicit result model.</summary>
/// <typeparam name="TArgs">Procedure arguments contract.</typeparam> /// <typeparam name="TArgs">Procedure arguments contract.</typeparam>
/// <typeparam name="TResult">Procedure result model.</typeparam> /// <typeparam name="TResult">Procedure result model.</typeparam>
public abstract class Procedure<TArgs, TResult> : Procedure<TArgs> public abstract class Procedure<TArgs, TResult> : Procedure<TArgs>, IProcedureDescriptor<TResult>
where TArgs : IProcedureParameters where TArgs : IProcedureParameters
{ {
} }

View File

@@ -170,6 +170,39 @@ namespace DynamORM.Tests.Procedure
}); });
} }
[Test]
public void TestExecTypedOverloadRejectsWrongArgumentsType()
{
var procedures = new DynamicProcedureInvoker(null);
Assert.Throws<System.InvalidOperationException>(() =>
{
var ignored = procedures.Exec<ExecProcedureDescriptorWithExplicitResult, ProcedureAttributedResult>(new ProcedureParameterObject());
});
}
[Test]
public void TestTypedProcedureHandleRejectsWrongArgumentsType()
{
var procedures = new DynamicProcedureInvoker(null);
Assert.Throws<System.InvalidOperationException>(() =>
{
var ignored = procedures.Typed<ExecProcedureDescriptorWithExplicitResult, ProcedureAttributedResult>()
.Exec(new ProcedureParameterObject());
});
}
[Test]
public void TestDynamicDatabaseTypedProcedureHandleRejectsWrongArgumentsType()
{
Assert.Throws<System.InvalidOperationException>(() =>
{
var ignored = Database.TypedProcedure<ExecProcedureDescriptorWithExplicitResult, ProcedureAttributedResult>()
.Exec(new ProcedureParameterObject());
});
}
[Test] [Test]
public void TestDeclaredResultPayloadBindingMapsMainAndOutValues() public void TestDeclaredResultPayloadBindingMapsMainAndOutValues()
{ {

View File

@@ -38,8 +38,9 @@ using System.Text;
using DynamORM.Builders; using DynamORM.Builders;
using DynamORM.Builders.Extensions; using DynamORM.Builders.Extensions;
using DynamORM.Builders.Implementation; using DynamORM.Builders.Implementation;
using DynamORM.Helpers; using DynamORM.Helpers;
using DynamORM.Mapper; using DynamORM.Mapper;
using DynamORM.Objects;
namespace DynamORM namespace DynamORM
{ {
@@ -1267,6 +1268,55 @@ namespace DynamORM
return new DynamicProcedureInvoker(this).Exec<TProcedure>(args); return new DynamicProcedureInvoker(this).Exec<TProcedure>(args);
} }
/// <summary>Execute typed stored procedure descriptor with strong result type.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <returns>Procedure result.</returns>
public virtual TResult Procedure<TProcedure, TResult>()
where TProcedure : IProcedureDescriptor<TResult>
{
return Procedure<TProcedure, TResult>(null);
}
/// <summary>Execute typed stored procedure descriptor with strong result type.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <param name="args">Procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual TResult Procedure<TProcedure, TResult>(object args)
where TProcedure : IProcedureDescriptor<TResult>
{
if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
throw new InvalidOperationException("Database connection desn't support stored procedures.");
return new DynamicProcedureInvoker(this).Exec<TProcedure, TResult>(args);
}
/// <summary>Create typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <returns>Typed execution handle.</returns>
public virtual TypedProcedureCall<TProcedure> TypedProcedure<TProcedure>()
where TProcedure : IProcedureDescriptor
{
if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
throw new InvalidOperationException("Database connection desn't support stored procedures.");
return new TypedProcedureCall<TProcedure>(new DynamicProcedureInvoker(this));
}
/// <summary>Create typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <returns>Typed execution handle.</returns>
public virtual TypedProcedureCall<TProcedure, TResult> TypedProcedure<TProcedure, TResult>()
where TProcedure : IProcedureDescriptor<TResult>
{
if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
throw new InvalidOperationException("Database connection desn't support stored procedures.");
return new TypedProcedureCall<TProcedure, TResult>(new DynamicProcedureInvoker(this));
}
#endregion Procedure #endregion Procedure
#region Execute #region Execute

View File

@@ -35,6 +35,7 @@ using System.Dynamic;
using System.Linq; using System.Linq;
using DynamORM.Helpers; using DynamORM.Helpers;
using DynamORM.Mapper; using DynamORM.Mapper;
using DynamORM.Objects;
namespace DynamORM namespace DynamORM
{ {
@@ -80,6 +81,36 @@ namespace DynamORM
descriptor.ArgumentsType, descriptor.ArgumentsType,
descriptor.ProcedureType); descriptor.ProcedureType);
} }
/// <summary>Execute typed stored procedure descriptor with strong result type.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <param name="args">Optional procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual TResult Exec<TProcedure, TResult>(object args = null)
where TProcedure : IProcedureDescriptor<TResult>
{
return ConvertProcedureResult<TResult>(Exec<TProcedure>(args));
}
/// <summary>Create typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <returns>Typed execution handle.</returns>
public virtual TypedProcedureCall<TProcedure> Typed<TProcedure>()
where TProcedure : IProcedureDescriptor
{
return new TypedProcedureCall<TProcedure>(this);
}
/// <summary>Create typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
/// <returns>Typed execution handle.</returns>
public virtual TypedProcedureCall<TProcedure, TResult> Typed<TProcedure, TResult>()
where TProcedure : IProcedureDescriptor<TResult>
{
return new TypedProcedureCall<TProcedure, TResult>(this);
}
/// <summary>This is where the magic begins.</summary> /// <summary>This is where the magic begins.</summary>
/// <param name="binder">Binder to create owner.</param> /// <param name="binder">Binder to create owner.</param>
@@ -501,6 +532,21 @@ namespace DynamORM
return result; return result;
} }
private static TResult ConvertProcedureResult<TResult>(object result)
{
if (result == null || result == DBNull.Value)
return default(TResult);
if (result is TResult)
return (TResult)result;
DynamicTypeMap mapper = DynamicMapperCache.GetMapper(typeof(TResult));
if (mapper != null)
return (TResult)DynamicExtensions.Map(result, typeof(TResult));
return (TResult)typeof(TResult).CastObject(result);
}
/// <summary>Performs application-defined tasks associated with /// <summary>Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.</summary> /// freeing, releasing, or resetting unmanaged resources.</summary>

View File

@@ -28,9 +28,21 @@
namespace DynamORM.Objects namespace DynamORM.Objects
{ {
/// <summary>Exposes typed stored procedure descriptor metadata.</summary>
public interface IProcedureDescriptor
{
}
/// <summary>Exposes typed stored procedure descriptor metadata with explicit result type.</summary>
/// <typeparam name="TResult">Procedure result type.</typeparam>
public interface IProcedureDescriptor<TResult> : IProcedureDescriptor
{
}
/// <summary>Base class for typed stored procedure descriptors.</summary> /// <summary>Base class for typed stored procedure descriptors.</summary>
/// <typeparam name="TArgs">Procedure arguments contract.</typeparam> /// <typeparam name="TArgs">Procedure arguments contract.</typeparam>
public abstract class Procedure<TArgs> public abstract class Procedure<TArgs>
: IProcedureDescriptor
where TArgs : IProcedureParameters where TArgs : IProcedureParameters
{ {
} }
@@ -38,7 +50,7 @@ namespace DynamORM.Objects
/// <summary>Base class for typed stored procedure descriptors with explicit result model.</summary> /// <summary>Base class for typed stored procedure descriptors with explicit result model.</summary>
/// <typeparam name="TArgs">Procedure arguments contract.</typeparam> /// <typeparam name="TArgs">Procedure arguments contract.</typeparam>
/// <typeparam name="TResult">Procedure result model.</typeparam> /// <typeparam name="TResult">Procedure result model.</typeparam>
public abstract class Procedure<TArgs, TResult> : Procedure<TArgs> public abstract class Procedure<TArgs, TResult> : Procedure<TArgs>, IProcedureDescriptor<TResult>
where TArgs : IProcedureParameters where TArgs : IProcedureParameters
{ {
} }

View File

@@ -0,0 +1,73 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using DynamORM.Objects;
namespace DynamORM
{
/// <summary>Typed stored procedure execution handle.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
public class TypedProcedureCall<TProcedure>
where TProcedure : IProcedureDescriptor
{
protected readonly DynamicProcedureInvoker Invoker;
internal TypedProcedureCall(DynamicProcedureInvoker invoker)
{
Invoker = invoker;
}
/// <summary>Execute stored procedure descriptor.</summary>
/// <param name="args">Optional procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public virtual object Exec(object args = null)
{
return Invoker.Exec<TProcedure>(args);
}
}
/// <summary>Typed stored procedure execution handle with strong result type.</summary>
/// <typeparam name="TProcedure">Procedure descriptor type.</typeparam>
/// <typeparam name="TResult">Procedure result type.</typeparam>
public class TypedProcedureCall<TProcedure, TResult> : TypedProcedureCall<TProcedure>
where TProcedure : IProcedureDescriptor<TResult>
{
internal TypedProcedureCall(DynamicProcedureInvoker invoker)
: base(invoker)
{
}
/// <summary>Execute stored procedure descriptor.</summary>
/// <param name="args">Optional procedure arguments contract.</param>
/// <returns>Procedure result.</returns>
public new virtual TResult Exec(object args = null)
{
return Invoker.Exec<TProcedure, TResult>(args);
}
}
}