Skip to content

Commit 15f086f

Browse files
author
electricessence
committed
5.9.0
Implemented correct transaction flow. Added extensions for creating commands from transactions. Expanded expressive commands to allow for transactions. Added examples.
1 parent 02341c3 commit 15f086f

27 files changed

+8560
-1522
lines changed

Examples/Examples.csproj

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\Open.Database.Extensions.csproj" />
9+
</ItemGroup>
10+
11+
</Project>

Examples/Transaction.cs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Open.Database.Extensions;
2+
using System.Data.SqlClient;
3+
4+
public static partial class Examples
5+
{
6+
public readonly static DbConnectionFactory ConnectionFactory
7+
= new DbConnectionFactory(()=>new SqlConnection());
8+
9+
// Returns true if the transaction is successful.
10+
public static bool TryTransaction()
11+
=> ConnectionFactory.Using(connection =>
12+
// Open a connection and start a transaction.
13+
connection.ExecuteTransactionConditional(transaction => {
14+
15+
// First procedure does some updates.
16+
var count = transaction
17+
.StoredProcedure("[Updated Procedure]")
18+
.ExecuteNonQuery();
19+
20+
// Second procedure validates the results.
21+
// If it returns true, then the transaction is commited.
22+
// If it returns false, then the transaction is rolled back.
23+
return transaction
24+
.StoredProcedure("[Validation Procedure]")
25+
.AddParam("@ExpectedCount", count)
26+
.ExecuteScalar<bool>();
27+
}));
28+
29+
}
30+

ExpressiveCommandBase.cs

+45-34
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,19 @@ public abstract partial class ExpressiveCommandBase<TConnection, TCommand, TDbTy
3131
/// </summary>
3232
protected readonly TConnection Connection;
3333

34+
/// <summary>
35+
/// The transaction to execute commands on if not using a connection factory.
36+
/// </summary>
37+
protected readonly IDbTransaction Transaction;
38+
3439
ExpressiveCommandBase(
3540
CommandType type,
3641
string command,
37-
List<Param> @params = null)
42+
IEnumerable<Param> @params)
3843
{
3944
Type = type;
4045
Command = command ?? throw new ArgumentNullException(nameof(command));
41-
Params = @params ?? throw new ArgumentNullException(nameof(@params));
46+
Params = @params?.ToList() ?? new List<Param>();
4247
Timeout = CommandTimeout.DEFAULT_SECONDS;
4348
}
4449

@@ -50,25 +55,28 @@ protected ExpressiveCommandBase(
5055
IDbConnectionFactory<TConnection> connFactory,
5156
CommandType type,
5257
string command,
53-
List<Param> @params)
54-
: this(type, command, @params ?? new List<Param>())
58+
IEnumerable<Param> @params)
59+
: this(type, command, @params)
5560
{
5661
ConnectionFactory = connFactory ?? throw new ArgumentNullException(nameof(connFactory));
5762
}
5863

59-
/// <param name="connection">The connection to execute the command on.</param>
60-
/// <param name="type">The command type>.</param>
61-
/// <param name="command">The SQL command.</param>
62-
/// <param name="params">The list of params</param>
63-
protected ExpressiveCommandBase(
64+
/// <param name="connection">The connection to execute the command on.</param>
65+
/// <param name="transaction">The optional transaction to execute the command on.</param>
66+
/// <param name="type">The command type>.</param>
67+
/// <param name="command">The SQL command.</param>
68+
/// <param name="params">The list of params</param>
69+
protected ExpressiveCommandBase(
6470
TConnection connection,
71+
IDbTransaction transaction,
6572
CommandType type,
6673
string command,
67-
List<Param> @params)
68-
: this(type, command, @params ?? new List<Param>())
74+
IEnumerable<Param> @params)
75+
: this(type, command, @params)
6976
{
7077
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
71-
}
78+
Transaction = transaction;
79+
}
7280

7381
/// <summary>
7482
/// The command text or procedure name to use.
@@ -252,36 +260,36 @@ public TThis SetTimeout(ushort seconds)
252260
/// Handles providing the connection for use with the command.
253261
/// </summary>
254262
/// <param name="action">The handler for use with the connection.</param>
255-
protected void UsingConnection(Action<TConnection> action)
263+
protected void UsingConnection(Action<TConnection, IDbTransaction> action)
256264
{
257-
if (ConnectionFactory != null)
258-
{
259-
using (var conn = ConnectionFactory.Create())
260-
{
261-
action(conn);
262-
}
263-
}
264-
else
265-
{
266-
action(Connection);
267-
}
268-
}
265+
if (Connection != null)
266+
{
267+
action(Connection, Transaction);
268+
}
269+
else
270+
{
271+
using (var conn = ConnectionFactory.Create())
272+
{
273+
action(conn, null);
274+
}
275+
}
276+
}
269277

