1- using System . Threading . Tasks ;
1+ using System . Linq ;
2+ using System . Threading . Tasks ;
3+ using EntityFramework . Exceptions . Common ;
24using EntityFramework . Exceptions . Oracle ;
35using Microsoft . EntityFrameworkCore ;
46using Testcontainers . Oracle ;
@@ -12,10 +14,51 @@ public OracleTests(OracleTestContextFixture fixture) : base(fixture.DemoContext)
1214 {
1315 }
1416
15- [ Fact ( Skip = "Skipping as oracle can't trigger deadlock." ) ]
16- public override Task Deadlock ( )
17+ [ Fact ]
18+ public override async Task Deadlock ( )
1719 {
18- return Task . CompletedTask ;
20+ var p1 = DemoContext . Products . Add ( new ( ) { Name = "Test1" } ) ;
21+ var p2 = DemoContext . Products . Add ( new ( ) { Name = "Test2" } ) ;
22+ await DemoContext . SaveChangesAsync ( ) ;
23+
24+ var id1 = p1 . Entity . Id ;
25+ var id2 = p2 . Entity . Id ;
26+
27+ await using var controlContext = new DemoContext ( DemoContext . Options ) ;
28+ await using var transaction1 = await DemoContext . Database . BeginTransactionAsync ( ) ;
29+ await using var transaction2 = await controlContext . Database . BeginTransactionAsync ( ) ;
30+
31+ // Each transaction locks one row
32+ await DemoContext . Products . Where ( c => c . Id == id1 )
33+ . ExecuteUpdateAsync ( c => c . SetProperty ( p => p . Name , "Test11" ) ) ;
34+ await controlContext . Products . Where ( c => c . Id == id2 )
35+ . ExecuteUpdateAsync ( c => c . SetProperty ( p => p . Name , "Test21" ) ) ;
36+
37+ // Start both cross-updates concurrently to create a deadlock cycle
38+ var task1 = Task . Run ( ( ) => DemoContext . Products
39+ . Where ( c => c . Id == id2 )
40+ . ExecuteUpdateAsync ( c => c . SetProperty ( p => p . Name , "Test22" ) ) ) ;
41+ var task2 = Task . Run ( ( ) => controlContext . Products
42+ . Where ( c => c . Id == id1 )
43+ . ExecuteUpdateAsync ( c => c . SetProperty ( p => p . Name , "Test12" ) ) ) ;
44+
45+ // Oracle only rolls back the victim's statement, not its transaction,
46+ // so the non-victim remains blocked. Use WhenAny to catch the victim first.
47+ var completedTask = await Task . WhenAny ( task1 , task2 ) ;
48+ await Assert . ThrowsAsync < DeadlockException > ( ( ) => completedTask ) ;
49+
50+ // Roll back the victim's transaction to release its earlier locks
51+ // and unblock the other session.
52+ if ( completedTask == task1 )
53+ {
54+ await transaction1 . RollbackAsync ( ) ;
55+ await task2 ;
56+ }
57+ else
58+ {
59+ await transaction2 . RollbackAsync ( ) ;
60+ await task1 ;
61+ }
1962 }
2063}
2164
0 commit comments