Skip to content

Commit 0ecf35b

Browse files
authored
Merge pull request #255 from Plasma/fix-issue-251
Fix transient Collection was Modified exception (Issue #251)
2 parents 71f0d0b + 9fbdb5d commit 0ecf35b

File tree

4 files changed

+51
-30
lines changed

4 files changed

+51
-30
lines changed

src/Hangfire.PostgreSql/IConnectionFactory.cs

+10-9
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@
2121

2222
using Npgsql;
2323

24-
namespace Hangfire.PostgreSql;
25-
26-
/// <summary>
27-
/// Connection factory for runtime create connection
28-
/// </summary>
29-
public interface IConnectionFactory
24+
namespace Hangfire.PostgreSql
3025
{
3126
/// <summary>
32-
/// Get or create NpgsqlConnection
27+
/// Connection factory for runtime create connection
3328
/// </summary>
34-
NpgsqlConnection GetOrCreateConnection();
35-
}
29+
public interface IConnectionFactory
30+
{
31+
/// <summary>
32+
/// Get or create NpgsqlConnection
33+
/// </summary>
34+
NpgsqlConnection GetOrCreateConnection();
35+
}
36+
}

src/Hangfire.PostgreSql/PostgreSqlStorage.cs

+15-10
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public PostgreSqlStorage(
8787
throw new ArgumentException($"Connection string [{connectionString}] is not valid", nameof(connectionString));
8888
}
8989

90-
_connectionStringBuilder = builder;
90+
_connectionStringBuilder = SetupConnectionStringBuilderParameters(builder);
9191
_connectionSetup = connectionSetup;
9292

9393
if (options.PrepareSchemaIfNecessary)
@@ -238,15 +238,6 @@ internal NpgsqlConnection CreateAndOpenConnection()
238238
connection = _existingConnection;
239239
if (connection == null)
240240
{
241-
// The connection string must not be modified when transaction enlistment is enabled, otherwise it will cause
242-
// prepared transactions and probably fail when other statements (outside of hangfire) ran within the same
243-
// transaction. Also see #248.
244-
if (!Options.EnableTransactionScopeEnlistment)
245-
{
246-
_connectionStringBuilder.Enlist = false;
247-
SetTimezoneToUtcForNpgsqlCompatibility(_connectionStringBuilder);
248-
}
249-
250241
connection = new NpgsqlConnection(_connectionStringBuilder.ToString());
251242
_connectionSetup?.Invoke(connection);
252243
}
@@ -441,5 +432,19 @@ private bool TryCreateConnectionStringBuilder(string connectionString, out Npgsq
441432
return false;
442433
}
443434
}
435+
436+
private NpgsqlConnectionStringBuilder SetupConnectionStringBuilderParameters(NpgsqlConnectionStringBuilder builder)
437+
{
438+
// The connection string must not be modified when transaction enlistment is enabled, otherwise it will cause
439+
// prepared transactions and probably fail when other statements (outside of hangfire) ran within the same
440+
// transaction. Also see #248.
441+
if (!Options.EnableTransactionScopeEnlistment)
442+
{
443+
builder.Enlist = false;
444+
SetTimezoneToUtcForNpgsqlCompatibility(builder);
445+
}
446+
447+
return builder;
448+
}
444449
}
445450
}

tests/Hangfire.PostgreSql.Tests/PostgreSqlStorageFacts.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,27 @@ public PostgreSqlStorageFacts()
1818
_options = new PostgreSqlStorageOptions { PrepareSchemaIfNecessary = false, EnableTransactionScopeEnlistment = true };
1919
}
2020

21+
/// <summary>
22+
/// Ideally we could enforce UTC time-zone for all connections to Hangfire Postgres (to ensure proper DateTime handling) however if the connection is being enlisted
23+
/// then we cannot touch the ConnectionString in any way because otherwise this creates a new prepared transaction, which becomes aborted
24+
///
25+
/// The best we can do at the moment is enforce UTC on connections initialized (and not enlisted) by Hangfire Postgres
26+
/// </summary>
2127
[Fact]
22-
public void Connection_Timezone_Is_Set_To_UTC_For_Npgsql6_Compatibility()
28+
public void Connection_Timezone_Is_Set_To_UTC_For_Npgsql6_Compatibility_When_Enlistment_Not_Set()
2329
{
24-
PostgreSqlStorage storage = CreateStorage();
30+
// Arrange
31+
32+
// Turn off transaction enlistment for this test
33+
// We won't be performing any queries so there is no side-effect for having this disabled
34+
_options.EnableTransactionScopeEnlistment = false;
2535

36+
PostgreSqlStorage storage = CreateStorage();
37+
38+
// Act
2639
using (var connection = storage.CreateAndOpenConnection())
2740
{
41+
// Assert
2842
Assert.Equal("UTC", connection.Timezone);
2943
}
3044
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using Npgsql;
22

3-
namespace Hangfire.PostgreSql.Tests.Utils;
4-
5-
public class DefaultConnectionFactory: IConnectionFactory
3+
namespace Hangfire.PostgreSql.Tests.Utils
64
{
7-
/// <summary>
8-
/// Get or create NpgsqlConnection
9-
/// </summary>
10-
public NpgsqlConnection GetOrCreateConnection()
5+
public class DefaultConnectionFactory : IConnectionFactory
116
{
12-
return ConnectionUtils.CreateConnection();
7+
/// <summary>
8+
/// Get or create NpgsqlConnection
9+
/// </summary>
10+
public NpgsqlConnection GetOrCreateConnection()
11+
{
12+
return ConnectionUtils.CreateConnection();
13+
}
1314
}
14-
}
15+
}

0 commit comments

Comments
 (0)