# How To Use The New Bulk Update Feature In EF Core 7

> In this week's newsletter, we're going to explore the new ExecuteUpdate and ExecuteDelete methods that were released with EF7. They let you write a query and run a bulk update or delete on the entities matching that query, which can significantly improve performance in some scenarios.

Published: 2022-11-26. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/how-to-use-the-new-bulk-update-feature-in-ef-core-7

**EF Core 7** added the `ExecuteUpdate` and `ExecuteDelete` methods for running bulk updates and deletes.
You write a LINQ query that selects the target records, and EF Core translates it into a single `UPDATE` or `DELETE` statement that runs directly in the database.
From my testing, that can be as much as 10x faster than loading entities and calling `SaveChanges`.

In this week's newsletter, we're going to explore the new
`ExecuteUpdate` and `ExecuteDelete` methods that were released with **EF7**.

`ExecuteUpdate` allows us to write a query and run a bulk update
operation on the entities matching that query.

Similarly, `ExecuteDelete` allows us to write a query and delete
the entities matching that query.

We can significantly improve performance using the new methods in some scenarios, and I'm going to show you what those scenarios are.

Let's dive in.

## Updating And Deleting Entities Before EF Core 7

If you want to update a collection of entities before **EF7**,
you need to load the entities into memory using the `DatabaseContext`.

The [**EF ChangeTracker**](https://milanjovanovic.tech/blog/change-tracker-ef-core) will then track any changes made to these entities.
When you are ready to commit the changes to the database,
you simply call the `SaveChanges` method.

Here's an example where we load a few notifications,
and we want to snooze them so they aren't sent:

```csharp
var notifications = dbContext
    .Notifications
    .Where(n => !n.Snoozed)
    .ToList();

foreach(var notification in notifications)
{
    notification.Snoozed = true;
}

dbContext.SaveChanges();
```

**EF7** will generate the following **SQL** statement to update the records in the database:

```sql
UPDATE [Notifications] n
SET n.[Snoozed] = TRUE
WHERE n.[Id] = @notificationId_1;

...

UPDATE [Notifications] n
SET n.[Snoozed] = TRUE
WHERE n.[Id] = @notificationId_N;
```

Notice that for every notification we end up with one **SQL UPDATE** statement.
This won't scale well as the number of notifications increases.

## Updating Entities With ExecuteUpdate

With **EF7**, we now have access to the new `ExecuteUdpate` method.
It also has an asynchronous version - `ExecuteUpdateAsync`.

How do you use it?

You need to write a query that will select the records you want to update,
and then call the `ExecuteUpdate` method on the resulting `IQueryable`.

Let's rewrite the previous example using the new approach:

```csharp
dbContext
    .Notifications
    .Where(n => !n.Snoozed)
    .ExecuteUpdate(s => s.SetProperty(
        n => n.Snoozed,
        n => true));
```

In the call to `ExecuteUpdate` we call the `SetProperty` method to specify
which properties we want to update, and what values we want to set.
The `SetProperty` method can be called multiple times, if you need to update more than one property.

In this case, **EF7** will generate the following **SQL** query:

```sql
UPDATE n
SET n.[Snoozed] = TRUE
FROM [Notifications] AS n
WHERE n.[Snoozed] = FALSE;
```

Notice that this time we only have one **SQL** query being sent to the database.
This is a major performance improvement. It can be as much as 10x faster
than the old version, from my testing.

## Deleting Entities With ExecuteDelete

Let's also see how we can do bulk deletes using the `ExecuteDelete` and `ExecuteDeleteAsync` methods.

Again, you have to write a query that will select the records you want to delete,
and then call the `ExecuteDelete` method on the resulting `IQueryable`.

If you want to delete all snoozed notifications:

```csharp
dbContext
    .Notifications
    .Where(n => n.Snoozed)
    .ExecuteDelete();
```

And **EF7** will generate the following **SQL** query:

```sql
DELETE FROM n
FROM [Notifications] AS n
WHERE n.[Snoozed] = TRUE;
```

I think this will be incredibly useful when you want to
delete records in the database based on a specific condition.

## Transactions, Change Tracking And Query Filters With Bulk Methods

You need to be aware how transactions and change tracking
work with the new bulk methods. `ExecuteUpdate` and `ExecuteDelete` will
immediately go to database, and run the **SQL** query.

**What does this mean for transactions?**

If you want to run a bulk method together with other updates
applied with `SaveChanges`, by default they won't run in the same transaction.
You need to open an explicit **transaction** using the `DatabaseContext` to keep everything consistent.

**What does this mean for change tracking?**

`ExecuteUpdate` and `ExecuteDelete` run directly on the database, without loading any entities into memory.
**EF7** will not track these entities in the `ChangeTracker`.

If you have any database interceptors defined, they won't execute
after calling one of the bulk update methods.
This also means that if you override `SaveChanges`
to add custom behavior, it won't be called.

**Do Query Filters still work?**

Yes, **query filters** will be **correctly applied** when calling `ExecuteUpdate` or `ExecuteDelete`.

## When Should You Use The New Bulk Methods?

I think this is an excellent new addition to **EF7**,
and it solves a real problem when you need to run
a typical **UPDATE** or **DELETE** query with a
**WHERE** statement applied.

Previously, you had to write raw **SQL** and execute it using something like [**Dapper**](https://milanjovanovic.tech/blog/dapper-dotnet-guide).

I will likely use this approach when it applies to my projects.

---

## Frequently asked questions

### What do ExecuteUpdate and ExecuteDelete do in EF Core?

EF Core 7 added ExecuteUpdate and ExecuteDelete, along with async versions. You write a LINQ query that selects the target records, then call the method on the resulting IQueryable. EF Core translates it into a single UPDATE or DELETE statement that runs directly in the database.

### Why is ExecuteUpdate faster than SaveChanges for bulk updates?

The SaveChanges approach loads entities into memory and generates one UPDATE statement per entity, which does not scale as the number of rows grows. ExecuteUpdate sends a single UPDATE statement with a WHERE clause to the database. In my testing it was as much as 10x faster.

### Do ExecuteUpdate and ExecuteDelete run in a transaction with SaveChanges?

Not by default. The bulk methods immediately go to the database and run the SQL query, so they do not share a transaction with changes applied through SaveChanges. Open an explicit transaction using the database context to keep everything consistent.

### Does EF Core track entities modified by ExecuteUpdate?

No. ExecuteUpdate and ExecuteDelete run directly on the database without loading entities into memory, so the ChangeTracker never sees them. Database interceptors and custom SaveChanges overrides do not execute either. Query filters, however, are still correctly applied.

### How do you update multiple properties with ExecuteUpdate?

Call SetProperty multiple times inside the ExecuteUpdate call. Each SetProperty call specifies one property to update and the value to set, and EF Core combines them into a single UPDATE statement.
