# EntityFramework.Exceptions turns DbUpdateException into six typed failures

> EntityFramework.Exceptions intercepts Entity Framework Core save errors and rethrows them as typed exceptions such as UniqueConstraintException and MaxLengthExceededException, all still deriving from DbUpdateException so existing catch blocks keep working. The catch is that constraint names are only available when the index is part of the EF model, and never on SQLite.

**Giorgi/EntityFramework.Exceptions** — Strongly typed exceptions for Entity Framework Core. Supports SQLServer, PostgreSQL, SQLite, Oracle and  MySql.

- Repository: https://github.com/Giorgi/EntityFramework.Exceptions
- Website: https://giorgi.dev/entity-framework/introducing-entityframework-exceptions/
- Stars: 1,737 · Forks: 85
- Language: C#
- License: NOASSERTION
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/giorgi-entityframework-exceptions

## The problem is one catch block for every database error

Entity Framework Core wraps every failure that occurs while saving in DbUpdateException. That includes a unique constraint violation, a string longer than the column allows, a null in a required column, a numeric overflow and a deadlock, all arriving as the same exception type with a provider specific inner exception.

The usual workaround, as the README describes it, is to dig into the concrete DbException subclass instance and check the error code to work out the actual cause. That code is provider specific, so it also means a second switch statement per database engine, and it is code that has to be rewritten whenever a provider changes its numbering.

This library does that translation once per provider. You install the package for your database, call UseExceptionProcessor on the context, and catch the exception type that matches the failure.

## Six exception types and one base class kept for compatibility

The exception names listed in the README are UniqueConstraintException, CannotInsertNullException, MaxLengthExceededException, NumericOverflowException, ReferenceConstraintException and DeadlockException.

Two of them carry extra structure. UniqueConstraintException and ReferenceConstraintException expose a ConstraintName property and a ConstraintProperties collection, so a handler can report which index was violated and which properties took part in it rather than telling a user that a duplicate exists.

The compatibility decision is stated plainly: all of these exceptions inherit from DbUpdateException. That means an application with an existing catch of DbUpdateException keeps compiling and keeps catching these, so adoption does not force a coordinated rewrite of every error handler at once. It also means careless handling, since a catch of DbUpdateException is still a catch of all six.

## ConstraintName has two documented failure modes

The README carries two warning blocks, and both matter more than the feature list.

The first: ConstraintName and ConstraintProperties are populated only if the index is defined in the Entity Framework model. If the index exists in the database but is not part of the model, or if it was added with the MigrationBuilder.Sql method, those properties stay empty. In other words, a schema built from raw SQL migrations gets you the typed exception without the useful detail, which is the part most people actually wanted.

The second: those properties are never populated on SQLite. The typed exception still fires there, so the classification works, but you cannot rely on the constraint name.

Both are stated as warnings in the README rather than buried, and they should shape when you adopt this. If your error messages need to name the index, check how your indexes are created first.

## One package per provider, two for MySQL

Installation is a per-database package, and the MySQL story has two variants because there are two common providers:

```
dotnet add package EntityFrameworkCore.Exceptions.SqlServer
dotnet add package EntityFrameworkCore.Exceptions.PostgreSQL
dotnet add package EntityFrameworkCore.Exceptions.Sqlite
dotnet add package EntityFrameworkCore.Exceptions.Oracle
dotnet add package EntityFrameworkCore.Exceptions.MySql
dotnet add package EntityFrameworkCore.Exceptions.MySql.Pomelo
```

The repository layout shows a shared core in EntityFramework.Exceptions/ and a separate DbExceptionClassifier/ project, which is the code that knows each provider's error codes. One shared library, one classifier per database, which is a cleaner arrangement than six forked copies.

## Enabling it is one call in OnConfiguring

The registration is a single extension method on the options builder inside OnConfiguring:

```csharp
class DemoContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<ProductSale> ProductSale { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<Product>().HasIndex(u => u.Name).IsUnique();
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseExceptionProcessor();
    }
}
```

