简体   繁体   中英

I want a way to update a range of records based on a where clause in entity framework without using ToList() and foreach

I have a function in my asp.net core app which updates a bunch of records based on a certain criteria I write in a where clause... I read that ToList() has bad performance, so is there a better and faster way than using tolist and foreach??? This is my current way doing it, I would appreciate it if someone provides a more efficient way

    public async Task UpdateCatalogOnTenantApproval(int tenantID)
    {
        var catalogQuery = GetQueryable();
        var catalog = await catalogQuery.Where(x => x.IdTenant == tenantID).ToListAsync();
        catalog.ForEach(c => { c.IsApprovedByAdmin = true; c.IsActive = true; });
        Context.UpdateRange(catalog);
       await Context.SaveChangesAsync(); ;
    }

Entity Framework does not handle bulk update operations by default -- hence your existing code. If you really want to do these bulk operations, then you have two options:

  1. Write the SQL yourself and use the ExecuteSqlCommand() method to execute it; or
  2. Look at 3rd party extensions, such as https://entityframework-extensions.net/

read that ToList() has bad performance,

That is wrong. ToList has as good a performance as you will get - submit a bad query which is overly complex and which results in bad SQL that SQL Server will take ages to execute and it is slow.

Also, many people think "ToList" is slow (as in: in the profiler). You see, yo ustart with a db context, take a set of entities there, add some where clauses - all fast. Then ToList and it takes "long" (compared to the rest). Well, THAT is where the query is sent to the sql server;) WHere (x=>whatever) takes "no time" because all it does is add some nodes to the expression tree, not executing the query. THAT is mostly what people mix up - delayed execution which exeutes only when asked for the results.

And third, some people like "ToList().Where() and complain about performance. Filter as much as possible no the DB.

All three reasons are why people think ToList is slow - but all it shows is a lack of understanding of how LINQ and SQL operate.

We can reduce query cost by selecting a subset of data before attaching for EF to track, and then updating. However, it may be just pointless micro-optimization that does not perform significantly better unless you are processing massive amount of records.

// select pk for EF to track, and the 2 fields to be modified
var catalog = await catalogQuery.Where(x => x.IdTenant == tenantID)
              .Select(x => new Catelog{x.CatelogId, x.IsApprovedByAdmin, x.IsActive }).ToListAsync();

//next we attach range here to let EF track the list
Context.AttachRange(catalog);

//perform your update as usual, this will be flagged as modified if changed
catalog.ForEach(c => { c.IsApprovedByAdmin = true; c.IsActive = true; });

//save and let EF update based on modified fields.
await Context.SaveChangesAsync(); 

Let me explain to you what you have done and what you are trying to do. You are partially right about the performance issues related to ToList and ToListAsync as they are mainly responsible to upload entities to the memory and track them.

Based on that if your request is expected to deal intensively with light data you are not required to enhance your code. if it is not, however, there are many open approaches each one has its pros and cons and you have to treat and balance between them for each case you do not want to use the dual app-SQL requests.

let's be more realistic by talking about your case:

1- we assume that your method is a resource-consuming by (loading high volume of data, intensively called, or both)

2- I see the modification is too static by updating all of the rows by c.IsApprovedByAdmin = true; c.IsActive = true; c.IsApprovedByAdmin = true; c.IsActive = true;

form (1) and (2) I suggest to write a stored procedure or ExexcuteSqlCammand (as Bryan Lewis suggested) that does this for you

because (3) the stored procedures, triggers, and all the SQL based operation are hard-maintainable and are highly potential for hidden exceptions. In your case, however, you less likely to fell into that as your code is too basic and you could reduce more the risk by construct your query from dynamic elements such as nameof(yourClassName that is the table name).YouProperty and the like...

Anyway, this is an example to show that there is no ideal approach and you have study each case alone.

Finally, I do not agree with the 3d parties extensions as most of freely provided developed by unprofessionals and tracking exceptions caused by them are nightmares, and the paid versions are too expensive and not 0-exception extensions. The 3d party extension are more oriented to the complex bulk update/delete and/or huge data. eg

await Context.UpdateAsync(e=> new Catalog
                                  { Archived = e.LastUpdate > 
                                       DateTime.UtcNow.AddYears(-99)? false : true
                                   });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM