Skip to content

Commit 91a780b

Browse files
thorajclaude
andcommitted
fix: Use reference equality for transaction sharing detection
PROBLEM: -------- The original CanShareTransaction() implementation used CONNECTION STRING comparison to determine if contexts could share transactions. This was fundamentally incorrect because EF Core's UseTransaction() requires the SAME DbConnection OBJECT INSTANCE (reference equality), not just matching connection strings. Each call to .UseSqlServer(connectionString) creates a NEW DbConnection object, so: - Two contexts with identical connection strings had DIFFERENT connection objects - String comparison returned TRUE (same strings) - UseTransaction() would fail or behave unpredictably (different objects) WHY THIS MATTERS: ----------------- EF Core's UseTransaction(DbTransaction) method requires that the DbTransaction be associated with the SAME physical DbConnection that all contexts use. Reference equality is required - the database cannot coordinate transactions across separate connection objects even if they connect to the same database. SOLUTION: --------- 1. Changed CanShareTransaction() from string comparison to reference equality check: OLD: return connection?.ConnectionString == firstConnectionString; NEW: return ReferenceEquals(connection, firstConnection); 2. Updated ALL documentation to show correct pattern: - Users MUST create ONE DbConnection object - Pass SAME connection instance to all contexts - Connection lifetime is user's responsibility 3. Added XML documentation explaining reference equality requirement 4. Updated context factory pattern to store and reuse connection object 5. Added connection lifetime management guidance IMPACT: ------- This is a design correction made BEFORE PR casbin-net#85 merges to upstream, so there is NO breaking change to released code. The PR documentation and implementation are being corrected to reflect EF Core's actual requirements. Users following the corrected pattern will get reliable atomic transactions. Users who need separate databases will get predictable non-atomic behavior. FILES CHANGED: -------------- - EFCoreAdapter.cs: CanShareTransaction() now uses ReferenceEquals() - MULTI_CONTEXT_USAGE_GUIDE.md: All examples show shared connection object pattern - MULTI_CONTEXT_DESIGN.md: Detection logic updated to explain reference equality 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent b1201e2 commit 91a780b

File tree

3 files changed

+141
-92
lines changed

3 files changed

+141
-92
lines changed

Casbin.Persist.Adapter.EFCore/EFCoreAdapter.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,27 +258,36 @@ private void SavePolicyWithIndividualTransactions(IPolicyStore store, List<DbCon
258258
}
259259
}
260260

261+
/// <summary>
262+
/// Determines if contexts can share a transaction by checking if they use the same physical DbConnection instance.
263+
/// </summary>
264+
/// <remarks>
265+
/// EF Core's UseTransaction() requires that all contexts use the SAME DbConnection object instance
266+
/// (reference equality), not just identical connection strings. Users must explicitly create contexts
267+
/// with a shared DbConnection object for transaction coordination to work.
268+
/// </remarks>
269+
/// <param name="contexts">List of contexts to check for shared connection</param>
270+
/// <returns>True if all contexts share the same DbConnection instance; otherwise false</returns>
261271
private bool CanShareTransaction(List<DbContext> contexts)
262272
{
263-
// Check if all contexts share the same connection string
264-
// For SQLite, separate database files cannot share transactions
273+
// Check if all contexts share the same physical DbConnection object
274+
// EF Core's UseTransaction() requires reference equality, not string equality
265275
if (contexts.Count <= 1) return true;
266276

267277
try
268278
{
269279
var firstConnection = contexts[0].Database.GetDbConnection();
270-
var firstConnectionString = firstConnection?.ConnectionString;
271280

272-
if (string.IsNullOrEmpty(firstConnectionString))
281+
if (firstConnection == null)
273282
{
274-
// If we can't determine connection strings, assume separate connections
275283
return false;
276284
}
277285

286+
// Check reference equality - contexts must share the SAME connection object
278287
return contexts.All(c =>
279288
{
280289
var connection = c.Database.GetDbConnection();
281-
return connection?.ConnectionString == firstConnectionString;
290+
return ReferenceEquals(connection, firstConnection);
282291
});
283292
}
284293
catch (Exception)

MULTI_CONTEXT_DESIGN.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,15 @@ private bool CanShareTransaction(List<DbContext> contexts)
134134
try
135135
{
136136
var firstConnection = contexts[0].Database.GetDbConnection();
137-
var firstConnectionString = firstConnection?.ConnectionString;
138137

139-
if (string.IsNullOrEmpty(firstConnectionString))
138+
if (firstConnection == null)
140139
return false;
141140

141+
// Check reference equality - contexts must share the SAME connection object
142142
return contexts.All(c =>
143143
{
144144
var connection = c.Database.GetDbConnection();
145-
return connection?.ConnectionString == firstConnectionString;
145+
return ReferenceEquals(connection, firstConnection);
146146
});
147147
}
148148
catch (Exception)
@@ -155,14 +155,15 @@ private bool CanShareTransaction(List<DbContext> contexts)
155155
```
156156

157157
**Detection Strategy:**
158-
- Compare connection strings across all contexts
159-
- If all match → use shared transaction (atomic)
160-
- If any differ → use individual transactions (not atomic)
158+
- Check if all contexts share the **same DbConnection object** (reference equality)
159+
- EF Core's `UseTransaction()` requires the same physical connection instance
160+
- If all contexts use same DbConnection → use shared transaction (atomic)
161+
- If any context uses different DbConnection → use individual transactions (not atomic)
161162
- No errors thrown → graceful degradation
162163

163164
#### Shared Transaction Pattern
164165

165-
When connection strings match:
166+
When contexts share the same DbConnection object:
166167

167168
```csharp
168169
// Pseudocode
@@ -183,7 +184,7 @@ transaction.Commit(); // Atomic across all contexts
183184

184185
#### Individual Transaction Pattern
185186

186-
When connection strings differ (e.g., separate SQLite files):
187+
When contexts use different DbConnection objects:
187188

188189
```csharp
189190
// Pseudocode - WARNING: Not atomic across contexts
@@ -214,8 +215,8 @@ foreach (var context in contexts)
214215
| **SQLite** || N/A | ✅ (same file) | ⚠️ (no atomicity) | ✅ (same file only) |
215216

216217
**Key Constraints:**
217-
- All contexts must connect to the same database for shared transactions
218-
- SQLite cannot share transactions across different files
218+
- All contexts must use the **same DbConnection object instance** for shared transactions
219+
- Users must explicitly create and pass a shared connection object to all contexts
219220
- Distributed transactions (cross-database) are not supported
220221

221222
## Implementation Details

0 commit comments

Comments
 (0)