From d972059c4d4171fc5d1a0c9dc836f4596c312a50 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Feb 2026 20:36:18 +0000 Subject: [PATCH 1/3] Add claude.md with project conventions and build instructions https://claude.ai/code/session_0147Z5VzzZpX1pUfaAapNZie --- claude.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 claude.md diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..ca83152 --- /dev/null +++ b/claude.md @@ -0,0 +1,51 @@ +# EntityFramework.Exceptions + +Typed exception handling for Entity Framework Core. Converts database-specific errors into strongly-typed .NET exceptions instead of generic `DbUpdateException`. + +## Build & Test + +```bash +dotnet restore # Restore NuGet dependencies +dotnet build --no-restore # Build all projects +dotnet test --no-build # Run tests (requires Docker for Testcontainers) +``` + +Tests use **Testcontainers** and require a running Docker daemon. Each database provider (SQL Server, PostgreSQL, MySQL, Oracle, SQLite) spins up its own container. + +## Project Structure + +The solution (`EntityFramework.Exceptions.slnx`) has two main layers: + +- **DbExceptionClassifier/** — Database-specific error code classification. Each provider implements `IDbExceptionClassifier` to map native error codes to a `DatabaseError` enum. + - `Common/` — `IDbExceptionClassifier` interface + - `SqlServer/`, `PostgreSQL/`, `MySQL/`, `MySQL.Pomelo/`, `Oracle/`, `Sqlite/` — Provider implementations + +- **EntityFramework.Exceptions/** — EF Core integration via interceptors. Catches `DbException`, classifies it, and throws a typed exception. + - `Common/` — Base `ExceptionProcessorInterceptor`, exception classes (`UniqueConstraintException`, `CannotInsertNullException`, `MaxLengthExceededException`, `NumericOverflowException`, `ReferenceConstraintException`), `ExceptionFactory` + - `SqlServer/`, `PostgreSQL/`, `MySQL/`, `MySQL.Pomelo/`, `Oracle/`, `Sqlite/` — Provider-specific interceptors and `UseExceptionProcessor()` extension methods + - `Tests/` — xUnit test suite using Testcontainers + +- **Directory.Build.props** — Shared build properties (target framework, version, NuGet metadata). All non-Common projects automatically reference their corresponding Common project. + +## Architecture + +1. **Interceptor pattern**: `ExceptionProcessorInterceptor` implements `IDbCommandInterceptor` and `ISaveChangesInterceptor` +2. **Classification**: Each database provider has an `IDbExceptionClassifier` that maps native error codes to `DatabaseError` enum values +3. **Factory**: `ExceptionFactory` creates the appropriate typed exception +4. **Extension methods**: Each provider exposes `UseExceptionProcessor()` on `DbContextOptionsBuilder` + +## Code Conventions + +- **C# / .NET 8.0** with file-scoped namespaces, primary constructors, nullable reference types, and implicit usings +- **Naming**: `[Database]ExceptionClassifier`, `[Database]ExceptionProcessorInterceptor`, `ExceptionProcessorExtensions.UseExceptionProcessor()` +- **Test naming**: `[Scenario]Throws[ExceptionType]` (e.g., `UniqueColumnViolationThrowsUniqueConstraintException`) +- **MySQL.Pomelo** shares source files with **MySQL** via `` in .csproj and uses `#if POMELO` preprocessor directive +- Exception classes follow a standard pattern: inherit `DbUpdateException`, provide all standard constructor overloads, and optionally expose `ConstraintName`, `ConstraintProperties`, and `SchemaQualifiedTableName` properties + +## Testing Notes + +- Base test class `DatabaseTests` defines ~12 virtual test methods; provider-specific test classes inherit and override as needed +- **SQLite** does not populate `ConstraintName`/`ConstraintProperties` and does not enforce numeric overflow +- **SQL Server** skips numeric overflow tests (`ArgumentException` from SqlClient) +- **MySQL** primary key violations do not populate constraint properties +- Test fixtures use `IAsyncLifetime` for container lifecycle management From 8a0db27dcd9b8d26ca43203e09e3ea1161c67896 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Feb 2026 20:44:59 +0000 Subject: [PATCH 2/3] Address PR feedback: fix test command and SQLite Docker note - Change `dotnet test --no-build` to `dotnet test --no-restore` so tests build as needed on clean checkouts - Clarify that SQLite tests run in-process and don't require Docker, while only containerized providers need a Docker daemon https://claude.ai/code/session_0147Z5VzzZpX1pUfaAapNZie --- claude.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/claude.md b/claude.md index ca83152..485415c 100644 --- a/claude.md +++ b/claude.md @@ -7,10 +7,10 @@ Typed exception handling for Entity Framework Core. Converts database-specific e ```bash dotnet restore # Restore NuGet dependencies dotnet build --no-restore # Build all projects -dotnet test --no-build # Run tests (requires Docker for Testcontainers) +dotnet test --no-restore # Run tests (requires Docker for Testcontainers) ``` -Tests use **Testcontainers** and require a running Docker daemon. Each database provider (SQL Server, PostgreSQL, MySQL, Oracle, SQLite) spins up its own container. +Tests use **Testcontainers** and require a running Docker daemon for containerized providers (SQL Server, PostgreSQL, MySQL, Oracle), each of which spins up its own container. **SQLite** tests run in-process and do not require Docker. ## Project Structure From 7f8d97d40f15ae235edbbe1ae77ab7f859e6e7da Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Feb 2026 21:10:00 +0000 Subject: [PATCH 3/3] Add test for PostgreSQL SqlState 22001 max length violation (fixes #92) Add a PostgreSQL-specific test that verifies MaxLengthExceededException is thrown when executing raw SQL that inserts a value exceeding the character varying column limit. This exercises the IDbCommandInterceptor CommandFailed path for PostgreSQL error 22001 (StringDataRightTruncation). The PostgreSQL classifier already handles SqlState 22001 via PostgresErrorCodes.StringDataRightTruncation. This test ensures the full interceptor pipeline works for raw SQL operations in addition to the existing SaveChanges and ExecuteUpdate test coverage. https://claude.ai/code/session_0147Z5VzzZpX1pUfaAapNZie --- .../Tests/PostgreSQLTests.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/EntityFramework.Exceptions/Tests/PostgreSQLTests.cs b/EntityFramework.Exceptions/Tests/PostgreSQLTests.cs index c152848..ace10df 100644 --- a/EntityFramework.Exceptions/Tests/PostgreSQLTests.cs +++ b/EntityFramework.Exceptions/Tests/PostgreSQLTests.cs @@ -1,5 +1,7 @@ -using EntityFramework.Exceptions.PostgreSQL; +using EntityFramework.Exceptions.Common; +using EntityFramework.Exceptions.PostgreSQL; using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; using Testcontainers.PostgreSql; using Xunit; @@ -10,6 +12,19 @@ public class PostgreSQLTests : DatabaseTests, IClassFixture(() => + DemoContext.Database.ExecuteSqlInterpolated( + $"INSERT INTO \"Products\" (\"Name\") VALUES ({longName})")); + await Assert.ThrowsAsync(() => + DemoContext.Database.ExecuteSqlInterpolatedAsync( + $"INSERT INTO \"Products\" (\"Name\") VALUES ({longName})")); + } } public class PostgreSQLDemoContextFixture : DemoContextFixture