270278
/// <summary>
271279
/// Handles providing the connection for use with the command.
272280
/// </summary>
273281
/// <param name="action">The handler for use with the connection.</param>
274-
protected T UsingConnection<T>(Func<TConnection, T> action)
282+
protected T UsingConnection<T>(Func<TConnection, IDbTransaction, T> action)
275283
{
276284
if (Connection != null)
277285
{
278-
return action(Connection);
286+
return action(Connection, Transaction);
279287
}
280288
else
281289
{
282290
using (var conn = ConnectionFactory.Create())
283291
{
284-
return action(conn);
292+
return action(conn, null);
285293
}
286294
}
287295
}
@@ -291,13 +299,14 @@ protected T UsingConnection<T>(Func<TConnection, T> action)
291299
/// </summary>
292300
/// <param name="handler">The handler function for each IDataRecord.</param>
293301
public void Execute(Action<TCommand> handler)
294-
=> UsingConnection(con =>
302+
=> UsingConnection((con,t) =>
295303
{
296304
using (var cmd = con.CreateCommand(Type, Command, Timeout))
297305
{
298306
var c = cmd as TCommand;
299307
if (c == null) throw new InvalidCastException($"Actual command type ({cmd.GetType()}) is not compatible with expected command type ({typeof(TCommand)}).");
300-
AddParams(c);
308+
if (t != null) c.Transaction = t;
309+
AddParams(c);
301310
con.EnsureOpen();
302311
handler(c);
303312
}
@@ -311,13 +320,14 @@ public void Execute(Action<TCommand> handler)
311320
/// <param name="transform">The transform function for each IDataRecord.</param>
312321
/// <returns>The result of the transform.</returns>
313322
public T Execute<T>(Func<TCommand, T> transform)
314-
=> UsingConnection(con =>
323+
=> UsingConnection((con,t) =>
315324
{
316325
using (var cmd = con.CreateCommand(Type, Command, Timeout))
317326
{
318327
var c = cmd as TCommand;
319328
if (c == null) throw new InvalidCastException($"Actual command type ({cmd.GetType()}) is not compatible with expected command type ({typeof(TCommand)}).");
320-
AddParams(c);
329+
if (t != null) c.Transaction = t;
330+
AddParams(c);
321331
con.EnsureOpen();
322332
return transform(c);
323333
}
@@ -329,13 +339,14 @@ public T Execute<T>(Func<TCommand, T> transform)
329339
/// </summary>
330340
/// <returns>The value from the return parameter.</returns>
331341
public object ExecuteReturn()
332-
=> UsingConnection(con =>
342+
=> UsingConnection((con,t) =>
333343
{
334344
using (var cmd = con.CreateCommand(Type, Command, Timeout))
335345
{
336346
var c = cmd as TCommand;
337347
if (c == null) throw new InvalidCastException($"Actual command type ({cmd.GetType()}) is not compatible with expected command type ({typeof(TCommand)}).");
338-
AddParams(c);
348+
if (t != null) c.Transaction = t;
349+
AddParams(c);
339350
var returnParameter = c.CreateParameter();
340351
returnParameter.Direction = ParameterDirection.ReturnValue;
341352
c.Parameters.Add(returnParameter);

ExpressiveDbCommand.cs

+22-31
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,40 @@
11
using System.Collections.Generic;
22
using System.Data;
33
using System.Data.Common;
4-
using System.Linq;
54

65
namespace Open.Database.Extensions
76
{
87

9-
/// <summary>
10-
/// An abstraction for executing commands on a database using best practices and simplified expressive syntax.
11-
/// </summary>
12-
public class ExpressiveDbCommand : ExpressiveDbCommandBase<DbConnection, DbCommand, DbType, ExpressiveDbCommand>
8+
/// <summary>
9+
/// An abstraction for executing commands on a database using best practices and simplified expressive syntax.
10+
/// </summary>
11+
public class ExpressiveDbCommand : ExpressiveDbCommandBase<DbConnection, DbCommand, DbType, ExpressiveDbCommand>
1312
{
1413
/// <param name="connFactory">The factory to generate connections from.</param>
1514
/// <param name="type">The command type>.</param>
1615
/// <param name="command">The SQL command.</param>
1716
/// <param name="params">The list of params</param>
18-
public ExpressiveDbCommand(IDbConnectionFactory<DbConnection> connFactory, CommandType type, string command, List<Param> @params)
19-
: base(connFactory, type, command, @params?.ToList())
17+
public ExpressiveDbCommand(
18+
IDbConnectionFactory<DbConnection> connFactory,
19+
CommandType type,
20+
string command,
21+
IEnumerable<Param> @params = null)
22+
: base(connFactory, type, command, @params)
2023
{
2124
}
2225

23-
/// <param name="connFactory">The factory to generate connections from.</param>
24-
/// <param name="type">The command type>.</param>
25-
/// <param name="command">The SQL command.</param>
26-
/// <param name="params">The list of params</param>
27-
public ExpressiveDbCommand(IDbConnectionFactory<DbConnection> connFactory, CommandType type, string command, params Param[] @params)
28-
: base(connFactory, type, command, @params?.ToList())
29-
{
30-
}
31-
32-
/// <param name="connection">The connection to execute the command on.</param>
33-
/// <param name="type">The command type>.</param>
34-
/// <param name="command">The SQL command.</param>
35-
/// <param name="params">The list of params</param>
36-
public ExpressiveDbCommand(DbConnection connection, CommandType type, string command, List<Param> @params)
37-
: base(connection, type, command, @params?.ToList())
38-
{
39-
}
40-
41-
/// <param name="connection">The connection to execute the command on.</param>
42-
/// <param name="type">The command type>.</param>
43-
/// <param name="command">The SQL command.</param>
44-
/// <param name="params">The list of params</param>
45-
public ExpressiveDbCommand(DbConnection connection, CommandType type, string command, params Param[] @params)
46-
: this(connection, type, command, @params?.ToList())
26+
/// <param name="connection">The connection to execute the command on.</param>
27+
/// <param name="transaction">The optional transaction to execute the command on.</param>
28+
/// <param name="type">The command type>.</param>
29+
/// <param name="command">The SQL command.</param>
30+
/// <param name="params">The list of params</param>
31+
public ExpressiveDbCommand(
32+
DbConnection connection,
33+
DbTransaction transaction,
34+
CommandType type,
35+
string command,
36+
IEnumerable<Param> @params = null)
37+
: base(connection, transaction, type, command, @params)
4738
{
4839
}
4940

ExpressiveDbCommandBase.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,23 @@ protected ExpressiveDbCommandBase(
3232
IDbConnectionFactory<TConnection> connFactory,
3333
CommandType type,
3434
string command,
35-
List<Param> @params)
35+
IEnumerable<Param> @params)
3636
: base(connFactory, type, command, @params)
3737
{
3838
}
3939

4040
/// <param name="connection">The connection to execute the command on.</param>
41+
/// <param name="transaction">The optional transaction to execute the command on.</param>
4142
/// <param name="type">The command type>.</param>
4243
/// <param name="command">The SQL command.</param>
4344
/// <param name="params">The list of params</param>
4445
protected ExpressiveDbCommandBase(
4546
TConnection connection,
47+
IDbTransaction transaction,
4648
CommandType type,
4749
string command,
48-
List<Param> @params)
49-
: base(connection, type, command, @params)
50+
IEnumerable<Param> @params)
51+
: base(connection, transaction, type, command, @params)
5052
{
5153
}
5254

@@ -223,6 +225,7 @@ public Task IterateReaderAsync(Action<IDataRecord> handler, CancellationToken? t
223225
/// Iterates asynchronously until the handler returns false. Then cancels.
224226
/// </summary>
225227
/// <param name="predicate">If true, the iteration continues.</param>
228+
/// <param name="token">An optional cancellation token.</param>
226229
/// <returns>The task that completes when the iteration is done or the predicate evaluates false.</returns>
227230
public Task IterateReaderAsyncWhile(Func<IDataRecord, bool> predicate, CancellationToken? token = null)
228231
=> ExecuteAsync(command => command.IterateReaderAsyncWhile(predicate), token);

Extensions.ConnectionFactory.cs

+26-2
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,20 @@ public static ExpressiveDbCommand Command(
155155
this DbConnection target,
156156
string command,
157157
CommandType type = CommandType.Text)
158-
=> new ExpressiveDbCommand(target, type, command);
158+
=> new ExpressiveDbCommand(target, null, type, command);
159+
160+
/// <summary>
161+
/// Creates an ExpressiveDbCommand for subsequent configuration and execution.
162+
/// </summary>
163+
/// <param name="target">The transaction to execute the command on.</param>
164+
/// <param name="command">The command text or stored procedure name to use.</param>
165+
/// <param name="type">The command type.</param>
166+
/// <returns>The resultant ExpressiveDbCommand.</returns>
167+
public static ExpressiveDbCommand Command(
168+
this DbTransaction target,
169+
string command,
170+
CommandType type = CommandType.Text)
171+
=> new ExpressiveDbCommand(target.Connection, target, type, command);
159172

160173
/// <summary>
161174
/// Creates an ExpressiveDbCommand with command type set to StoredProcedure for subsequent configuration and execution.
@@ -166,7 +179,18 @@ public static ExpressiveDbCommand Command(
166179
public static ExpressiveDbCommand StoredProcedure(
167180
this DbConnection target,
168181
string command)
169-
=> new ExpressiveDbCommand(target, CommandType.StoredProcedure, command);
182+
=> new ExpressiveDbCommand(target, null, CommandType.StoredProcedure, command);
183+
184+
/// <summary>
185+
/// Creates an ExpressiveDbCommand with command type set to StoredProcedure for subsequent configuration and execution.
186+
/// </summary>
187+
/// <param name="target">The transaction to execute the command on.</param>
188+
/// <param name="command">The command text or stored procedure name to use.</param>
189+
/// <returns>The resultant ExpressiveDbCommand.</returns>
190+
public static ExpressiveDbCommand StoredProcedure(
191+
this DbTransaction target,
192+
string command)
193+
=> new ExpressiveDbCommand(target.Connection, target, CommandType.StoredProcedure, command);
170194

171195
/// <summary>
172196
/// Creates an ExpressiveDbCommand for subsequent configuration and execution.

0 commit comments

Comments
 (0)