Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bulk Update in Entity Framework Core

I pull a bunch of timesheet entries out of the database and use them to create an invoice. Once I save the invoice and have an Id I want to update the timesheet entries with the invoice Id. Is there a way to bulk update the entities without loading them one at a time?

void SaveInvoice(Invoice invoice, int[] timeEntryIds) {
    context.Invoices.Add(invoice);
    context.SaveChanges();

    // Is there anything like?
    context.TimeEntries
        .Where(te => timeEntryIds.Contains(te.Id))
        .Update(te => te.InvoiceId = invoice.Id);
}
like image 665
Adrian Brand Avatar asked Apr 20 '17 23:04

Adrian Brand


People also ask

Does EF core support bulk insert?

The EF Bulk Insert feature let you insert thousands of entities in your database efficiently. This feature is provided by the library EF Extensions (Included with EF Classic). EF Extensions is used by over 2000 customers all over the world and supports all Entity Framework versions (EF4, EF5, EF6, EF Core, EF Classic).

How do I update items in Entity Framework?

This can be achieved in several ways: setting the EntityState for the entity explicitly; using the DbContext. Update method (which is new in EF Core); using the DbContext. Attach method and then "walking the object graph" to set the state of individual properties within the graph explicitly.

What is bulk update?

A bulk update definition specifies a number of conditions and a single update function. A policy must satisfy all the specified conditions in order for it to updated by the function. Bulk updates are executed through a global activity. The bulk update definition code is a parameter of this activity.


3 Answers

Are you after the performance of simplified syntax?

I would suggest to use direct SQL query,

 string query = "Update TimeEntries Set InvoiceId = <invoiceId> Where Id in (comma separated ids)";    
 context.Database.ExecuteSqlCommandAsync(query);

For comma separated ids you can do string.Join(',', timeEntryIds)

It depends on what you actually need. If you want to go with Linq, then you need to iterate through each object.

like image 27
Dhanuka777 Avatar answered Oct 03 '22 06:10

Dhanuka777


The IQueryable.ToQueryString method introduced in Entity Framework Core 5.0 may help with this scenario. This method will generate SQL that can be included in a raw SQL query to perform a bulk update of records identified by that query.

For example:

void SaveInvoice(Invoice invoice, int[] timeEntryIds) {
    context.Invoices.Add(invoice);
    context.SaveChanges();

    var query = context.TimeEntries
        .Where(te => timeEntryIds.Contains(te.Id))
        .Select(te => te.Id);

    var sql = $"UPDATE TimeEntries SET InvoiceId = {{0}} WHERE Id IN ({query.ToQueryString()})";

    context.Database.ExecuteSqlRaw(sql, invoice.Id);
}

The major drawback of this approach is that you end up with raw SQL appearing in your code. However I don't know of any reasonable way to avoid that with current Entity Framework Core capabilities - you're stuck with this caveat, or the caveats of other answers posted here such as:

  • Introducing a dependency on another library such as Entity Framework Plus or ELinq.
  • Using DbContext.SaveChanges() which will involve the execution of multiple SQL queries to retrieve and update records one at a time rather than doing a bulk update.
like image 115
chrisg Avatar answered Oct 03 '22 08:10

chrisg


As of EFCore 7.0 you will see the built-in BulkUpdate() and BulkDelete methods:

context.Customers.Where(...).BulkDelete();
context.Customers.Where(...).BulkUpdate(c => new Customer { Age = c.Age + 1 });
context.Customers.Where(...).BulkUpdate(c => new { Age = c.Age + 1 });
like image 21
Majid Shahabfar Avatar answered Oct 03 '22 07:10

Majid Shahabfar