diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index a9d936f..55e0de4 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -2699,6 +2699,51 @@ namespace DynamORM return new DynamicProcedureInvoker(this).Exec(args); } + /// Execute typed stored procedure descriptor with strong result type. + /// Procedure descriptor type. + /// Procedure result type. + /// Procedure result. + public virtual TResult Procedure() + where TProcedure : IProcedureDescriptor + { + return Procedure(null); + } + /// Execute typed stored procedure descriptor with strong result type. + /// Procedure descriptor type. + /// Procedure result type. + /// Procedure arguments contract. + /// Procedure result. + public virtual TResult Procedure(object args) + where TProcedure : IProcedureDescriptor + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + return new DynamicProcedureInvoker(this).Exec(args); + } + /// Create typed stored procedure execution handle. + /// Procedure descriptor type. + /// Typed execution handle. + public virtual TypedProcedureCall TypedProcedure() + where TProcedure : IProcedureDescriptor + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + return new TypedProcedureCall(new DynamicProcedureInvoker(this)); + } + /// Create typed stored procedure execution handle. + /// Procedure descriptor type. + /// Procedure result type. + /// Typed execution handle. + public virtual TypedProcedureCall TypedProcedure() + where TProcedure : IProcedureDescriptor + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + return new TypedProcedureCall(new DynamicProcedureInvoker(this)); + } #endregion Procedure #region Execute @@ -5366,6 +5411,33 @@ namespace DynamORM descriptor.ArgumentsType, descriptor.ProcedureType); } + /// Execute typed stored procedure descriptor with strong result type. + /// Procedure descriptor type. + /// Procedure result type. + /// Optional procedure arguments contract. + /// Procedure result. + public virtual TResult Exec(object args = null) + where TProcedure : IProcedureDescriptor + { + return ConvertProcedureResult(Exec(args)); + } + /// Create typed stored procedure execution handle. + /// Procedure descriptor type. + /// Typed execution handle. + public virtual TypedProcedureCall Typed() + where TProcedure : IProcedureDescriptor + { + return new TypedProcedureCall(this); + } + /// Create typed stored procedure execution handle. + /// Procedure descriptor type. + /// Procedure result type. + /// Typed execution handle. + public virtual TypedProcedureCall Typed() + where TProcedure : IProcedureDescriptor + { + return new TypedProcedureCall(this); + } /// This is where the magic begins. /// Binder to create owner. /// Binder invoke result. @@ -5769,6 +5841,20 @@ namespace DynamORM } return result; } + private static TResult ConvertProcedureResult(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); + } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. public void Dispose() @@ -6899,6 +6985,43 @@ namespace DynamORM #endregion IExtendedDisposable Members } + /// Typed stored procedure execution handle. + /// Procedure descriptor type. + public class TypedProcedureCall + where TProcedure : IProcedureDescriptor + { + protected readonly DynamicProcedureInvoker Invoker; + + internal TypedProcedureCall(DynamicProcedureInvoker invoker) + { + Invoker = invoker; + } + /// Execute stored procedure descriptor. + /// Optional procedure arguments contract. + /// Procedure result. + public virtual object Exec(object args = null) + { + return Invoker.Exec(args); + } + } + /// Typed stored procedure execution handle with strong result type. + /// Procedure descriptor type. + /// Procedure result type. + public class TypedProcedureCall : TypedProcedureCall + where TProcedure : IProcedureDescriptor + { + internal TypedProcedureCall(DynamicProcedureInvoker invoker) + : base(invoker) + { + } + /// Execute stored procedure descriptor. + /// Optional procedure arguments contract. + /// Procedure result. + public new virtual TResult Exec(object args = null) + { + return Invoker.Exec(args); + } + } namespace Builders { /// Typed join kind used by typed fluent builder APIs. @@ -18579,16 +18702,26 @@ namespace DynamORM _database = null; } } + /// Exposes typed stored procedure descriptor metadata. + public interface IProcedureDescriptor + { + } + /// Exposes typed stored procedure descriptor metadata with explicit result type. + /// Procedure result type. + public interface IProcedureDescriptor : IProcedureDescriptor + { + } /// Base class for typed stored procedure descriptors. /// Procedure arguments contract. public abstract class Procedure + : IProcedureDescriptor where TArgs : IProcedureParameters { } /// Base class for typed stored procedure descriptors with explicit result model. /// Procedure arguments contract. /// Procedure result model. - public abstract class Procedure : Procedure + public abstract class Procedure : Procedure, IProcedureDescriptor where TArgs : IProcedureParameters { } diff --git a/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs b/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs index 6036f19..1b099f6 100644 --- a/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs +++ b/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs @@ -170,6 +170,39 @@ namespace DynamORM.Tests.Procedure }); } + [Test] + public void TestExecTypedOverloadRejectsWrongArgumentsType() + { + var procedures = new DynamicProcedureInvoker(null); + + Assert.Throws(() => + { + var ignored = procedures.Exec(new ProcedureParameterObject()); + }); + } + + [Test] + public void TestTypedProcedureHandleRejectsWrongArgumentsType() + { + var procedures = new DynamicProcedureInvoker(null); + + Assert.Throws(() => + { + var ignored = procedures.Typed() + .Exec(new ProcedureParameterObject()); + }); + } + + [Test] + public void TestDynamicDatabaseTypedProcedureHandleRejectsWrongArgumentsType() + { + Assert.Throws(() => + { + var ignored = Database.TypedProcedure() + .Exec(new ProcedureParameterObject()); + }); + } + [Test] public void TestDeclaredResultPayloadBindingMapsMainAndOutValues() { diff --git a/DynamORM/DynamicDatabase.cs b/DynamORM/DynamicDatabase.cs index 843b5f9..4e61f39 100644 --- a/DynamORM/DynamicDatabase.cs +++ b/DynamORM/DynamicDatabase.cs @@ -38,8 +38,9 @@ using System.Text; using DynamORM.Builders; using DynamORM.Builders.Extensions; using DynamORM.Builders.Implementation; -using DynamORM.Helpers; -using DynamORM.Mapper; +using DynamORM.Helpers; +using DynamORM.Mapper; +using DynamORM.Objects; namespace DynamORM { @@ -1267,6 +1268,55 @@ namespace DynamORM return new DynamicProcedureInvoker(this).Exec(args); } + /// Execute typed stored procedure descriptor with strong result type. + /// Procedure descriptor type. + /// Procedure result type. + /// Procedure result. + public virtual TResult Procedure() + where TProcedure : IProcedureDescriptor + { + return Procedure(null); + } + + /// Execute typed stored procedure descriptor with strong result type. + /// Procedure descriptor type. + /// Procedure result type. + /// Procedure arguments contract. + /// Procedure result. + public virtual TResult Procedure(object args) + where TProcedure : IProcedureDescriptor + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + return new DynamicProcedureInvoker(this).Exec(args); + } + + /// Create typed stored procedure execution handle. + /// Procedure descriptor type. + /// Typed execution handle. + public virtual TypedProcedureCall TypedProcedure() + where TProcedure : IProcedureDescriptor + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + return new TypedProcedureCall(new DynamicProcedureInvoker(this)); + } + + /// Create typed stored procedure execution handle. + /// Procedure descriptor type. + /// Procedure result type. + /// Typed execution handle. + public virtual TypedProcedureCall TypedProcedure() + where TProcedure : IProcedureDescriptor + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + return new TypedProcedureCall(new DynamicProcedureInvoker(this)); + } + #endregion Procedure #region Execute diff --git a/DynamORM/DynamicProcedureInvoker.cs b/DynamORM/DynamicProcedureInvoker.cs index 4192bb1..640253a 100644 --- a/DynamORM/DynamicProcedureInvoker.cs +++ b/DynamORM/DynamicProcedureInvoker.cs @@ -35,6 +35,7 @@ using System.Dynamic; using System.Linq; using DynamORM.Helpers; using DynamORM.Mapper; +using DynamORM.Objects; namespace DynamORM { @@ -80,6 +81,36 @@ namespace DynamORM descriptor.ArgumentsType, descriptor.ProcedureType); } + + /// Execute typed stored procedure descriptor with strong result type. + /// Procedure descriptor type. + /// Procedure result type. + /// Optional procedure arguments contract. + /// Procedure result. + public virtual TResult Exec(object args = null) + where TProcedure : IProcedureDescriptor + { + return ConvertProcedureResult(Exec(args)); + } + + /// Create typed stored procedure execution handle. + /// Procedure descriptor type. + /// Typed execution handle. + public virtual TypedProcedureCall Typed() + where TProcedure : IProcedureDescriptor + { + return new TypedProcedureCall(this); + } + + /// Create typed stored procedure execution handle. + /// Procedure descriptor type. + /// Procedure result type. + /// Typed execution handle. + public virtual TypedProcedureCall Typed() + where TProcedure : IProcedureDescriptor + { + return new TypedProcedureCall(this); + } /// This is where the magic begins. /// Binder to create owner. @@ -501,6 +532,21 @@ namespace DynamORM return result; } + + private static TResult ConvertProcedureResult(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); + } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. diff --git a/DynamORM/Objects/Procedure.cs b/DynamORM/Objects/Procedure.cs index 8182de3..99c1025 100644 --- a/DynamORM/Objects/Procedure.cs +++ b/DynamORM/Objects/Procedure.cs @@ -28,9 +28,21 @@ namespace DynamORM.Objects { + /// Exposes typed stored procedure descriptor metadata. + public interface IProcedureDescriptor + { + } + + /// Exposes typed stored procedure descriptor metadata with explicit result type. + /// Procedure result type. + public interface IProcedureDescriptor : IProcedureDescriptor + { + } + /// Base class for typed stored procedure descriptors. /// Procedure arguments contract. public abstract class Procedure + : IProcedureDescriptor where TArgs : IProcedureParameters { } @@ -38,7 +50,7 @@ namespace DynamORM.Objects /// Base class for typed stored procedure descriptors with explicit result model. /// Procedure arguments contract. /// Procedure result model. - public abstract class Procedure : Procedure + public abstract class Procedure : Procedure, IProcedureDescriptor where TArgs : IProcedureParameters { } diff --git a/DynamORM/TypedProcedureCall.cs b/DynamORM/TypedProcedureCall.cs new file mode 100644 index 0000000..7d09d54 --- /dev/null +++ b/DynamORM/TypedProcedureCall.cs @@ -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 +{ + /// Typed stored procedure execution handle. + /// Procedure descriptor type. + public class TypedProcedureCall + where TProcedure : IProcedureDescriptor + { + protected readonly DynamicProcedureInvoker Invoker; + + internal TypedProcedureCall(DynamicProcedureInvoker invoker) + { + Invoker = invoker; + } + + /// Execute stored procedure descriptor. + /// Optional procedure arguments contract. + /// Procedure result. + public virtual object Exec(object args = null) + { + return Invoker.Exec(args); + } + } + + /// Typed stored procedure execution handle with strong result type. + /// Procedure descriptor type. + /// Procedure result type. + public class TypedProcedureCall : TypedProcedureCall + where TProcedure : IProcedureDescriptor + { + internal TypedProcedureCall(DynamicProcedureInvoker invoker) + : base(invoker) + { + } + + /// Execute stored procedure descriptor. + /// Optional procedure arguments contract. + /// Procedure result. + public new virtual TResult Exec(object args = null) + { + return Invoker.Exec(args); + } + } +}