Gracefully Handling Entity Framework Exceptions with EntityFramework.Exceptions

7/24/2023
2 minute read

Working with databases can sometimes be daunting, mainly when errors occur. These errors or exceptions can be due to many reasons, such as constraint violations, connection issues, or syntax errors. Entity Framework throws a generic DbException or DbUpdateException for most of these database issues. But we cand get more specific exceptions based on the concrete "problem"! That's where EntityFramework.Exceptions comes in.

The way it works

EntityFramework.Exceptions sites in the middle between you and Entity Framework and wraps the exceptions depending on various conditions in a new exception with a meaningful name. For example if you have a IsRequired property that unfortunately is null, you will get an CannotInsertNullException. Not only makes it debugging easier but you could also catch specific exceptions and handle them gracefully.

Setup

The setup is quite easy, add the NuGet package for the provider you need. Here is a complete overview. For example:

dotnet add package EntityFrameworkCore.Exceptions.SqlServer
dotnet add package EntityFrameworkCore.Exceptions.PostgreSQL
...

And so on. In my case I will use Sqlite with the in-memory database to quickly showcase how it works. Then you have to add the "middleware":

public class BlogContext : DbContext
{
    public DbSet<BlogPost> BlogPosts { get; set; } = default!;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlite("Filename=:memory:")
            .UseExceptionProcessor();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<BlogPost>().Property(b => b.Title)
            .IsRequired();
    }
}

UseExceptionProcessor is the interesting part that comes from the package.

An example

The example above shows, that my Title has to be required - so it can not be null. But what if we do?

await using var context = new BlogContext();
context.Database.OpenConnection();
context.Database.EnsureCreated();

var blogPostWithNullTitle = new BlogPost
{
    Title = null,
    Content = "Content"
};

context.BlogPosts.Add(blogPostWithNullTitle);
await context.SaveChangesAsync();

Now here is the exception without the middleware:

/Users/stgi/repos/BlogExamples/EntityFrameworkExceptions/bin/Debug/net7.0/EntityFrameworkExceptions
Unhandled exception. Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
 ---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'NOT NULL constraint failed: BlogPosts.Title'.
   at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
   at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
...

And here with:

/Users/stgi/repos/BlogExamples/EntityFrameworkExceptions/bin/Debug/net7.0/EntityFrameworkExceptions
Unhandled exception. EntityFramework.Exceptions.Common.CannotInsertNullException: Cannot insert null
 ---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'NOT NULL constraint failed: BlogPosts.Title'.
   at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
   at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()

So only a thin layer on top that encapsulate the original exception in a more meaningful one!

That allows you to do stuff like this:

try
{
    await context.SaveChangesAsync();
}
catch (CannotInsertNullException e)
{
    // Do something specific to this exception
}

Resources

  • Source code to this blog post: here
  • All my sample code is hosted in this repository: here

Entity Framework and ordered indexes

In Entity Framework 7, the team has added support for ordered indexes to the fluent API. In this blog post we will look at how to use this feature and what it means for your database.

Entity Framework - Storing complex objects as JSON

From time to time, it is nice to store complex objects or lists as JSON in the database. With Entity Framework 8, this is now easily possible. But this was possible all along with Entity Framework 7.

Entity Framework 8: Raw SQL queries on unmapped types

The next iteration of Entity Framework, namely Entity Framework 8, will have a new and exciting feature:

Support raw SQL queries without defining an entity type for the result

That means less boilerplate code!

An error has occurred. This application may no longer respond until reloaded. Reload x