Conversation
Add IsDeadlockError
Oracle can trigger deadlocks (ORA-00060) using the classic cross-update pattern. Unlike PostgreSQL/SQL Server which roll back the victim's entire transaction, Oracle only rolls back the victim's statement. This requires using Task.WhenAny instead of Task.WhenAll, then explicitly rolling back the victim's transaction to release earlier locks and unblock the other session. https://claude.ai/code/session_01WusLPsCyEsDpSbp2Nm6Tkx
Add IsDeadlockError
There was a problem hiding this comment.
Pull request overview
This PR adds deadlock detection to the exception-classification pipeline by introducing an IsDeadlockError classifier hook, mapping provider-specific deadlock signals to a new DeadlockException, and adding cross-provider tests to validate the behavior.
Changes:
- Add
IsDeadlockErrortoIDbExceptionClassifierand implement it for SQLite, SQL Server, PostgreSQL, Oracle, and MySQL. - Introduce
DeadlockExceptionand wire deadlock classification through the interceptor/factory. - Add/override deadlock-focused tests and enable creating secondary
DemoContextinstances from stored options.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| EntityFramework.Exceptions/Tests/SqliteTests.cs | Adds a SQLite-specific deadlock test case. |
| EntityFramework.Exceptions/Tests/OracleTests.cs | Adds an Oracle-specific deadlock test case with Oracle-specific rollback handling. |
| EntityFramework.Exceptions/Tests/DemoContext.cs | Stores DbContextOptions for creating secondary contexts in tests. |
| EntityFramework.Exceptions/Tests/DatabaseTests.cs | Adds a provider-agnostic deadlock test (overridden by some providers). |
| EntityFramework.Exceptions/Common/Exceptions.cs | Adds DeadlockException type. |
| EntityFramework.Exceptions/Common/ExceptionProcessorInterceptor.cs | Adds deadlock classification path and enum value. |
| EntityFramework.Exceptions/Common/ExceptionFactory.cs | Maps DatabaseError.DeadLock to DeadlockException. |
| DbExceptionClassifier/Sqlite/SqliteExceptionClassifier.cs | Implements SQLite deadlock detection. |
| DbExceptionClassifier/SqlServer/SqlServerExceptionClassifier.cs | Implements SQL Server deadlock detection (1205). |
| DbExceptionClassifier/PostgreSQL/PostgreSQLExceptionClassifier.cs | Implements PostgreSQL deadlock detection (DeadlockDetected). |
| DbExceptionClassifier/Oracle/OracleExceptionClassifier.cs | Implements Oracle deadlock detection (ORA-00060). |
| DbExceptionClassifier/MySQL/MySQLExceptionClassifier.cs | Implements MySQL deadlock detection. |
| DbExceptionClassifier/Common/IDbExceptionClassifier.cs | Adds IsDeadlockError to the classifier interface. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| await using var controlContext = new DemoContext(DemoContext.Options); | ||
| await using var transaction1 = await DemoContext.Database.BeginTransactionAsync(); | ||
|
|
There was a problem hiding this comment.
BeginTransactionAsync() on DemoContext doesn’t acquire a lock in SQLite until a statement is executed. As written, transaction1 is never used, so the controlContext update may succeed and the test will fail/flap. Execute at least one read/write statement inside transaction1 (and keep it uncommitted) before running the conflicting controlContext ExecuteUpdateAsync, or use an explicit locking transaction mode to reliably trigger SQLITE_LOCKED_SHAREDCACHE.
| // Execute a write operation within transaction1 to acquire a SQLite lock | |
| await DemoContext.Products | |
| .Where(c => c.Id == product.Id) | |
| .ExecuteUpdateAsync(c => c.SetProperty(p => p.Name, "Test11")); |
| public bool IsNumericOverflowError(DbException exception); | ||
| public bool IsUniqueConstraintError(DbException exception); | ||
| public bool IsMaxLengthExceededError(DbException exception); | ||
| public bool IsDeadlockError(DbException exception); |
There was a problem hiding this comment.
Adding IsDeadlockError as a new abstract interface member is a source/binary breaking change for any external IDbExceptionClassifier implementations. If this package is meant to be extensible, consider providing a default interface implementation returning false (net8 supports DIMs) to preserve compatibility, or ensure the package versioning reflects a breaking change.
| public bool IsDeadlockError(DbException exception); | |
| public bool IsDeadlockError(DbException exception) => false; |
EntityFramework.Exceptions/Common/ExceptionProcessorInterceptor.cs
Outdated
Show resolved
Hide resolved
…r.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
No description provided.