Efficient EF deletion

Exploring a more efficient method to perform deletes in Entity Framework

Home DailyDrop

Daily Knowledge Drop

When using Entity Framework to perform a delete (prior to EF7), first the record in question is required to be selected from the table (so that EF change tracking has visibility of it), then is marked as deleted, and then the actual database delete is performed with SaveChanges.

Khalid Abuhakmeh has a very interesting idea, which eliminates the need for the extra round trip to select the data - manually add the item to EF change tracking (without first checking the database), mark it as deleted, and save the changes. If a specific exception occurs because the record is not available for deletion, handle that separately.


Use case

In our use case, we will define a Delete endpoint, which when called will delete the Blog record using the supplied Id.

Traditional method

Below is a sample of the "traditional" method of handling the use case:

// context is injected from the DI container
// id is supplied as part of the url
app.MapDelete("/blog/v1/{id:int}", async (DemoContext context, int id) =>
{
    // first lookup the blog by the id
    var blog = await context.Blogs.FindAsync(id);

    // if the blog was found
    if(blog != null)
    {
        // mark it as deleted
        context.Remove(blog);
    }

    await context.SaveChangesAsync();

});
  1. First, a query is performed against the database to ensure that a Blog with the specified Id exists (with FindAsync)
  2. In the case when the record does exist, the row will start being tracked by Entity Framework's change tracker
  3. The record is flagged as delete, with the Remove command
  4. The changes are finally applied to the database, with SaveChangesAsync

With this method, there are two round trips to the database - in steps 1 and 4.


Efficient Method

With the more efficient method, one database round trip can be eliminated:

// context is injected from the DI container
// id is supplied as part of the url
app.MapDelete("/blog/v1/{id:int}", async (DemoContext context, int id) =>
{
    try
    {
        // create an instance of the object with the
        // supplied id. 
        var blog = new Blog { Id = id };
        // make the change tracker aware of this object
        var contextBlog = context.Blogs.Attach(blog);
        // mark it as deleted
        contextBlog.State = EntityState.Deleted;

        // delete
        await context.SaveChangesAsync();
    }
    catch(DbUpdateConcurrencyException ex)
    {
        Console.WriteLine("Swallowing delete exception.");
    }
});

With this method:

  1. Instead of checking in the database that a record exists with the supplied Id, it is assumed a record does already exist with the id
  2. A record, with just the Id (the primary key value) set, is created, attached to the change tracker, and marked as deleted
  3. SaveChangesAsync is then called to perform the actual delete on the database
  4. If a record with that specific Id does in fact not exist, an exception will be thrown - which is caught and swallowed (with logging)

The outcome in both cases is the same, but an expensive database round trip has been eliminated.

In the references link below, Khalid Abuhakmeh has additional ideas and code samples on how this functionality can be cleaned and wrapped into an extension method to make usage even easier.


Notes

Having run into this exact issue before, this method is very interesting. Exceptions do have an overhead, but the overhead of a database round trip will almost always out-weigh the exception overhead. Definitely consider the "efficient" method if the "traditional pattern is used throughout code. Having said that, EF7 introduces Bulk Update/Delete functionality which will allow for this nativity in the EF framework - so this method will become obsolete with time.


References

More Efficient Deletes With Entity Framework Core

Daily Drop 207: 22-11-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net ef entityframework delete