EntityFramework.Exceptions turns DbUpdateException into six typed failures
Strongly typed exceptions for Entity Framework Core. Supports SQLServer, PostgreSQL, SQLite, Oracle and MySql.
At a glance
- What is it?
- 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.
- Who is it for?
- 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.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
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.PomeloThe 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:
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.
Editorial 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.
Frequently asked questions
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.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/giorgi-entityframework-exceptions)Was this useful?