The model definition in that example is not incidental. The unique index on Product.Name is what makes the demo throw a UniqueConstraintException, and it is declared in OnModelCreating, which is exactly the condition the first warning describes. An index created outside the model would give the same exception type with an empty ConstraintName.

Because the switch is in OnConfiguring rather than in the DI registration, it is per context class, which is worth knowing if you build options in a shared extension.

## What the alternative looks like without a dependency

The realistic alternative is not another library but writing the classifier yourself, which for a single provider and a single application is often a page of code: catch DbUpdateException, switch on the inner exception type, inspect SqlError.Number or the PostgreSQL SqlState, and return your own result type.

The difference in approach is that a homegrown classifier is tied to one provider and one driver version, and it sits inside your application where it competes with your error handling for attention. This library moves that knowledge into a maintained project, covers five database engines, and keeps the translation out of your code entirely.

The cost is a dependency per provider and a package you must upgrade when Entity Framework Core moves. The README's target badge reads net10.0, which tells you the current target framework, and GitHub Actions runs a dotnet workflow with coverage reported to Coveralls, so both are visible signals of maintenance rather than something you have to infer.

## Licence and maintenance state

The repository licence is recorded as a custom one that GitHub cannot classify, with a License.md file at the root, while the README badge labels it Apache 2.0. Treat that as needing a direct read of License.md for anything commercial, since the two signals disagree.

The project publishes no GitHub releases at all, so versions are tracked only through NuGet packages. The last push was on 2026-10-16.

Two structural notes for contributors. The solution is now an EntityFramework.Exceptions.slnx file rather than the older.sln format, and Directory.Build.props at the root carries shared build settings across the projects. There is also a CODE_OF_CONDUCT.md.

The project takes sponsorship, with Entity Framework Extensions and Dapper Plus named as major sponsors and JetBrains sponsoring through its OSS Power-Ups series. That is worth knowing only because a project depending on sponsorship for continuity carries the usual risk, and here the alternative is reading vendor error codes yourself.

## Conclusion

Adopt EntityFramework.Exceptions when your application catches DbUpdateException today and then digs into provider error codes, since this replaces that with a typed exception per provider package. Skip it if you only need a log line, since one catch block on DbUpdateException plus the message is often enough. Verify first which provider package matches your driver, and check whether ConstraintName will actually be populated, because indexes added outside the EF model or created with MigrationBuilder.Sql do not carry it.

## FAQ

### How do I install EntityFramework.Exceptions?

Add the package matching your database with dotnet add package, for example EntityFrameworkCore.Exceptions.SqlServer, EntityFrameworkCore.Exceptions.PostgreSQL, EntityFrameworkCore.Exceptions.Sqlite, EntityFrameworkCore.Exceptions.Oracle or EntityFrameworkCore.Exceptions.MySql, plus a Pomelo variant for the Pomelo MySQL provider.

### How do I enable typed exceptions in a DbContext?

Call the UseExceptionProcessor extension method on the options builder inside OnConfiguring. After that, a failed save throws the specific exception type, such as UniqueConstraintException, instead of only DbUpdateException.

### Why is ConstraintName empty on my UniqueConstraintException?

ConstraintName and ConstraintProperties are populated only when the index is defined in the Entity Framework model. They stay empty if the index exists in the database but is not in the model, or if it was added with MigrationBuilder.Sql, and they are never populated on SQLite.

### Will typed exceptions break my existing catch of DbUpdateException?

No. All of the typed exceptions inherit from DbUpdateException for backwards compatibility, so an existing catch block still catches them. That also means a broad catch of DbUpdateException hides the specific types unless you narrow it.

## Sources

- [Giorgi/EntityFramework.Exceptions on GitHub](https://github.com/Giorgi/EntityFramework.Exceptions)
- [Issues](https://github.com/Giorgi/EntityFramework.Exceptions/issues)
- [Project website](https://giorgi.dev/entity-framework/introducing-entityframework-exceptions/)
- [README](https://github.com/Giorgi/EntityFramework.Exceptions/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/giorgi-entityframework-exceptions
