diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index 7d2786a..afd2ab7 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -5361,22 +5361,44 @@ namespace DynamORM // Get generic types IList types = binder.GetGenericTypeArguments() ?? new List(); 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(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(); + + if (args.Length > 1) + throw new InvalidOperationException("Exec(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 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 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() + .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 { } + /// Allows to add stored procedure metadata to class. + [AttributeUsage(AttributeTargets.Class)] + public class ProcedureAttribute : Attribute + { + /// Gets or sets procedure owner name. + public string Owner { get; set; } + + /// Gets or sets procedure name. + public string Name { get; set; } + + /// Gets or sets a value indicating whether metadata overrides other defaults. + public bool Override { get; set; } + } /// Declares metadata for object-based stored procedure parameters. [AttributeUsage(AttributeTargets.Property)] public class ProcedureParameterAttribute : ColumnAttribute @@ -18456,6 +18552,19 @@ namespace DynamORM _database = null; } } + /// Base class for typed stored procedure descriptors. + /// Procedure arguments contract. + public abstract class Procedure + 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 + where TArgs : IProcedureParameters + { + } /// Marks an object as an explicit stored procedure parameter contract. public interface IProcedureParameters { diff --git a/DynamORM.Tests/Helpers/ProcedureParameterModels.cs b/DynamORM.Tests/Helpers/ProcedureParameterModels.cs index 504c98f..093a2be 100644 --- a/DynamORM.Tests/Helpers/ProcedureParameterModels.cs +++ b/DynamORM.Tests/Helpers/ProcedureParameterModels.cs @@ -139,4 +139,18 @@ namespace DynamORM.Tests.Helpers [ProcedureParameter("status", Direction = ParameterDirection.Output, Order = 1, DbType = DbType.Int32)] public int Status { get; set; } } + + [Procedure(Name = "sp_exec_test", Owner = "dbo")] + public class ExecProcedureDescriptor : Procedure + { + } + + public class ExecProcedureDefaultDescriptor : Procedure + { + } + + [Procedure(Name = "sp_exec_result")] + public class ExecProcedureDescriptorWithExplicitResult : Procedure + { + } } diff --git a/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs b/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs index df49905..a98f2d7 100644 --- a/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs +++ b/DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs @@ -5,6 +5,7 @@ */ using System.Data; +using System.Dynamic; using DynamORM.Helpers; using DynamORM.Tests.Helpers; using NUnit.Framework; @@ -122,6 +123,55 @@ namespace DynamORM.Tests.Procedure Assert.IsNull(DynamicProcedureResultBinder.GetDeclaredResultType(new object())); } + [Test] + public void TestProcedureDescriptorResolvesAttributeNameAndArguments() + { + var descriptor = DynamicProcedureDescriptor.Resolve(typeof(ExecProcedureDescriptor)); + + Assert.AreEqual(typeof(ExecProcedureDescriptor), descriptor.ProcedureType); + Assert.AreEqual(typeof(ProcedureParameterObject), descriptor.ArgumentsType); + Assert.IsNull(descriptor.ResultType); + Assert.AreEqual("dbo.sp_exec_test", descriptor.ProcedureName); + Assert.AreEqual("sp_exec_test", descriptor.ResultName); + } + + [Test] + public void TestProcedureDescriptorResolvesDefaultNameAndExplicitResult() + { + var defaultDescriptor = DynamicProcedureDescriptor.Resolve(typeof(ExecProcedureDefaultDescriptor)); + var explicitDescriptor = DynamicProcedureDescriptor.Resolve(typeof(ExecProcedureDescriptorWithExplicitResult)); + + Assert.AreEqual("ExecProcedureDefaultDescriptor", defaultDescriptor.ProcedureName); + Assert.AreEqual(typeof(ProcedureParameterObject), defaultDescriptor.ArgumentsType); + Assert.IsNull(defaultDescriptor.ResultType); + + Assert.AreEqual("sp_exec_result", explicitDescriptor.ProcedureName); + Assert.AreEqual(typeof(ProcedureParameterColumnFallbackObject), explicitDescriptor.ArgumentsType); + Assert.AreEqual(typeof(ProcedureAttributedResult), explicitDescriptor.ResultType); + } + + [Test] + public void TestExecRejectsWrongArgumentsType() + { + dynamic procedures = new DynamicProcedureInvoker(null); + + Assert.Throws(() => + { + var ignored = procedures.Exec(new ProcedureParameterColumnFallbackObject()); + }); + } + + [Test] + public void TestExecRejectsMultipleArguments() + { + dynamic procedures = new DynamicProcedureInvoker(null); + + Assert.Throws(() => + { + var ignored = procedures.Exec(new ProcedureParameterObject(), new ProcedureParameterObject()); + }); + } + [Test] public void TestDeclaredResultPayloadBindingMapsMainAndOutValues() { diff --git a/DynamORM/DynamicProcedureInvoker.cs b/DynamORM/DynamicProcedureInvoker.cs index 329ca72..068115d 100644 --- a/DynamORM/DynamicProcedureInvoker.cs +++ b/DynamORM/DynamicProcedureInvoker.cs @@ -86,30 +86,53 @@ namespace DynamORM /// Binder arguments. /// Binder invoke result. /// Returns true if invoke was performed. - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - // parse the method + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + // parse the method CallInfo info = binder.CallInfo; // Get generic types IList types = binder.GetGenericTypeArguments() ?? new List(); 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(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(); + + if (args.Length > 1) + throw new InvalidOperationException("Exec(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 retParams = null; using (IDbConnection con = _db.Open()) using (IDbCommand cmd = con.CreateCommand()) { - if (_prefixes == null || _prefixes.Count == 0) - cmd.SetCommand(CommandType.StoredProcedure, binder.Name); - else - cmd.SetCommand(CommandType.StoredProcedure, string.Format("{0}.{1}", string.Join(".", _prefixes), binder.Name)); + if (_prefixes == null || _prefixes.Count == 0) + cmd.SetCommand(CommandType.StoredProcedure, procedureName); + else + 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) { @@ -301,7 +324,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()) { @@ -433,10 +456,10 @@ namespace DynamORM if (mainResult != null) { if (mainResult == DBNull.Value) - res.Add(binder.Name, null); - else - res.Add(binder.Name, mainResult); - } + res.Add(resultName, null); + else + res.Add(resultName, mainResult); + } foreach (KeyValuePair pos in retParams) res.Add(pos.Key, ((IDbDataParameter)cmd.Parameters[pos.Value]).Value); @@ -451,14 +474,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; diff --git a/DynamORM/Helpers/DynamicProcedureDescriptor.cs b/DynamORM/Helpers/DynamicProcedureDescriptor.cs new file mode 100644 index 0000000..4cefb15 --- /dev/null +++ b/DynamORM/Helpers/DynamicProcedureDescriptor.cs @@ -0,0 +1,100 @@ +/* + * 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 System; +using System.Linq; +using DynamORM.Mapper; +using DynamORM.Objects; + +namespace DynamORM.Helpers +{ + 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() + .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 + }; + } + } +} diff --git a/DynamORM/Mapper/ProcedureAttribute.cs b/DynamORM/Mapper/ProcedureAttribute.cs new file mode 100644 index 0000000..06ea9a5 --- /dev/null +++ b/DynamORM/Mapper/ProcedureAttribute.cs @@ -0,0 +1,46 @@ +/* + * 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 System; + +namespace DynamORM.Mapper +{ + /// Allows to add stored procedure metadata to class. + [AttributeUsage(AttributeTargets.Class)] + public class ProcedureAttribute : Attribute + { + /// Gets or sets procedure owner name. + public string Owner { get; set; } + + /// Gets or sets procedure name. + public string Name { get; set; } + + /// Gets or sets a value indicating whether metadata overrides other defaults. + public bool Override { get; set; } + } +} diff --git a/DynamORM/Objects/Procedure.cs b/DynamORM/Objects/Procedure.cs new file mode 100644 index 0000000..8182de3 --- /dev/null +++ b/DynamORM/Objects/Procedure.cs @@ -0,0 +1,45 @@ +/* + * 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. +*/ + +namespace DynamORM.Objects +{ + /// Base class for typed stored procedure descriptors. + /// Procedure arguments contract. + public abstract class Procedure + 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 + where TArgs : IProcedureParameters + { + } +}