Skip to content

Commit a1a258d

Browse files
authored
[release][minor] Add transaction support (#251)
* WIP - test with transactions * working transaction generation still needs tests and docs * add test for mysql * tests * add tests * Add docs * fix tests * fix tests * change the way we create query sql with transaction * add rollback test * fix PR comments
1 parent 59e23a2 commit a1a258d

40 files changed

Lines changed: 8457 additions & 2331 deletions

CodeGenerator/Generators/QueriesGen.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,19 @@ public class {{className}}
6464
{
6565
{{dbDriver.GetConstructorStatements().JoinByNewLine()}}
6666
}
67-
private string {{Variable.ConnectionString.AsPropertyName()}} { get; }
67+
68+
private {{className}}({{dbDriver.TransactionClassName}} {{Variable.Transaction.AsVarName()}})
69+
{
70+
{{dbDriver.GetTransactionConstructorStatements().JoinByNewLine()}}
71+
}
72+
73+
public static {{className}} WithTransaction({{dbDriver.TransactionClassName}} {{Variable.Transaction.AsVarName()}})
74+
{
75+
return new {{className}}({{Variable.Transaction.AsVarName()}});
76+
}
77+
78+
private {{dbDriver.AddNullableSuffixIfNeeded(dbDriver.TransactionClassName, false)}} {{Variable.Transaction.AsPropertyName()}} { get; }
79+
private {{dbDriver.AddNullableSuffixIfNeeded("string", false)}} {{Variable.ConnectionString.AsPropertyName()}} { get; }
6880
}
6981
""")!;
7082
return classDeclaration.AddMembers(classMembers.ToArray());

Drivers/DbDriver.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public abstract class DbDriver
1515

1616
public string DefaultSchema { get; }
1717

18+
public abstract string TransactionClassName { get; }
19+
1820
public Dictionary<string, Dictionary<string, Table>> Tables { get; }
1921

2022
public Dictionary<string, Dictionary<string, Plugin.Enum>> Enums { get; }
@@ -118,6 +120,16 @@ public virtual string[] GetConstructorStatements()
118120
.ToArray();
119121
}
120122

123+
public virtual string[] GetTransactionConstructorStatements()
124+
{
125+
return new List<string>
126+
{
127+
$"this.{Variable.Transaction.AsPropertyName()} = {Variable.Transaction.AsVarName()};"
128+
}
129+
.AppendIf("Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;", Options.UseDapper)
130+
.ToArray();
131+
}
132+
121133
public virtual MemberDeclarationSyntax[] GetMemberDeclarationsForUtils()
122134
{
123135
return [];
Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.CodeAnalysis.CSharp.Syntax;
22
using Plugin;
3+
using System.Linq;
34
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
45

56
namespace SqlcGenCsharp.Drivers.Generators;
@@ -21,40 +22,94 @@ public MemberDeclarationSyntax Generate(string queryTextConstant, string argInte
2122

2223
private string GetMethodBody(string queryTextConstant, Query query)
2324
{
24-
var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(query);
2525
var sqlTextTransform = CommonGen.GetSqlTransformations(query, queryTextConstant);
26-
return dbDriver.Options.UseDapper ? GetAsDapper() : GetAsDriver();
27-
28-
string GetAsDapper()
29-
{
30-
var dapperParamsSection = CommonGen.ConstructDapperParamsDict(query.Params);
31-
var dapperArgs = dapperParamsSection != string.Empty
32-
? $", {Variable.QueryParams.AsVarName()}"
33-
: string.Empty;
34-
return $$"""
35-
using ({{establishConnection}})
36-
{{{sqlTextTransform}}{{dapperParamsSection}}
37-
await {{Variable.Connection.AsVarName()}}.ExecuteAsync({{queryTextConstant}}{{dapperArgs}});
26+
var useDapper = dbDriver.Options.UseDapper;
27+
28+
var dapperParams = useDapper ? CommonGen.ConstructDapperParamsDict(query.Params) : string.Empty;
29+
var sqlVar = sqlTextTransform != string.Empty ? Variable.TransformedSql.AsVarName() : queryTextConstant;
30+
var transactionProperty = Variable.Transaction.AsPropertyName();
31+
32+
var noTxBody = useDapper ? GetDapperNoTxBody(sqlVar, query) : GetDriverNoTxBody(sqlVar, query);
33+
var withTxBody = useDapper ? GetDapperWithTxBody(sqlVar, query) : GetDriverWithTxBody(sqlVar, query);
34+
35+
return $$"""
36+
{{sqlTextTransform}}
37+
{{dapperParams}}
38+
if (this.{{transactionProperty}} == null)
39+
{
40+
{{noTxBody}}
41+
}
42+
{{withTxBody}}
43+
""";
44+
}
45+
46+
private string GetDapperNoTxBody(string sqlVar, Query query)
47+
{
48+
var (establishConnection, _) = dbDriver.EstablishConnection(query);
49+
var dapperArgs = query.Params.Any() ? $", {Variable.QueryParams.AsVarName()}" : string.Empty;
50+
return $$"""
51+
using ({{establishConnection}})
52+
{
53+
await {{Variable.Connection.AsVarName()}}.ExecuteAsync({{sqlVar}}{{dapperArgs}});
54+
}
55+
return;
56+
""";
57+
}
58+
59+
private string GetDapperWithTxBody(string sqlVar, Query query)
60+
{
61+
var transactionProperty = Variable.Transaction.AsPropertyName();
62+
var dapperArgs = query.Params.Any() ? $", {Variable.QueryParams.AsVarName()}" : string.Empty;
63+
return $$"""
64+
if (this.{{transactionProperty}}?.Connection == null || this.{{transactionProperty}}?.Connection.State != System.Data.ConnectionState.Open)
65+
{
66+
throw new System.InvalidOperationException("Transaction is provided, but its connection is null.");
67+
}
68+
69+
await this.{{transactionProperty}}.Connection.ExecuteAsync(
70+
{{sqlVar}}{{dapperArgs}},
71+
transaction: this.{{transactionProperty}});
72+
""";
73+
}
74+
75+
private string GetDriverNoTxBody(string sqlVar, Query query)
76+
{
77+
var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(query);
78+
var createSqlCommand = dbDriver.CreateSqlCommand(sqlVar);
79+
var commandParameters = CommonGen.AddParametersToCommand(query.Params);
80+
return $$"""
81+
using ({{establishConnection}})
82+
{
83+
{{connectionOpen.AppendSemicolonUnlessEmpty()}}
84+
using ({{createSqlCommand}})
85+
{
86+
{{commandParameters}}
87+
await {{Variable.Command.AsVarName()}}.ExecuteNonQueryAsync();
3888
}
39-
""";
40-
}
41-
42-
string GetAsDriver()
43-
{
44-
var commandParameters = CommonGen.AddParametersToCommand(query.Params);
45-
var createSqlCommand = dbDriver.CreateSqlCommand(sqlTextTransform != string.Empty ? Variable.TransformedSql.AsVarName() : queryTextConstant);
46-
var executeScalar = $"await {Variable.Command.AsVarName()}.ExecuteScalarAsync();";
47-
return $$"""
48-
using ({{establishConnection}})
49-
{
50-
{{connectionOpen.AppendSemicolonUnlessEmpty()}}{{sqlTextTransform}}
51-
using ({{createSqlCommand}})
52-
{
53-
{{commandParameters}}
54-
{{executeScalar}}
55-
}
56-
}
57-
""";
58-
}
89+
}
90+
return;
91+
""";
92+
}
93+
94+
private string GetDriverWithTxBody(string sqlVar, Query query)
95+
{
96+
var transactionProperty = Variable.Transaction.AsPropertyName();
97+
var commandVar = Variable.Command.AsVarName();
98+
var commandParameters = CommonGen.AddParametersToCommand(query.Params);
99+
100+
return $$"""
101+
if (this.{{transactionProperty}}?.Connection == null || this.{{transactionProperty}}?.Connection.State != System.Data.ConnectionState.Open)
102+
{
103+
throw new System.InvalidOperationException("Transaction is provided, but its connection is null.");
104+
}
105+
106+
using (var {{commandVar}} = this.{{transactionProperty}}.Connection.CreateCommand())
107+
{
108+
{{commandVar}}.CommandText = {{sqlVar}};
109+
{{commandVar}}.Transaction = this.{{transactionProperty}};
110+
{{commandParameters}}
111+
await {{commandVar}}.ExecuteNonQueryAsync();
112+
}
113+
""";
59114
}
60115
}
Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.CodeAnalysis.CSharp.Syntax;
22
using Plugin;
3+
using System.Linq;
34
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
45

56
namespace SqlcGenCsharp.Drivers.Generators;
@@ -21,42 +22,92 @@ public MemberDeclarationSyntax Generate(string queryTextConstant, string argInte
2122

2223
private string GetMethodBody(string queryTextConstant, Query query)
2324
{
24-
var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(query);
2525
var sqlTextTransform = CommonGen.GetSqlTransformations(query, queryTextConstant);
26-
connectionOpen = connectionOpen.AppendSemicolonUnlessEmpty();
27-
return dbDriver.Options.UseDapper ? GetAsDapper() : GetAsDriver();
28-
29-
string GetAsDapper()
30-
{
31-
var dapperParamsSection = CommonGen.ConstructDapperParamsDict(query.Params);
32-
var dapperArgs = dapperParamsSection == string.Empty
33-
? string.Empty
34-
: $", {Variable.QueryParams.AsVarName()}";
35-
return $$"""
36-
using ({{establishConnection}})
37-
{{{sqlTextTransform}}{{dapperParamsSection}}
38-
return await {{Variable.Connection.AsVarName()}}.QuerySingleAsync<{{dbDriver.GetIdColumnType(query)}}>({{queryTextConstant}}{{dapperArgs}});
39-
}
40-
""";
41-
}
42-
43-
string GetAsDriver()
44-
{
45-
var sqlTextVar = sqlTextTransform == string.Empty ? queryTextConstant : Variable.TransformedSql.AsVarName();
46-
var createSqlCommand = dbDriver.CreateSqlCommand(sqlTextVar);
47-
var commandParameters = CommonGen.AddParametersToCommand(query.Params);
48-
var returnLastId = ((IExecLastId)dbDriver).GetLastIdStatement(query).JoinByNewLine();
49-
return $$"""
50-
using ({{establishConnection}})
51-
{
52-
{{connectionOpen}}{{sqlTextTransform}}
53-
using ({{createSqlCommand}})
54-
{
26+
var useDapper = dbDriver.Options.UseDapper;
27+
28+
var dapperParams = useDapper ? CommonGen.ConstructDapperParamsDict(query.Params) : string.Empty;
29+
var sqlVar = sqlTextTransform != string.Empty ? Variable.TransformedSql.AsVarName() : queryTextConstant;
30+
var transactionProperty = Variable.Transaction.AsPropertyName();
31+
32+
var noTxBody = useDapper ? GetDapperNoTxBody(sqlVar, query) : GetDriverNoTxBody(sqlVar, query);
33+
var withTxBody = useDapper ? GetDapperWithTxBody(sqlVar, query) : GetDriverWithTxBody(sqlVar, query);
34+
35+
return $$"""
36+
{{sqlTextTransform}}
37+
{{dapperParams}}
38+
if (this.{{transactionProperty}} == null)
39+
{
40+
{{noTxBody}}
41+
}
42+
{{withTxBody}}
43+
""";
44+
}
45+
46+
private string GetDapperNoTxBody(string sqlVar, Query query)
47+
{
48+
var (establishConnection, _) = dbDriver.EstablishConnection(query);
49+
var dapperArgs = query.Params.Any() ? $", {Variable.QueryParams.AsVarName()}" : string.Empty;
50+
return $$"""
51+
using ({{establishConnection}})
52+
{
53+
return await {{Variable.Connection.AsVarName()}}.QuerySingleAsync<{{dbDriver.GetIdColumnType(query)}}>({{sqlVar}}{{dapperArgs}});
54+
}
55+
""";
56+
}
57+
58+
private string GetDapperWithTxBody(string sqlVar, Query query)
59+
{
60+
var transactionProperty = Variable.Transaction.AsPropertyName();
61+
var dapperArgs = query.Params.Any() ? $", {Variable.QueryParams.AsVarName()}" : string.Empty;
62+
return $$"""
63+
if (this.{{transactionProperty}}?.Connection == null || this.{{transactionProperty}}?.Connection.State != System.Data.ConnectionState.Open)
64+
{
65+
throw new System.InvalidOperationException("Transaction is provided, but its connection is null.");
66+
}
67+
68+
return await this.{{transactionProperty}}.Connection.QuerySingleAsync<{{dbDriver.GetIdColumnType(query)}}>({{sqlVar}}{{dapperArgs}}, transaction: this.{{transactionProperty}});
69+
""";
70+
}
71+
72+
private string GetDriverNoTxBody(string sqlVar, Query query)
73+
{
74+
var (establishConnection, connectionOpen) = dbDriver.EstablishConnection(query);
75+
var createSqlCommand = dbDriver.CreateSqlCommand(sqlVar);
76+
var commandParameters = CommonGen.AddParametersToCommand(query.Params);
77+
var returnLastId = ((IExecLastId)dbDriver).GetLastIdStatement(query).JoinByNewLine();
78+
return $$"""
79+
using ({{establishConnection}})
80+
{
81+
{{connectionOpen.AppendSemicolonUnlessEmpty()}}
82+
using ({{createSqlCommand}})
83+
{
5584
{{commandParameters}}
5685
{{returnLastId}}
57-
}
58-
}
59-
""";
60-
}
86+
}
87+
}
88+
""";
89+
}
90+
91+
private string GetDriverWithTxBody(string sqlVar, Query query)
92+
{
93+
var transactionProperty = Variable.Transaction.AsPropertyName();
94+
var commandVar = Variable.Command.AsVarName();
95+
var commandParameters = CommonGen.AddParametersToCommand(query.Params);
96+
var returnLastId = ((IExecLastId)dbDriver).GetLastIdStatement(query).JoinByNewLine();
97+
98+
return $$"""
99+
if (this.{{transactionProperty}}?.Connection == null || this.{{transactionProperty}}?.Connection.State != System.Data.ConnectionState.Open)
100+
{
101+
throw new System.InvalidOperationException("Transaction is provided, but its connection is null.");
102+
}
103+
104+
using (var {{commandVar}} = this.{{transactionProperty}}.Connection.CreateCommand())
105+
{
106+
{{commandVar}}.CommandText = {{sqlVar}};
107+
{{commandVar}}.Transaction = this.{{transactionProperty}};
108+
{{commandParameters}}
109+
{{returnLastId}}
110+
}
111+
""";
61112
}
62113
}

0 commit comments

Comments
 (0)