Skip to content

Commit 6f8c0be

Browse files
authored
feat: Hardens DB command helpers by flowing ambient EF transactions and setup EF command timeouts (#2977)
1 parent e5d53b6 commit 6f8c0be

File tree

3 files changed

+92
-39
lines changed

3 files changed

+92
-39
lines changed

src/VirtoCommerce.Platform.Data/Extensions/DatabaseFacadeExtensions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ public static class DatabaseFacadeExtensions
1010
public static void MigrateIfNotApplied(this DatabaseFacade databaseFacade, string targetMigration)
1111
{
1212
var connectionTimeout = databaseFacade.GetDbConnection().ConnectionTimeout;
13-
databaseFacade.SetCommandTimeout(connectionTimeout);
13+
var commandTimeout = databaseFacade.GetCommandTimeout();
14+
if (commandTimeout is null)
15+
{
16+
databaseFacade.SetCommandTimeout(connectionTimeout);
17+
}
1418

1519
var platformMigrator = databaseFacade.GetService<IMigrator>();
1620
var appliedMigrations = databaseFacade.GetAppliedMigrations();

src/VirtoCommerce.Platform.Data/Extensions/DbContextCommandExtensions.cs

Lines changed: 86 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Data;
33
using System.Threading.Tasks;
44
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Storage;
56

67
namespace VirtoCommerce.Platform.Data.Extensions
78
{
@@ -10,74 +11,122 @@ public static class DbContextCommandExtensions
1011
public static async Task<int> ExecuteNonQueryAsync(this DbContext context, string rawSql, params object[] parameters)
1112
{
1213
var conn = context.Database.GetDbConnection();
13-
using (var command = conn.CreateCommand())
14+
await using var command = conn.CreateCommand();
15+
16+
command.CommandText = rawSql;
17+
if (parameters != null)
1418
{
15-
command.CommandText = rawSql;
16-
if (parameters != null)
19+
foreach (var p in parameters)
1720
{
18-
foreach (var p in parameters)
19-
{
20-
command.Parameters.Add(p);
21-
}
21+
command.Parameters.Add(p);
2222
}
23-
if (conn.State != ConnectionState.Open)
23+
}
24+
25+
if (context.Database.CurrentTransaction != null)
26+
{
27+
command.Transaction = context.Database.CurrentTransaction.GetDbTransaction();
28+
}
29+
30+
var wasOpen = conn.State == ConnectionState.Open;
31+
if (!wasOpen)
32+
{
33+
await conn.OpenAsync();
34+
}
35+
36+
try
37+
{
38+
return await command.ExecuteNonQueryAsync();
39+
}
40+
finally
41+
{
42+
if (!wasOpen)
2443
{
25-
await conn.OpenAsync();
44+
await conn.CloseAsync();
2645
}
27-
return await command.ExecuteNonQueryAsync();
2846
}
2947
}
3048

3149
public static async Task<T> ExecuteScalarAsync<T>(this DbContext context, string rawSql, params object[] parameters)
3250
{
3351
var conn = context.Database.GetDbConnection();
34-
using (var command = conn.CreateCommand())
52+
await using var command = conn.CreateCommand();
53+
54+
command.CommandText = rawSql;
55+
if (parameters != null)
3556
{
36-
command.CommandText = rawSql;
37-
if (parameters != null)
57+
foreach (var p in parameters)
3858
{
39-
foreach (var p in parameters)
40-
{
41-
command.Parameters.Add(p);
42-
}
59+
command.Parameters.Add(p);
4360
}
44-
if (conn.State != ConnectionState.Open)
61+
}
62+
63+
if (context.Database.CurrentTransaction != null)
64+
{
65+
command.Transaction = context.Database.CurrentTransaction.GetDbTransaction();
66+
}
67+
68+
var wasOpen = conn.State == ConnectionState.Open;
69+
if (!wasOpen)
70+
{
71+
await conn.OpenAsync();
72+
}
73+
74+
try
75+
{
76+
return (T)await command.ExecuteScalarAsync();
77+
}
78+
finally
79+
{
80+
if (!wasOpen)
4581
{
46-
await conn.OpenAsync();
82+
await conn.CloseAsync();
4783
}
48-
return (T)await command.ExecuteScalarAsync();
4984
}
5085
}
5186

5287
public static async Task<T[]> ExecuteArrayAsync<T>(this DbContext context, string rawSql, params object[] parameters)
5388
{
5489
var conn = context.Database.GetDbConnection();
55-
using (var command = conn.CreateCommand())
90+
await using var command = conn.CreateCommand();
91+
92+
command.CommandText = rawSql;
93+
if (parameters != null)
5694
{
57-
command.CommandText = rawSql;
58-
if (parameters != null)
95+
foreach (var p in parameters)
5996
{
60-
foreach (var p in parameters)
61-
{
62-
command.Parameters.Add(p);
63-
}
97+
command.Parameters.Add(p);
6498
}
99+
}
65100

66-
if (conn.State != ConnectionState.Open)
67-
{
68-
await conn.OpenAsync();
69-
}
101+
if (context.Database.CurrentTransaction != null)
102+
{
103+
command.Transaction = context.Database.CurrentTransaction.GetDbTransaction();
104+
}
70105

106+
var wasOpen = conn.State == ConnectionState.Open;
107+
if (!wasOpen)
108+
{
109+
await conn.OpenAsync();
110+
}
111+
112+
try
113+
{
71114
var result = new List<T>();
72-
using (var reader = await command.ExecuteReaderAsync())
115+
await using var reader = await command.ExecuteReaderAsync();
116+
while (await reader.ReadAsync())
73117
{
74-
while (await reader.ReadAsync())
75-
{
76-
result.Add(await reader.GetFieldValueAsync<T>(0));
77-
}
118+
result.Add(await reader.GetFieldValueAsync<T>(0));
78119
}
79120

80-
return result.ToArray();
121+
return [.. result];
122+
123+
}
124+
finally
125+
{
126+
if (!wasOpen)
127+
{
128+
await conn.CloseAsync();
129+
}
81130
}
82131
}
83132
}

src/VirtoCommerce.Platform.Hangfire/Extensions/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static IGlobalConfiguration AddHangfireStorage(this IGlobalConfiguration
3737
return globalConfiguration;
3838
}
3939

40-
public static object AddHangfire(this IServiceCollection services, IConfiguration configuration)
40+
public static IServiceCollection AddHangfire(this IServiceCollection services, IConfiguration configuration)
4141
{
4242
services.AddSingleton<RecurringJobService>();
4343
services.AddSingleton<IRecurringJobService, RecurringJobService>();

0 commit comments

Comments
 (0)