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 diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..485415c --- /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-restore # Run tests (requires Docker for Testcontainers) +``` + +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 + +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