From 6f7533264d9bec46c24ef516968ff9c927205529 Mon Sep 17 00:00:00 2001 From: Abraham Heidebrecht Date: Wed, 31 Aug 2016 21:37:35 -0400 Subject: [PATCH 1/3] Added option to not clone connections on all stored procedure calls --- .../Dynamic/DynamicStoredProcedureResults.cs | 2 +- CodeOnlyStoredProcedure/GlobalSettings.cs | 9 +- .../IDbConnectionExtensions.cs | 2 +- CodeOnlyStoredProcedure/StoredProcedure.cs | 14 ++- .../CodeOnlyTests-NET40.csproj | 3 + CodeOnlyTests/CodeOnlyTests.csproj | 1 + CodeOnlyTests/IDbConnectionExtensionsTests.cs | 105 ++++++++++++++++++ 7 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 CodeOnlyTests/IDbConnectionExtensionsTests.cs diff --git a/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedureResults.cs b/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedureResults.cs index fc6c0ec..90ad380 100644 --- a/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedureResults.cs +++ b/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedureResults.cs @@ -308,7 +308,7 @@ public override DynamicMetaObject BindConvert(ConvertBinder binder) e = Expression.Call(null, singleExtension.Value.MakeGenericMethod(retType), e); } - // make sure to close the connection + // make sure to dispose the DynamicStoredProcedureResults var res = Expression.Variable(retType); e = Expression.Block(retType, new[] { res }, diff --git a/CodeOnlyStoredProcedure/GlobalSettings.cs b/CodeOnlyStoredProcedure/GlobalSettings.cs index f85b004..38f0fd7 100644 --- a/CodeOnlyStoredProcedure/GlobalSettings.cs +++ b/CodeOnlyStoredProcedure/GlobalSettings.cs @@ -9,16 +9,15 @@ internal class GlobalSettings private static GlobalSettings instance = new GlobalSettings(); public static GlobalSettings Instance { get { return instance; } } - public IList DataTransformers { get; } - public ConcurrentDictionary InterfaceMap { get; } + public IList DataTransformers { get; } = new List(); + public ConcurrentDictionary InterfaceMap { get; } = new ConcurrentDictionary(); public bool IsTestInstance { get; } public bool ConvertAllNumericValues { get; set; } + public bool CloneConnectionForEachCall { get; set; } = true; private GlobalSettings(bool isTestInstance = false) { - IsTestInstance = isTestInstance; - DataTransformers = new List(); - InterfaceMap = new ConcurrentDictionary(); + IsTestInstance = isTestInstance; } public static IDisposable UseTestInstance() diff --git a/CodeOnlyStoredProcedure/IDbConnectionExtensions.cs b/CodeOnlyStoredProcedure/IDbConnectionExtensions.cs index 05b124d..879bc45 100644 --- a/CodeOnlyStoredProcedure/IDbConnectionExtensions.cs +++ b/CodeOnlyStoredProcedure/IDbConnectionExtensions.cs @@ -194,7 +194,7 @@ internal static IDbCommand CreateCommand( // uses the connection (like an EF DbSet), we could possibly close // the connection while a transaction is in process. // By only opening a clone of the connection, we avoid this issue. - if (connection is ICloneable) + if (GlobalSettings.Instance.CloneConnectionForEachCall && connection is ICloneable) { connection = closeAfterExecute = (IDbConnection)((ICloneable)connection).Clone(); connection.Open(); diff --git a/CodeOnlyStoredProcedure/StoredProcedure.cs b/CodeOnlyStoredProcedure/StoredProcedure.cs index 28c65a4..69486e3 100644 --- a/CodeOnlyStoredProcedure/StoredProcedure.cs +++ b/CodeOnlyStoredProcedure/StoredProcedure.cs @@ -342,10 +342,16 @@ public static void AddGlobalTransformer(IDataTransformer transformer) /// Will automatically convert all values returned from the database into the proper type to set /// on the model properties for every StoredProcedure that executes. /// - public static void EnableConvertOnAllNumericValues() - { - GlobalSettings.Instance.ConvertAllNumericValues = true; - } + public static void EnableConvertOnAllNumericValues() => GlobalSettings.Instance.ConvertAllNumericValues = true; + + /// + /// Will prevent connections from being cloned when calling a stored procedure. The default behavior will clone + /// and dispose connections for every call (so long as the connection implements , + /// as there are fewer conflicts with other frameworks who share the connection. If you have full control + /// of the connection, we recommend disabling connection cloning. Especially if you make a lot of stored + /// procedure calls. + /// + public static void DisableConnectionCloningForEachCall() => GlobalSettings.Instance.CloneConnectionForEachCall = false; #endregion /// diff --git a/CodeOnlyTests-NET40/CodeOnlyTests-NET40.csproj b/CodeOnlyTests-NET40/CodeOnlyTests-NET40.csproj index 48e9298..33d8ac8 100644 --- a/CodeOnlyTests-NET40/CodeOnlyTests-NET40.csproj +++ b/CodeOnlyTests-NET40/CodeOnlyTests-NET40.csproj @@ -119,6 +119,9 @@ HierarchicalStoredProcedureTests.cs + + IDbConnectionExtensionsTests.cs + RowFactory\ComplexTypeRowFactoryTests.cs diff --git a/CodeOnlyTests/CodeOnlyTests.csproj b/CodeOnlyTests/CodeOnlyTests.csproj index f0f4795..0d0a2a6 100644 --- a/CodeOnlyTests/CodeOnlyTests.csproj +++ b/CodeOnlyTests/CodeOnlyTests.csproj @@ -80,6 +80,7 @@ + diff --git a/CodeOnlyTests/IDbConnectionExtensionsTests.cs b/CodeOnlyTests/IDbConnectionExtensionsTests.cs new file mode 100644 index 0000000..4b0dcc7 --- /dev/null +++ b/CodeOnlyTests/IDbConnectionExtensionsTests.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CodeOnlyStoredProcedure; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace CodeOnlyTests +{ + [TestClass] + public class IDbConnectionExtensionsTests + { + [TestClass] + public class CreateCommandTests + { + [TestMethod] + public void Clones_And_Opens_Connection_By_Default() + { + using (GlobalSettings.UseTestInstance()) + { + var db = new Mock(); + var db2 = new Mock(); + + db.As().Setup(c => c.Clone()).Returns(db2.Object); + + var cmd = new Mock(); + cmd.SetupAllProperties(); + + db2.Setup(d => d.CreateCommand()).Returns(cmd.Object); + + IDbConnection outConn; + var res = db.Object.CreateCommand("foo", "bar", 20, out outConn); + + outConn.Should().NotBeNull(); + res.Should().NotBeNull(); + res.CommandText.Should().Be("[foo].[bar]"); + res.CommandTimeout.Should().Be(20); + res.CommandType.Should().Be(CommandType.StoredProcedure); + + db.As().Verify(c => c.Clone(), Times.Once()); + db2.Verify(d => d.CreateCommand(), Times.Once()); + db2.Verify(d => d.Open(), Times.Once()); + } + } + + [TestMethod] + public void Does_Not_Clone_When_Connection_Is_Not_Cloneable() + { + using (GlobalSettings.UseTestInstance()) + { + var db = new Mock(); + var cmd = new Mock(); + cmd.SetupAllProperties(); + + db.Setup(d => d.CreateCommand()).Returns(cmd.Object); + + IDbConnection outConn; + var res = db.Object.CreateCommand("foo", "bar", 20, out outConn); + + outConn.Should().BeNull(); + res.Should().NotBeNull(); + res.CommandText.Should().Be("[foo].[bar]"); + res.CommandTimeout.Should().Be(20); + res.CommandType.Should().Be(CommandType.StoredProcedure); + + db.Verify(d => d.CreateCommand(), Times.Once()); + db.Verify(d => d.Open(), Times.Never()); + } + } + + [TestMethod] + public void Does_Not_Clone_When_Global_Option_Is_Set_To_Not_Clone_And_Opens_It_When_Closed() + { + using (GlobalSettings.UseTestInstance()) + { + GlobalSettings.Instance.CloneConnectionForEachCall = false; + + var db = new Mock(); + var cmd = new Mock(); + cmd.SetupAllProperties(); + + db.SetupGet(d => d.State).Returns(ConnectionState.Closed); + db.As().Setup(c => c.Clone()).Throws(new NotSupportedException()); + db.Setup(d => d.CreateCommand()).Returns(cmd.Object); + + IDbConnection outConn; + var res = db.Object.CreateCommand("foo", "bar", 20, out outConn); + + outConn.Should().BeNull(); + res.Should().NotBeNull(); + res.CommandText.Should().Be("[foo].[bar]"); + res.CommandTimeout.Should().Be(20); + res.CommandType.Should().Be(CommandType.StoredProcedure); + + db.Verify(d => d.CreateCommand(), Times.Once()); + db.Verify(d => d.Open(), Times.Never()); + } + } + } + } +} From f2bde1897483fdf030be15ff47160498fdf804e4 Mon Sep 17 00:00:00 2001 From: Abraham Heidebrecht Date: Thu, 1 Sep 2016 21:48:56 -0400 Subject: [PATCH 2/3] Implemented ability to execute non-query stored procedures dynamically. Updated all stored procedures that return results so they dispose their DataReader objects. This obviously should have been done before, but the problem was hidden by the fact that we cloned the connections before. --- .../Dynamic/DynamicStoredProcedure.cs | 12 +- .../Dynamic/DynamicStoredProcedureResults.cs | 161 +++++++--- .../IDbConnectionExtensions.cs | 209 +++++++++++-- CodeOnlyStoredProcedure/StoredProcedure.cs | 2 + .../StoredProcedure.generated.cs | 274 ++++++++++-------- .../StoredProcedureTemplate.tt | 20 +- .../Dynamic/DynamicStoredProcedureTests.cs | 138 +++++---- SmokeTests-NET40/App.config | 2 +- SmokeTests/DynamicSyntax.cs | 8 +- SmokeTests/Program.cs | 27 +- SmokeTests/app.config | 2 +- 11 files changed, 578 insertions(+), 277 deletions(-) diff --git a/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedure.cs b/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedure.cs index 7e73a93..4f9c139 100644 --- a/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedure.cs +++ b/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedure.cs @@ -24,6 +24,7 @@ internal class DynamicStoredProcedure : DynamicObject private readonly CancellationToken token; private readonly int timeout; private readonly DynamicExecutionMode executionMode; + private readonly bool hasResults; static DynamicStoredProcedure() { @@ -36,7 +37,8 @@ public DynamicStoredProcedure(IDbConnection connection, IEnumerable transformers, CancellationToken token, int timeout, - DynamicExecutionMode executionMode) + DynamicExecutionMode executionMode, + bool hasResults) { Contract.Requires(connection != null); Contract.Requires(transformers != null); @@ -46,6 +48,7 @@ public DynamicStoredProcedure(IDbConnection connection, this.token = token; this.timeout = timeout; this.executionMode = executionMode; + this.hasResults = hasResults; } private DynamicStoredProcedure(IDbConnection connection, @@ -53,7 +56,8 @@ private DynamicStoredProcedure(IDbConnection connection, CancellationToken token, int timeout, string schema, - DynamicExecutionMode executionMode) + DynamicExecutionMode executionMode, + bool hasResults) { Contract.Requires(connection != null); Contract.Requires(transformers != null); @@ -65,6 +69,7 @@ private DynamicStoredProcedure(IDbConnection connection, this.token = token; this.timeout = timeout; this.executionMode = executionMode; + this.hasResults = hasResults; } public override bool TryGetMember(GetMemberBinder binder, out object result) @@ -72,7 +77,7 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) if (!string.IsNullOrEmpty(schema)) throw new StoredProcedureException($"Schema already specified once. \n\tExisting schema: {schema}\n\tAdditional schema: {binder.Name}"); - result = new DynamicStoredProcedure(connection, transformers, token, timeout, binder.Name, executionMode); + result = new DynamicStoredProcedure(connection, transformers, token, timeout, binder.Name, executionMode, hasResults); return true; } @@ -166,6 +171,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o parameters, transformers, executionMode, + hasResults, token); return true; diff --git a/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedureResults.cs b/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedureResults.cs index 90ad380..8c97fac 100644 --- a/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedureResults.cs +++ b/CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedureResults.cs @@ -25,6 +25,7 @@ internal class DynamicStoredProcedureResults : DynamicObject, IDisposable }); private readonly Task resultTask; + private readonly Task nonQueryTask; private readonly IDbConnection connection; private readonly IDbCommand command; private readonly IEnumerable transformers; @@ -40,6 +41,7 @@ public DynamicStoredProcedureResults( List parameters, IEnumerable transformers, DynamicExecutionMode executionMode, + bool hasResults, CancellationToken token) { Contract.Requires(connection != null); @@ -57,27 +59,48 @@ public DynamicStoredProcedureResults( foreach (var p in parameters) command.Parameters.Add(p.CreateDbDataParameter(command)); - if (executionMode == DynamicExecutionMode.Synchronous) + if (!hasResults) + { + if (executionMode == DynamicExecutionMode.Synchronous) + { + command.ExecuteNonQuery(); + TransferOutputParameters(parameters, token); + } + else + { +#if !NET40 + var sqlCommand = command as SqlCommand; + if (sqlCommand != null) + nonQueryTask = sqlCommand.ExecuteNonQueryAsync(token); + else +#endif + nonQueryTask = Task.Factory.StartNew(() => command.ExecuteNonQuery(), + token, + TaskCreationOptions.None, + TaskScheduler.Default); + + nonQueryTask = nonQueryTask.ContinueWith(_ => TransferOutputParameters(parameters, token), token); + } + } + else if (executionMode == DynamicExecutionMode.Synchronous) { var tcs = new TaskCompletionSource(); token.ThrowIfCancellationRequested(); var res = command.ExecuteReader(); - token.ThrowIfCancellationRequested(); - foreach (IDbDataParameter p in command.Parameters) + TransferOutputParameters(parameters, token); + + if (token.IsCancellationRequested) { - if (p.Direction != ParameterDirection.Input) - { - parameters.OfType() - .FirstOrDefault(sp => sp.ParameterName == p.ParameterName) - ?.TransferOutputValue(p.Value); - } + res.Dispose(); + token.ThrowIfCancellationRequested(); + } + else + { + tcs.SetResult(res); + resultTask = tcs.Task; } - - token.ThrowIfCancellationRequested(); - tcs.SetResult(res); - resultTask = tcs.Task; } else { @@ -93,19 +116,10 @@ public DynamicStoredProcedureResults( TaskScheduler.Default); resultTask = resultTask.ContinueWith(r => - { - foreach (IDbDataParameter p in command.Parameters) - { - if (p.Direction != ParameterDirection.Input) - { - parameters.OfType() - .FirstOrDefault(sp => sp.ParameterName == p.ParameterName) - ?.TransferOutputValue(p.Value); - } - } - - return r.Result; - }, token); + { + TransferOutputParameters(parameters, token); + return r.Result; + }, token); } } @@ -117,29 +131,62 @@ public void Dispose() command?.Dispose(); } - private IEnumerable GetResults(bool isSingle) => RowFactory.Create(isSingle).ParseRows(resultTask.Result, transformers, token); + private IEnumerable GetResults(bool isSingle) + { + if (resultTask == null) + throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set."); + + try + { + return RowFactory.Create(isSingle).ParseRows(resultTask.Result, transformers, token); + } + finally + { + if (isSingle) + resultTask.Result.Dispose(); + } + } private Task ContinueNoResults() { + if (nonQueryTask != null) + { + return nonQueryTask.ContinueWith(r => + { + Dispose(); + + if (r.Status == TaskStatus.Faulted) + throw r.Exception; + }); + } + return resultTask.ContinueWith(r => { + r.Result.Dispose(); Dispose(); + if (r.Status == TaskStatus.Faulted) throw r.Exception; - }, token); + }); } private Task> CreateSingleContinuation() { + if (resultTask == null) + throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set."); + return resultTask.ContinueWith(_ => { try { return GetResults(true); } finally { Dispose(); } - }, token); + }); } private Task CreateSingleRowContinuation() { + if (resultTask == null) + throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set."); + return resultTask.ContinueWith(_ => { try { return GetResults(true).SingleOrDefault(); } @@ -150,29 +197,43 @@ private Task CreateSingleRowContinuation() private T GetMultipleResults() { Contract.Ensures(Contract.Result() != null); - - var types = typeof(T).GetGenericArguments(); - var res = types.Select((t, i) => + + if (resultTask == null) + throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set."); + + try { - if (i > 0) - resultTask.Result.NextResult(); + var types = typeof(T).GetGenericArguments(); + var res = types.Select((t, i) => + { + if (i > 0) + resultTask.Result.NextResult(); - token.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); - return getResultsMethod.Value - .MakeGenericMethod(t.GetEnumeratedType()) - .Invoke(this, new object[] { false }); - }).ToArray(); + return getResultsMethod.Value + .MakeGenericMethod(t.GetEnumeratedType()) + .Invoke(this, new object[] { false }); + }).ToArray(); - return (T)tupleCreates.Value[types.Length] - .MakeGenericMethod(types) - .Invoke(null, res); + return (T)tupleCreates.Value[types.Length] + .MakeGenericMethod(types) + .Invoke(null, res); + } + finally + { + resultTask.Result.Dispose(); + } } private Task CreateMultipleContinuation() { Contract.Ensures(Contract.Result>() != null); + if (resultTask == null) + throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set."); + + return resultTask.ContinueWith(_ => { try { return GetMultipleResults(); } @@ -191,7 +252,21 @@ private object InternalGetAwaiter() if (executionMode == DynamicExecutionMode.Synchronous) throw new NotSupportedException(DynamicStoredProcedure.asyncParameterDirectionError); - return DynamicStoredProcedureResultsAwaiter.Create(this, resultTask, continueOnCaller); + return DynamicStoredProcedureResultsAwaiter.Create(this, nonQueryTask ?? resultTask, continueOnCaller); + } + + private void TransferOutputParameters(List parameters, CancellationToken token) + { + foreach (IDbDataParameter p in command.Parameters) + { + token.ThrowIfCancellationRequested(); + if (p.Direction != ParameterDirection.Input) + { + parameters.OfType() + .FirstOrDefault(sp => sp.ParameterName == p.ParameterName) + ?.TransferOutputValue(p.Value); + } + } } private class Meta : DynamicMetaObject diff --git a/CodeOnlyStoredProcedure/IDbConnectionExtensions.cs b/CodeOnlyStoredProcedure/IDbConnectionExtensions.cs index 879bc45..194af7a 100644 --- a/CodeOnlyStoredProcedure/IDbConnectionExtensions.cs +++ b/CodeOnlyStoredProcedure/IDbConnectionExtensions.cs @@ -45,7 +45,7 @@ public static dynamic Execute(this IDbConnection connection, params IDataTransfo /// All parameters must be named. However, they can be marked as ref (InputOutput SQL Parameter) or /// out (Output SQL Parameter). /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: - /// IEnumerable<Person> people = connection.Execute() + /// IEnumerable<Person> people = connection.Execute(30) /// .data_schema // if omitted, defaults to dbo /// .usp_GetPeople(minimumAge: 20); /// @@ -59,7 +59,8 @@ public static dynamic Execute(this IDbConnection connection, int timeout, params transformers, CancellationToken.None, timeout, - executionMode: DynamicExecutionMode.Synchronous); + DynamicExecutionMode.Synchronous, + true); } /// @@ -69,12 +70,13 @@ public static dynamic Execute(this IDbConnection connection, int timeout, params /// The IDbConnection to use to execute the Stored Procedure. /// The s to use to massage the data in the result set. /// A dynamic object that represents the results from the StoredProcedure execution. - /// All parameters must be named. However, they can be marked as ref (InputOutput SQL Parameter) or - /// out (Output SQL Parameter). + /// All parameters must be named. Since this is an asynchronous call, they can not be marked as ref (InputOutput SQL Parameter) or + /// out (Output SQL Parameter). If your stored procedure returns multiple result sets, cast the result to a + /// whose type parameters are values. /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: - /// IEnumerable<Person> people = connection.ExecuteAsync() - /// .data_schema // if omitted, defaults to dbo - /// .usp_GetPeople(minimumAge: 20); + /// IEnumerable<Person> people = await connection.ExecuteAsync(token) + /// .data_schema // if omitted, defaults to dbo + /// .usp_GetPeople(minimumAge: 20); /// public static dynamic ExecuteAsync(this IDbConnection connection, params IDataTransformer[] transformers) { @@ -93,12 +95,13 @@ public static dynamic ExecuteAsync(this IDbConnection connection, params IDataTr /// The amount of time in seconds before the stored procedure will be aborted. /// The s to use to massage the data in the result set. /// A dynamic object that represents the results from the StoredProcedure execution. - /// All parameters must be named. However, they can be marked as ref (InputOutput SQL Parameter) or - /// out (Output SQL Parameter). + /// All parameters must be named. Since this is an asynchronous call, they can not be marked as ref (InputOutput SQL Parameter) or + /// out (Output SQL Parameter). If your stored procedure returns multiple result sets, cast the result to a + /// whose type parameters are values. /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: - /// IEnumerable<Person> people = connection.ExecuteAsync(15, new InternAllStringstransformer()) - /// .data_schema // if omitted, defaults to dbo - /// .usp_GetPeople(minimumAge: 20); + /// IEnumerable<Person> people = await connection.ExecuteAsync(142) + /// .data_schema // if omitted, defaults to dbo + /// .usp_GetPeople(minimumAge: 20); /// public static dynamic ExecuteAsync(this IDbConnection connection, int timeout, params IDataTransformer[] transformers) { @@ -117,14 +120,13 @@ public static dynamic ExecuteAsync(this IDbConnection connection, int timeout, p /// The to use to cancel the execution of the Stored Procedure. /// The s to use to massage the data in the result set. /// A dynamic object that represents the results from the StoredProcedure execution. - /// All parameters must be named. However, they can be marked as ref (InputOutput SQL Parameter) or - /// out (Output SQL Parameter). Unless you try to await (or cast to a Task) the result from the Call(). - /// Since .NET has no easy way of returning multiple items from a Task, attempting to do so - /// will fail. + /// All parameters must be named. Since this is an asynchronous call, they can not be marked as ref (InputOutput SQL Parameter) or + /// out (Output SQL Parameter). If your stored procedure returns multiple result sets, cast the result to a + /// whose type parameters are values. /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: - /// IEnumerable<Person> people = connection.ExecuteAsync(token) - /// .data_schema // if omitted, defaults to dbo - /// .usp_GetPeople(minimumAge: 20); + /// IEnumerable<Person> people = await connection.ExecuteAsync(token) + /// .data_schema // if omitted, defaults to dbo + /// .usp_GetPeople(minimumAge: 20); /// public static dynamic ExecuteAsync(this IDbConnection connection, CancellationToken token, params IDataTransformer[] transformers) { @@ -144,14 +146,13 @@ public static dynamic ExecuteAsync(this IDbConnection connection, CancellationTo /// The amount of time in seconds before the stored procedure will be aborted. /// The s to use to massage the data in the result set. /// A dynamic object that represents the results from the StoredProcedure execution. - /// All parameters must be named. However, they can be marked as ref (InputOutput SQL Parameter) or - /// out (Output SQL Parameter). Unless you try to await (or cast to a Task) the result from the Call(). - /// Since .NET has no easy way of returning multiple items from a Task, attempting to do so - /// will fail. + /// All parameters must be named. Since this is an asynchronous call, they can not be marked as ref (InputOutput SQL Parameter) or + /// out (Output SQL Parameter). If your stored procedure returns multiple result sets, cast the result to a + /// whose type parameters are values. /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: - /// IEnumerable<Person> people = connection.ExecuteAsync(token) - /// .data_schema // if omitted, defaults to dbo - /// .usp_GetPeople(minimumAge: 20); + /// IEnumerable<Person> people = await connection.ExecuteAsync(token, 42) + /// .data_schema // if omitted, defaults to dbo + /// .usp_GetPeople(minimumAge: 20); /// public static dynamic ExecuteAsync(this IDbConnection connection, CancellationToken token, int timeout, params IDataTransformer[] transformers) { @@ -163,7 +164,161 @@ public static dynamic ExecuteAsync(this IDbConnection connection, CancellationTo transformers, token, timeout, - executionMode: DynamicExecutionMode.Asynchronous); + DynamicExecutionMode.Asynchronous, + true); + } + + /// + /// Calls a StoredProcedure that does not return a result set using a dynamic syntax synchronously. Since it returns no + /// results, there will be nothing to cast to. + /// + /// The IDbConnection to use to execute the Stored Procedure. + /// The s to use to massage the data in the result set. + /// A dynamic object that can be used to easily call your stored procedure. + /// All parameters must be named. However, they can be marked as ref (InputOutput SQL Parameter) or + /// out (Output SQL Parameter). + /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: + /// connection.ExecuteNonQuery() + /// .data_schema // if omitted, defaults to dbo + /// .usp_SaveAge(personId: 16, age: 34); + /// + public static dynamic ExecuteNonQuery(this IDbConnection connection, params IDataTransformer[] transformers) + { + Contract.Requires(connection != null); + Contract.Requires(transformers != null && Contract.ForAll(transformers, t => t != null)); + Contract.Ensures (Contract.Result() != null); + + return connection.ExecuteNonQuery(StoredProcedure.defaultTimeout, transformers); + } + + /// + /// Calls a StoredProcedure that does not return a result set using a dynamic syntax synchronously. Since it returns no + /// results, there will be nothing to cast to. + /// + /// The IDbConnection to use to execute the Stored Procedure. + /// The amount of time in seconds before the stored procedure will be aborted. + /// The s to use to massage the data in the result set. + /// A dynamic object that can be used to easily call your stored procedure. + /// All parameters must be named. However, they can be marked as ref (InputOutput SQL Parameter) or + /// out (Output SQL Parameter). + /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: + /// connection.ExecuteNonQuery(45) + /// .data_schema // if omitted, defaults to dbo + /// .usp_SaveAge(personId: 16, age: 34); + /// + public static dynamic ExecuteNonQuery(this IDbConnection connection, int timeout, params IDataTransformer[] transformers) + { + Contract.Requires(connection != null); + Contract.Requires(transformers != null && Contract.ForAll(transformers, t => t != null)); + Contract.Ensures(Contract.Result() != null); + + return new DynamicStoredProcedure(connection, + transformers, + CancellationToken.None, + timeout, + DynamicExecutionMode.Synchronous, + false); + } + + /// + /// Calls a StoredProcedure that does not return a result set using a dynamic syntax asynchronously. Since it returns no + /// results, there will be nothing to cast to. + /// + /// The IDbConnection to use to execute the Stored Procedure. + /// The s to use to massage the data in the result set. + /// A dynamic object that can be used to easily call your stored procedure. + /// All parameters must be named. Since this is an asynchronous call, they can not be marked as ref + /// (InputOutput SQL Parameter) or out (Output SQL Parameter). + /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: + /// await connection.ExecuteNonQueryAsync() + /// .data_schema // if omitted, defaults to dbo + /// .usp_SaveAge(personId: 16, age: 34); + /// + public static dynamic ExecuteNonQueryAsync(this IDbConnection connection, params IDataTransformer[] transformers) + { + Contract.Requires(connection != null); + Contract.Requires(transformers != null && Contract.ForAll(transformers, t => t != null)); + Contract.Ensures(Contract.Result() != null); + + return connection.ExecuteNonQueryAsync(CancellationToken.None, StoredProcedure.defaultTimeout, transformers); + } + + /// + /// Calls a StoredProcedure that does not return a result set using a dynamic syntax asynchronously. Since it returns no + /// results, there will be nothing to cast to. + /// + /// The IDbConnection to use to execute the Stored Procedure. + /// The to use to cancel the execution of the Stored Procedure. + /// The s to use to massage the data in the result set. + /// A dynamic object that can be used to easily call your stored procedure. + /// All parameters must be named. Since this is an asynchronous call, they can not be marked as ref + /// (InputOutput SQL Parameter) or out (Output SQL Parameter). + /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: + /// await connection.ExecuteNonQueryAsync(token) + /// .data_schema // if omitted, defaults to dbo + /// .usp_SaveAge(personId: 16, age: 34); + /// + public static dynamic ExecuteNonQueryAsync(this IDbConnection connection, CancellationToken token, params IDataTransformer[] transformers) + { + Contract.Requires(connection != null); + Contract.Requires(transformers != null && Contract.ForAll(transformers, t => t != null)); + Contract.Ensures(Contract.Result() != null); + + return connection.ExecuteNonQueryAsync(token, StoredProcedure.defaultTimeout, transformers); + } + + /// + /// Calls a StoredProcedure that does not return a result set using a dynamic syntax asynchronously. Since it returns no + /// results, there will be nothing to cast to. + /// + /// The IDbConnection to use to execute the Stored Procedure. + /// The amount of time in seconds before the stored procedure will be aborted. + /// The s to use to massage the data in the result set. + /// A dynamic object that can be used to easily call your stored procedure. + /// All parameters must be named. Since this is an asynchronous call, they can not be marked as ref + /// (InputOutput SQL Parameter) or out (Output SQL Parameter). + /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: + /// await connection.ExecuteNonQueryAsync(token, 30) + /// .data_schema // if omitted, defaults to dbo + /// .usp_SaveAge(personId: 16, age: 34); + /// + public static dynamic ExecuteNonQueryAsync(this IDbConnection connection, int timeout, params IDataTransformer[] transformers) + { + Contract.Requires(connection != null); + Contract.Requires(transformers != null && Contract.ForAll(transformers, t => t != null)); + Contract.Ensures(Contract.Result() != null); + + return connection.ExecuteNonQueryAsync(CancellationToken.None, timeout, transformers); + } + + /// + /// Calls a StoredProcedure that does not return a result set using a dynamic syntax asynchronously. Since it returns no + /// results, there will be nothing to cast to. + /// + /// The IDbConnection to use to execute the Stored Procedure. + /// The to use to cancel the execution of the Stored Procedure. + /// The amount of time in seconds before the stored procedure will be aborted. + /// The s to use to massage the data in the result set. + /// A dynamic object that can be used to easily call your stored procedure. + /// All parameters must be named. Since this is an asynchronous call, they can not be marked as ref + /// (InputOutput SQL Parameter) or out (Output SQL Parameter). + /// Since this uses a dynamic syntax, you can execute StoredProcedures with a much cleaner style: + /// await connection.ExecuteNonQueryAsync(token, 30) + /// .data_schema // if omitted, defaults to dbo + /// .usp_SaveAge(personId: 16, age: 34); + /// + public static dynamic ExecuteNonQueryAsync(this IDbConnection connection, CancellationToken token, int timeout, params IDataTransformer[] transformers) + { + Contract.Requires(connection != null); + Contract.Requires(transformers != null && Contract.ForAll(transformers, t => t != null)); + Contract.Ensures(Contract.Result() != null); + + return new DynamicStoredProcedure(connection, + transformers, + token, + timeout, + DynamicExecutionMode.Asynchronous, + false); } /// diff --git a/CodeOnlyStoredProcedure/StoredProcedure.cs b/CodeOnlyStoredProcedure/StoredProcedure.cs index 69486e3..c967836 100644 --- a/CodeOnlyStoredProcedure/StoredProcedure.cs +++ b/CodeOnlyStoredProcedure/StoredProcedure.cs @@ -351,6 +351,8 @@ public static void AddGlobalTransformer(IDataTransformer transformer) /// of the connection, we recommend disabling connection cloning. Especially if you make a lot of stored /// procedure calls. /// + /// Make sure your connection supports multiple active result sets, or concurrent calls will throw + /// an exception. public static void DisableConnectionCloningForEachCall() => GlobalSettings.Instance.CloneConnectionForEachCall = false; #endregion diff --git a/CodeOnlyStoredProcedure/StoredProcedure.generated.cs b/CodeOnlyStoredProcedure/StoredProcedure.generated.cs index 040f18a..1b94103 100644 --- a/CodeOnlyStoredProcedure/StoredProcedure.generated.cs +++ b/CodeOnlyStoredProcedure/StoredProcedure.generated.cs @@ -113,8 +113,10 @@ private IEnumerable Execute(IDbConnection connection, CancellationToken toke token.ThrowIfCancellationRequested(); - var reader = cmd.ExecuteReader(); - results = T1Factory.ParseRows(reader, DataTransformers, token); + using (var reader = cmd.ExecuteReader()) + { + results = T1Factory.ParseRows(reader, DataTransformers, token); + } } connection?.Close(); @@ -188,11 +190,13 @@ async Task> ExecuteAsync(DbCommand cmd, IDbConnection toClose, C var dbParameters = AddParameters(cmd); token.ThrowIfCancellationRequested(); - var reader = await cmd.ExecuteReaderAsync(token); - results = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); + using (var reader = await cmd.ExecuteReaderAsync(token)) + { + results = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); - token.ThrowIfCancellationRequested(); - TransferOutputParameters(token, dbParameters); + token.ThrowIfCancellationRequested(); + TransferOutputParameters(token, dbParameters); + } toClose?.Close(); cmd.Dispose(); @@ -335,18 +339,20 @@ private Tuple, IEnumerable> Execute(IDbConnection connection token.ThrowIfCancellationRequested(); - var reader = cmd.ExecuteReader(); - var t1 = T1Factory.ParseRows(reader, DataTransformers, token); + using (var reader = cmd.ExecuteReader()) + { + var t1 = T1Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t2 = T2Factory.ParseRows(reader, DataTransformers, token); - results = Tuple.Create(t1, t2); + var t2 = T2Factory.ParseRows(reader, DataTransformers, token); + results = Tuple.Create(t1, t2); - token.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); - TransferOutputParameters(CancellationToken.None, dbParameters); + TransferOutputParameters(CancellationToken.None, dbParameters); + } } connection?.Close(); @@ -420,14 +426,16 @@ async Task, IEnumerable>> ExecuteAsync(DbCommand cmd, var dbParameters = AddParameters(cmd); token.ThrowIfCancellationRequested(); - var reader = await cmd.ExecuteReaderAsync(token); - var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); + using (var reader = await cmd.ExecuteReaderAsync(token)) + { + var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); var t2 = await T2Factory.ParseRowsAsync(reader, DataTransformers, token); results = Tuple.Create(t1, t2); - token.ThrowIfCancellationRequested(); - TransferOutputParameters(token, dbParameters); + token.ThrowIfCancellationRequested(); + TransferOutputParameters(token, dbParameters); + } toClose?.Close(); cmd.Dispose(); @@ -586,23 +594,25 @@ private Tuple, IEnumerable, IEnumerable> Execute(IDbConn token.ThrowIfCancellationRequested(); - var reader = cmd.ExecuteReader(); - var t1 = T1Factory.ParseRows(reader, DataTransformers, token); + using (var reader = cmd.ExecuteReader()) + { + var t1 = T1Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t2 = T2Factory.ParseRows(reader, DataTransformers, token); + var t2 = T2Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t3 = T3Factory.ParseRows(reader, DataTransformers, token); - results = Tuple.Create(t1, t2, t3); + var t3 = T3Factory.ParseRows(reader, DataTransformers, token); + results = Tuple.Create(t1, t2, t3); - token.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); - TransferOutputParameters(CancellationToken.None, dbParameters); + TransferOutputParameters(CancellationToken.None, dbParameters); + } } connection?.Close(); @@ -676,16 +686,18 @@ async Task, IEnumerable, IEnumerable>> ExecuteAsyn var dbParameters = AddParameters(cmd); token.ThrowIfCancellationRequested(); - var reader = await cmd.ExecuteReaderAsync(token); - var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); + using (var reader = await cmd.ExecuteReaderAsync(token)) + { + var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); var t2 = await T2Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); var t3 = await T3Factory.ParseRowsAsync(reader, DataTransformers, token); results = Tuple.Create(t1, t2, t3); - token.ThrowIfCancellationRequested(); - TransferOutputParameters(token, dbParameters); + token.ThrowIfCancellationRequested(); + TransferOutputParameters(token, dbParameters); + } toClose?.Close(); cmd.Dispose(); @@ -849,28 +861,30 @@ private Tuple, IEnumerable, IEnumerable, IEnumerable token.ThrowIfCancellationRequested(); - var reader = cmd.ExecuteReader(); - var t1 = T1Factory.ParseRows(reader, DataTransformers, token); + using (var reader = cmd.ExecuteReader()) + { + var t1 = T1Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t2 = T2Factory.ParseRows(reader, DataTransformers, token); + var t2 = T2Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t3 = T3Factory.ParseRows(reader, DataTransformers, token); + var t3 = T3Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t4 = T4Factory.ParseRows(reader, DataTransformers, token); - results = Tuple.Create(t1, t2, t3, t4); + var t4 = T4Factory.ParseRows(reader, DataTransformers, token); + results = Tuple.Create(t1, t2, t3, t4); - token.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); - TransferOutputParameters(CancellationToken.None, dbParameters); + TransferOutputParameters(CancellationToken.None, dbParameters); + } } connection?.Close(); @@ -944,8 +958,9 @@ async Task, IEnumerable, IEnumerable, IEnumerable< var dbParameters = AddParameters(cmd); token.ThrowIfCancellationRequested(); - var reader = await cmd.ExecuteReaderAsync(token); - var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); + using (var reader = await cmd.ExecuteReaderAsync(token)) + { + var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); var t2 = await T2Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); @@ -954,8 +969,9 @@ async Task, IEnumerable, IEnumerable, IEnumerable< var t4 = await T4Factory.ParseRowsAsync(reader, DataTransformers, token); results = Tuple.Create(t1, t2, t3, t4); - token.ThrowIfCancellationRequested(); - TransferOutputParameters(token, dbParameters); + token.ThrowIfCancellationRequested(); + TransferOutputParameters(token, dbParameters); + } toClose?.Close(); cmd.Dispose(); @@ -1124,33 +1140,35 @@ private Tuple, IEnumerable, IEnumerable, IEnumerable token.ThrowIfCancellationRequested(); - var reader = cmd.ExecuteReader(); - var t1 = T1Factory.ParseRows(reader, DataTransformers, token); + using (var reader = cmd.ExecuteReader()) + { + var t1 = T1Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t2 = T2Factory.ParseRows(reader, DataTransformers, token); + var t2 = T2Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t3 = T3Factory.ParseRows(reader, DataTransformers, token); + var t3 = T3Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t4 = T4Factory.ParseRows(reader, DataTransformers, token); + var t4 = T4Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t5 = T5Factory.ParseRows(reader, DataTransformers, token); - results = Tuple.Create(t1, t2, t3, t4, t5); + var t5 = T5Factory.ParseRows(reader, DataTransformers, token); + results = Tuple.Create(t1, t2, t3, t4, t5); - token.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); - TransferOutputParameters(CancellationToken.None, dbParameters); + TransferOutputParameters(CancellationToken.None, dbParameters); + } } connection?.Close(); @@ -1224,8 +1242,9 @@ async Task, IEnumerable, IEnumerable, IEnumerable< var dbParameters = AddParameters(cmd); token.ThrowIfCancellationRequested(); - var reader = await cmd.ExecuteReaderAsync(token); - var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); + using (var reader = await cmd.ExecuteReaderAsync(token)) + { + var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); var t2 = await T2Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); @@ -1236,8 +1255,9 @@ async Task, IEnumerable, IEnumerable, IEnumerable< var t5 = await T5Factory.ParseRowsAsync(reader, DataTransformers, token); results = Tuple.Create(t1, t2, t3, t4, t5); - token.ThrowIfCancellationRequested(); - TransferOutputParameters(token, dbParameters); + token.ThrowIfCancellationRequested(); + TransferOutputParameters(token, dbParameters); + } toClose?.Close(); cmd.Dispose(); @@ -1411,38 +1431,40 @@ private Tuple, IEnumerable, IEnumerable, IEnumerable token.ThrowIfCancellationRequested(); - var reader = cmd.ExecuteReader(); - var t1 = T1Factory.ParseRows(reader, DataTransformers, token); + using (var reader = cmd.ExecuteReader()) + { + var t1 = T1Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t2 = T2Factory.ParseRows(reader, DataTransformers, token); + var t2 = T2Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t3 = T3Factory.ParseRows(reader, DataTransformers, token); + var t3 = T3Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t4 = T4Factory.ParseRows(reader, DataTransformers, token); + var t4 = T4Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t5 = T5Factory.ParseRows(reader, DataTransformers, token); + var t5 = T5Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t6 = T6Factory.ParseRows(reader, DataTransformers, token); - results = Tuple.Create(t1, t2, t3, t4, t5, t6); + var t6 = T6Factory.ParseRows(reader, DataTransformers, token); + results = Tuple.Create(t1, t2, t3, t4, t5, t6); - token.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); - TransferOutputParameters(CancellationToken.None, dbParameters); + TransferOutputParameters(CancellationToken.None, dbParameters); + } } connection?.Close(); @@ -1516,8 +1538,9 @@ async Task, IEnumerable, IEnumerable, IEnumerable< var dbParameters = AddParameters(cmd); token.ThrowIfCancellationRequested(); - var reader = await cmd.ExecuteReaderAsync(token); - var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); + using (var reader = await cmd.ExecuteReaderAsync(token)) + { + var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); var t2 = await T2Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); @@ -1530,8 +1553,9 @@ async Task, IEnumerable, IEnumerable, IEnumerable< var t6 = await T6Factory.ParseRowsAsync(reader, DataTransformers, token); results = Tuple.Create(t1, t2, t3, t4, t5, t6); - token.ThrowIfCancellationRequested(); - TransferOutputParameters(token, dbParameters); + token.ThrowIfCancellationRequested(); + TransferOutputParameters(token, dbParameters); + } toClose?.Close(); cmd.Dispose(); @@ -1710,43 +1734,45 @@ private Tuple, IEnumerable, IEnumerable, IEnumerable token.ThrowIfCancellationRequested(); - var reader = cmd.ExecuteReader(); - var t1 = T1Factory.ParseRows(reader, DataTransformers, token); + using (var reader = cmd.ExecuteReader()) + { + var t1 = T1Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t2 = T2Factory.ParseRows(reader, DataTransformers, token); + var t2 = T2Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t3 = T3Factory.ParseRows(reader, DataTransformers, token); + var t3 = T3Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t4 = T4Factory.ParseRows(reader, DataTransformers, token); + var t4 = T4Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t5 = T5Factory.ParseRows(reader, DataTransformers, token); + var t5 = T5Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t6 = T6Factory.ParseRows(reader, DataTransformers, token); + var t6 = T6Factory.ParseRows(reader, DataTransformers, token); - reader.NextResult(); - token.ThrowIfCancellationRequested(); + reader.NextResult(); + token.ThrowIfCancellationRequested(); - var t7 = T7Factory.ParseRows(reader, DataTransformers, token); - results = Tuple.Create(t1, t2, t3, t4, t5, t6, t7); + var t7 = T7Factory.ParseRows(reader, DataTransformers, token); + results = Tuple.Create(t1, t2, t3, t4, t5, t6, t7); - token.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); - TransferOutputParameters(CancellationToken.None, dbParameters); + TransferOutputParameters(CancellationToken.None, dbParameters); + } } connection?.Close(); @@ -1820,8 +1846,9 @@ async Task, IEnumerable, IEnumerable, IEnumerable< var dbParameters = AddParameters(cmd); token.ThrowIfCancellationRequested(); - var reader = await cmd.ExecuteReaderAsync(token); - var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); + using (var reader = await cmd.ExecuteReaderAsync(token)) + { + var t1 = await T1Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); var t2 = await T2Factory.ParseRowsAsync(reader, DataTransformers, token); await reader.NextResultAsync(token); @@ -1836,8 +1863,9 @@ async Task, IEnumerable, IEnumerable, IEnumerable< var t7 = await T7Factory.ParseRowsAsync(reader, DataTransformers, token); results = Tuple.Create(t1, t2, t3, t4, t5, t6, t7); - token.ThrowIfCancellationRequested(); - TransferOutputParameters(token, dbParameters); + token.ThrowIfCancellationRequested(); + TransferOutputParameters(token, dbParameters); + } toClose?.Close(); cmd.Dispose(); diff --git a/CodeOnlyStoredProcedure/StoredProcedureTemplate.tt b/CodeOnlyStoredProcedure/StoredProcedureTemplate.tt index f468c7e..62a75d7 100644 --- a/CodeOnlyStoredProcedure/StoredProcedureTemplate.tt +++ b/CodeOnlyStoredProcedure/StoredProcedureTemplate.tt @@ -122,11 +122,15 @@ public class StoredProcedureTemplate : Template WriteLine(""); WriteLine("token.ThrowIfCancellationRequested();"); WriteLine(""); - WriteLine("var reader = cmd.ExecuteReader();"); + WriteLine("using (var reader = cmd.ExecuteReader())"); + WriteLine("{"); + PushIndent("\t"); if (TypeCount == 1) { WriteLine("results = T1Factory.ParseRows(reader, DataTransformers, token);"); + PopIndent(); + WriteLine("}"); PopIndent(); return; } @@ -156,6 +160,9 @@ public class StoredProcedureTemplate : Template WriteLine(""); WriteLine("TransferOutputParameters(CancellationToken.None, dbParameters);"); + PopIndent(); + WriteLine("}"); + PopIndent(); } @@ -183,6 +190,7 @@ public class StoredProcedureTemplate : Template Write(", t" + i); WriteLine(");"); + PopIndent(); } @@ -407,11 +415,13 @@ public class StoredProcedureTemplate : Template var dbParameters = AddParameters(cmd); token.ThrowIfCancellationRequested(); - var reader = await cmd.ExecuteReaderAsync(token); - <#+ WriteReadResultsAsync(); #> + using (var reader = await cmd.ExecuteReaderAsync(token)) + { + <#+ WriteReadResultsAsync(); #> - token.ThrowIfCancellationRequested(); - TransferOutputParameters(token, dbParameters); + token.ThrowIfCancellationRequested(); + TransferOutputParameters(token, dbParameters); + } toClose?.Close(); cmd.Dispose(); diff --git a/CodeOnlyTests/Dynamic/DynamicStoredProcedureTests.cs b/CodeOnlyTests/Dynamic/DynamicStoredProcedureTests.cs index 273e837..f0a6952 100644 --- a/CodeOnlyTests/Dynamic/DynamicStoredProcedureTests.cs +++ b/CodeOnlyTests/Dynamic/DynamicStoredProcedureTests.cs @@ -32,7 +32,7 @@ public void CanSpecifyCustomSchema() { var ctx = CreatePeople("Foo"); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); IEnumerable people = toTest.foo.usp_GetPeople(); people.Should().ContainSingle("Foo", "because only one person should have been returned."); @@ -47,7 +47,7 @@ public void MultipleSchemasThrowsException() { var ctx = CreatePeople("Foo"); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); IEnumerable res = null; Action shouldThrow = () => res = toTest.foo.bar.usp_GetPeople(); @@ -64,7 +64,7 @@ public void CanCallWithoutArguments() { var ctx = CreatePeople("Foo"); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); IEnumerable people = toTest.usp_GetPeople(); @@ -78,7 +78,7 @@ public void ClosesClonedConnectionWhenDone_WithResults() var db2 = CreatePeople("Foo"); db.As().Setup(d => d.Clone()).Returns(db2); - dynamic toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); IEnumerable people = toTest.usp_GetPeople(); @@ -92,7 +92,7 @@ public void DisposesCommandWhenDone_WithResults() Mock cmd; var db = CreatePeople(out cmd, "Foo"); - dynamic toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); IEnumerable people = toTest.usp_GetPeople(); @@ -106,7 +106,7 @@ public void ClosesClonedConnectionWhenDone_WithSingleResult() var db2 = CreatePeople("Foo"); db.As().Setup(d => d.Clone()).Returns(db2); - dynamic toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); Person person = toTest.usp_GetPeople(); @@ -120,7 +120,7 @@ public void DisposesCommandWhenDone_WithSingleResult() Mock cmd; var db = CreatePeople(out cmd, "Foo"); - dynamic toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); Person person = toTest.usp_GetPeople(); @@ -134,7 +134,7 @@ public void ClosesClonedConnectionWhenDone_WithoutResults() var db2 = CreatePeople("Foo"); db.As().Setup(d => d.Clone()).Returns(db2); - dynamic toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); toTest.usp_NoQuery().Dispose(); @@ -148,7 +148,7 @@ public void DisposesCommandWhenDone_WithoutResults() Mock cmd; var db = CreatePeople(out cmd, "Foo"); - dynamic toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); toTest.usp_GetPeople().Dispose(); @@ -160,7 +160,7 @@ public void CanCastExplicitly() { var ctx = CreatePeople("Foo"); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); var people = (IEnumerable)toTest.usp_GetPeople(); @@ -173,7 +173,7 @@ public void CastingToWrongItemTypeThrows() var ctx = CreatePeople("Foo"); IEnumerable results = null; - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); this.Invoking(_ => results = (IEnumerable)toTest.usp_GetPeople()) .ShouldThrow("casting a result set to the wrong item type should fail"); results.Should().BeNull("casting a result set to the wrong item type should fail"); @@ -184,7 +184,7 @@ public void CanGetSingleResultWithoutExpectingIEnumerable() { var ctx = CreatePeople("Foo"); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); var foo = (Person)toTest.usp_GetPeople(); foo.FirstName.Should().Be("Foo", "because that is the name of the only person returned by the Stored Procedure"); @@ -193,14 +193,14 @@ public void CanGetSingleResultWithoutExpectingIEnumerable() [TestMethod] public void CanCallWithReturnValueFromNonQuery() { - var ctx = CreatePeople(parms => + var ctx = CreateNonQuery(parms => { var parm = ((IDbDataParameter)parms[0]); Assert.AreEqual(ParameterDirection.ReturnValue, parm.Direction, "Not passed as ReturnValue"); parm.Value = 42; }); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, false); int retValue; toTest.usp_StoredProc(returnValue: out retValue); @@ -219,7 +219,7 @@ public void CanCallWithRefParameterNoQuery() parm.Value = 42; }); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); int id = 16; toTest.usp_StoredProc(id: ref id); @@ -237,7 +237,7 @@ public void CanCallWithOutParameterNoQuery() parm.Value = 42; }); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); int id; toTest.usp_StoredProc(id: out id); @@ -257,7 +257,7 @@ public void CancelledTokenWillNotExecute() this.Invoking(_ => { - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, cts.Token, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, cts.Token, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); var value = 13; IEnumerable people = toTest.usp_StoredProc(value: value); @@ -270,7 +270,7 @@ public void CanGetMultipleResultSets() { var ctx = CreateFamily(); - dynamic toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); Tuple, IEnumerable> results = toTest.usp_GetPeople(); @@ -295,7 +295,7 @@ public void CanPassUnattributedTableValueParameterClass() var ctx = new Mock(); ctx.Setup(c => c.CreateCommand()).Returns(cmd.Object); - dynamic toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); toTest.usp_AddPeople(people: new[] { "Foo", "Bar" }.Select(s => new Person { FirstName = s })); @@ -321,7 +321,7 @@ public void CanNotPassAnonymousClassForTableValueParameter() var ctx = new Mock(); ctx.Setup(c => c.CreateCommand()).Returns(cmd.Object); - dynamic toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); this.Invoking(_ => { @@ -347,7 +347,7 @@ public void CanNotPassStringsForTableValueParameter() var ctx = new Mock(); ctx.Setup(c => c.CreateCommand()).Returns(cmd.Object); - dynamic toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); this.Invoking(_ => { @@ -368,7 +368,7 @@ public void CanPassNullParameter() parm.Value.Should().Be(DBNull.Value, "because null was passed"); }); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); toTest.usp_GetPeople(id: default(int?)); } @@ -383,7 +383,7 @@ public void CanPassDBNullParameter() parm.Value.Should().Be(DBNull.Value, "because DBNull was passed"); }); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); toTest.usp_GetPeople(id: DBNull.Value); } @@ -391,7 +391,7 @@ public void CanPassDBNullParameter() public void UnnamedParameters_Throw_Useful_Exception() { var ctx = CreatePeople("Foo"); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Synchronous, true); try { toTest.usp_GetPeople("foo"); @@ -418,7 +418,7 @@ public void CanCallAsyncWithNoArguments() { var ctx = CreatePeople("Foo"); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var result = GetPeople(toTest).Result; result.Single().FirstName.Should().Be("Foo"); @@ -431,7 +431,7 @@ public void ClosesClonedConnectionWhenDone_WithSingleResult() var db2 = CreatePeople(_ => { }, "Foo"); db.As().Setup(d => d.Clone()).Returns(db2); - var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var result = GetPerson(toTest).Result; @@ -450,7 +450,7 @@ public void DisposesCommandWhenDone_WithSingleResult() Mock cmd; var db = CreatePeople(out cmd, "Foo"); - var toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var result = GetPerson(toTest).Result; cmd.Verify(c => c.Dispose(), Times.Once()); @@ -463,7 +463,7 @@ public void ClosesClonedConnectionWhenDone_WithMultipleResults() var db2 = CreateFamily(); db.As().Setup(d => d.Clone()).Returns(db2.Object); - var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var result = GetFamilies(toTest).Result; @@ -477,7 +477,7 @@ public void DisposesCommandWhen_Done_WithMultipleResults() Mock cmd; var db = CreateFamily(out cmd); - var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var result = GetPerson(toTest).Result; cmd.Verify(c => c.Dispose(), Times.Once()); @@ -490,7 +490,7 @@ public void ClosesClonedConnectionWhenDone_WithResults() var db2 = CreatePeople(_ => { }, "Foo"); db.As().Setup(d => d.Clone()).Returns(() => db2); - var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var result = GetPeople(toTest).Result; @@ -509,7 +509,7 @@ public void DisposesCommandWhen_Done_WithResults() Mock cmd; var db = CreatePeople(out cmd, "Foo"); - var toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var result = GetPerson(toTest).Result; cmd.Verify(c => c.Dispose(), Times.Once()); @@ -522,7 +522,7 @@ public void ClosesClonedConnectionWhenDone_WithoutResults() var db2 = CreatePeople("Foo"); db.As().Setup(d => d.Clone()).Returns(db2); - var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(db.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); Call(toTest, new { test = "bar" }).Wait(); @@ -536,7 +536,7 @@ public void DisposesCommandWhen_Done_WithoutResults() Mock cmd; var db = CreatePeople(out cmd, "Foo"); - var toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); Call(toTest, new { test = "bar" }).Wait(); cmd.Verify(c => c.Dispose(), Times.Once()); @@ -547,7 +547,7 @@ public void CanGetSingleResultWithoutExpectingIEnumerable() { var ctx = CreatePeople("Foo"); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var foo = GetPerson(toTest).Result; foo.FirstName.Should().Be("Foo", "because that is the name of the only person returned by the Stored Procedure"); @@ -559,7 +559,7 @@ public void DisposesCommandWhen_Done_WithSingleResult() Mock cmd; var db = CreatePeople(out cmd, "Foo"); - var toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(db, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var foo = GetPerson(toTest).Result; cmd.Verify(c => c.Dispose(), Times.Once()); @@ -575,7 +575,7 @@ public void CallAsyncWithSimpleReturnValueThrows() parm.Value = 42; }, "Foo"); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); try { @@ -603,7 +603,7 @@ public void CallAsyncWithSimpleRefValueThrows() parm.Value = "Bar"; }, "Foo"); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); try { @@ -630,7 +630,7 @@ public void CallAsyncWithSimpleOutValueThrows() parm.Value = 42M; }, "Foo"); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); try { @@ -650,14 +650,14 @@ public void CallAsyncWithSimpleOutValueThrows() [TestMethod] public void CanCallAsyncWithReturnValueFromNonQuery() { - var ctx = CreatePeople(parms => + var ctx = CreateNonQuery(parms => { var parm = ((IDbDataParameter)parms[0]); Assert.AreEqual(ParameterDirection.ReturnValue, parm.Direction, "Not passed as ReturnValue"); parm.Value = 42; }); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, false); var retValue = new Return(); Call(toTest, retValue).Wait(); @@ -668,7 +668,7 @@ public void CanCallAsyncWithReturnValueFromNonQuery() [TestMethod] public void CanCallAsyncWithRefParameterNonQuery() { - var ctx = CreatePeople(parms => + var ctx = CreateNonQuery(parms => { var parm = ((IDbDataParameter)parms[0]); Assert.AreEqual(ParameterDirection.InputOutput, parm.Direction, "Not passed as InputOutput"); @@ -676,7 +676,7 @@ public void CanCallAsyncWithRefParameterNonQuery() parm.Value = 42; }); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, false); var inputOutput = new InputOutput { Value = 16 }; @@ -688,14 +688,14 @@ public void CanCallAsyncWithRefParameterNonQuery() [TestMethod] public void CanCallAsyncWithOutParameterNonQuery() { - var ctx = CreatePeople(parms => + var ctx = CreateNonQuery(parms => { var parm = ((IDbDataParameter)parms[0]); Assert.AreEqual(ParameterDirection.Output, parm.Direction, "Not passed as Output"); parm.Value = 42; }); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, false); var output = new Output(); @@ -714,7 +714,7 @@ public void CanExecuteAsyncWithReturnValue() parm.Value = 42; }, "Foo", "Bar"); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var retValue = new Return(); var people = GetPeople(toTest, retValue).Result; @@ -734,7 +734,7 @@ public void CanExecuteAsyncWithRefParameterValue() parm.Value = 42; }, "Bar", "Baz"); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var inout = new InputOutput { Value = 22 }; var people = GetPeople(toTest, inout).Result; @@ -753,7 +753,7 @@ public void CanExecuteAsyncWithOutParameterValue() parm.Value = 42; }, "Bar", "Baz"); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var output = new Output(); var people = GetPeople(toTest, output).Result; @@ -793,7 +793,7 @@ public void CanGetMultipleResultSetsAsync() ctx.Setup(c => c.CreateCommand()) .Returns(cmd.Object); - var toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx.Object, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var results = GetFamilies(toTest).Result; @@ -811,7 +811,7 @@ public void ConfigureAwaitControlsThreadContinuationHappensOn() // sleep so the task won't get inlined var ctx = CreatePeople(_ => Thread.Sleep(250), "Foo"); - var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, 400, DynamicExecutionMode.Asynchronous); + var toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, 400, DynamicExecutionMode.Asynchronous, true); var res = GetPeopleInBackground(toTest).Result; @@ -830,7 +830,7 @@ public async Task CanCastExplicitly() { var ctx = CreatePeople("Foo"); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var people = await (Task>)toTest.usp_GetPeople(); @@ -862,7 +862,7 @@ public async Task CanConfigureAwait() SynchronizationContext.SetSynchronizationContext(sync.Object); var ctx = CreatePeople(_ => lockr.Wait(TimeSpan.FromMilliseconds(100)), "Foo"); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); IEnumerable res = await toTest.usp_GetPeople().ConfigureAwait(true); SynchronizationContext.SetSynchronizationContext(oldContext); @@ -938,7 +938,7 @@ public void CanCastExplicitly() { var ctx = CreatePeople("Foo"); - dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous); + dynamic toTest = new DynamicStoredProcedure(ctx, transformers, CancellationToken.None, TEST_TIMEOUT, DynamicExecutionMode.Asynchronous, true); var people = (Task>)toTest.usp_GetPeople(); @@ -1001,11 +1001,24 @@ private static IDbConnection CreatePeople(out Mock command, params s command = new Mock(); return CreatePeople(_ => { }, command, names); } + private static IDbConnection CreatePeople(Action readerCallback, params string[] names) { return CreatePeople(readerCallback, new Mock(), names); } + private static IDbConnection CreateNonQuery(Action readerCallback, int returnValue = 0) + { + var db = new Mock(); + var cmd = new Mock(); + db.Setup(d => d.CreateCommand()).Returns(cmd.Object); + cmd.SetupAllProperties(); + var parms = SetupParameters(cmd); + cmd.Setup(c => c.ExecuteNonQuery()).Callback(() => readerCallback(parms)).Returns(returnValue); + + return db.Object; + } + private static IDbConnection CreatePeople(Action readerCallback, Mock cmd, params string[] names) { var reader = new Mock(); @@ -1027,12 +1040,22 @@ private static IDbConnection CreatePeople(Action reader setup = setup.Returns(true); setup.Returns(false); - - var parms = new DataParameterCollection(); cmd.SetupAllProperties(); + var parms = SetupParameters(cmd); cmd.Setup(c => c.ExecuteReader()) .Callback(() => readerCallback(parms)) .Returns(reader.Object); + + var ctx = new Mock(); + ctx.Setup(c => c.CreateCommand()) + .Returns(cmd.Object); + + return ctx.Object; + } + + private static DataParameterCollection SetupParameters(Mock cmd) + { + var parms = new DataParameterCollection(); cmd.SetupGet(c => c.Parameters) .Returns(parms); cmd.Setup(c => c.CreateParameter()) @@ -1043,12 +1066,7 @@ private static IDbConnection CreatePeople(Action reader return m.Object; }); - - var ctx = new Mock(); - ctx.Setup(c => c.CreateCommand()) - .Returns(cmd.Object); - - return ctx.Object; + return parms; } private static Mock CreateFamily(out Mock cmd) diff --git a/SmokeTests-NET40/App.config b/SmokeTests-NET40/App.config index 28c08a4..ecf73cd 100644 --- a/SmokeTests-NET40/App.config +++ b/SmokeTests-NET40/App.config @@ -5,7 +5,7 @@
- + diff --git a/SmokeTests/DynamicSyntax.cs b/SmokeTests/DynamicSyntax.cs index 460a2dd..65f26e3 100644 --- a/SmokeTests/DynamicSyntax.cs +++ b/SmokeTests/DynamicSyntax.cs @@ -217,7 +217,7 @@ async Task> AsyncEnumResultSet_WithParameter(IDbConnection d Tuple GetReturnValueWithOutParam(IDbConnection db) { int retVal = -1; - db.Execute(Program.timeout).usp_ReturnsOne(returnValue: out retVal); + db.ExecuteNonQuery(Program.timeout).usp_ReturnsOne(returnValue: out retVal); if (retVal != 1) return Tuple.Create(false, "ReturnValue not set"); @@ -229,7 +229,7 @@ Tuple GetReturnValueWithOutParam(IDbConnection db) Tuple GetReturnValueWithInputProperty(IDbConnection db) { var input = new ReturnsOne(); - db.Execute(Program.timeout).usp_ReturnsOne(input); + db.ExecuteNonQuery(Program.timeout).usp_ReturnsOne(input); if (input.ReturnValue != 1) return Tuple.Create(false, "ReturnValue not set"); @@ -241,7 +241,7 @@ Tuple GetReturnValueWithInputProperty(IDbConnection db) async Task> AsyncAwaitGetReturnValueWithInputProperty(IDbConnection db) { var input = new ReturnsOne(); - await db.ExecuteAsync(Program.timeout).usp_ReturnsOne(input); + await db.ExecuteNonQueryAsync(Program.timeout).usp_ReturnsOne(input); if (input.ReturnValue != 1) return Tuple.Create(false, "ReturnValue not set"); @@ -253,7 +253,7 @@ async Task> AsyncAwaitGetReturnValueWithInputProperty(IDbCon Task> AsyncTaskGetReturnValueWithInputProperty(IDbConnection db) { var input = new ReturnsOne(); - Task t = db.ExecuteAsync(Program.timeout).usp_ReturnsOne(input); + Task t = db.ExecuteNonQueryAsync(Program.timeout).usp_ReturnsOne(input); return t.ContinueWith(_ => { diff --git a/SmokeTests/Program.cs b/SmokeTests/Program.cs index e2af36f..f64e1e6 100644 --- a/SmokeTests/Program.cs +++ b/SmokeTests/Program.cs @@ -32,6 +32,12 @@ static int Main(string[] args) var res = RunTests(toTest.Database.Connection, smokeTests, (t, db) => t(db)); res &= RunTests(toTest.Database.Connection, asyncSmokeTests, (t, db) => t(db).Result); + CodeOnlyStoredProcedure.StoredProcedure.DisableConnectionCloningForEachCall(); + toTest.Database.Connection.Open(); + + res &= RunTests(toTest.Database.Connection, smokeTests, (t, db) => t(db), "No Connection Cloning "); + res &= RunTests(toTest.Database.Connection, asyncSmokeTests, (t, db) => t(db).Result, "No Connection Cloning "); + if (!res) { // tests failed @@ -51,18 +57,19 @@ static int Main(string[] args) private static bool RunTests( IDbConnection db, IEnumerable> tests, - Func> runner) + Func> runner, + string prefix = "") { var result = true; foreach (var t in tests) { - BeginTest(t.Metadata); + BeginTest(t.Metadata, prefix); var res = runner(t.Value, db); result &= res.Item1; if (res.Item1) - TestSucceeded(t.Metadata); + TestSucceeded(t.Metadata, prefix); else - TestFailed(res.Item2, t.Metadata); + TestFailed(res.Item2, t.Metadata, prefix); } return result; @@ -74,9 +81,9 @@ static void Exiting() Console.ReadLine(); } - private static void BeginTest(ISmokeTest metadata) + private static void BeginTest(ISmokeTest metadata, string prefix) { - Console.Write("Running {0} - ", metadata.Name); + Console.Write($"Running {prefix}{metadata.Name} - "); if (isInAppveyor) { @@ -86,7 +93,7 @@ private static void BeginTest(ISmokeTest metadata) } } - private static void TestSucceeded(ISmokeTest metadata) + private static void TestSucceeded(ISmokeTest metadata, string prefix) { if (testWatch != null) testWatch.Stop(); @@ -98,14 +105,14 @@ private static void TestSucceeded(ISmokeTest metadata) if (isInAppveyor) { var message = BuildAppveyorTestMessage( - metadata.Name, + $"{prefix}{metadata.Name}", AppveyorTestStatus.Passed, Tuple.Create("durationMilliseconds", testWatch.ElapsedMilliseconds.ToString())); SendAppveyorTestMessage("PUT", message); } } - private static void TestFailed(string error, ISmokeTest metadata) + private static void TestFailed(string error, ISmokeTest metadata, string prefix) { if (testWatch != null) testWatch.Stop(); @@ -118,7 +125,7 @@ private static void TestFailed(string error, ISmokeTest metadata) if (isInAppveyor) { var message = BuildAppveyorTestMessage( - metadata.Name, + $"{prefix}{metadata.Name}", AppveyorTestStatus.Failed, Tuple.Create("ErrorMessage", error), Tuple.Create("durationMilliseconds", testWatch.ElapsedMilliseconds.ToString())); diff --git a/SmokeTests/app.config b/SmokeTests/app.config index 59c7b53..f2f5510 100644 --- a/SmokeTests/app.config +++ b/SmokeTests/app.config @@ -5,7 +5,7 @@
- + From 38efe798d3c16d2d181789b39de960d54e88895f Mon Sep 17 00:00:00 2001 From: Abraham Heidebrecht Date: Thu, 1 Sep 2016 21:58:32 -0400 Subject: [PATCH 3/3] Added release notes for changes --- CodeOnlyStoredProcedures.nuspec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CodeOnlyStoredProcedures.nuspec b/CodeOnlyStoredProcedures.nuspec index 3b83181..4e7b7f1 100644 --- a/CodeOnlyStoredProcedures.nuspec +++ b/CodeOnlyStoredProcedures.nuspec @@ -10,7 +10,9 @@ A library for easily calling Stored Procedures in .NET. Works great with Entity Framework Code First models. Code Only Stored Procedures will not create any Stored Procedures on your database. Instead, its aim is to make it easy to call your existing stored procedures by writing simple code. 2.3.0 -Fixed bug where hierarchical result sets could not be marked as optional +Can now opt in to not clone the database connection before executing a StoredProcedure. +Can now execute a non-query using the dynamic syntax. +Fixed bug where hierarchical result sets could not be marked as optional. Hierarchies are now much faster to build, especially with large data sets. DateTimeOffset is now fully supported (in the past you had to use attributes or the Fluent syntax to specify the DbType). Hierarchical result sets will now match using case-insensitive names if the case-sensitive ones aren't found.