Update Multiple Rows in Entity Framework from a list of ids - c#

I am trying to create a query for entity framework that will allow me to take a list of ids and update a field associated with them.
Example in SQL:
UPDATE Friends
SET msgSentBy = '1234'
WHERE id IN (1, 2, 3, 4)
How do I convert the above into entity framework?

something like below
var idList=new int[]{1, 2, 3, 4};
using (var db=new SomeDatabaseContext())
{
var friends= db.Friends.Where(f=>idList.Contains(f.ID)).ToList();
friends.ForEach(a=>a.msgSentBy='1234');
db.SaveChanges();
}
UPDATE:
you can update multiple fields as below
friends.ForEach(a =>
{
a.property1 = value1;
a.property2 = value2;
});

The IQueryable.ToQueryString method introduced in Entity Framework Core 5.0 may help with this scenario, if you are willing to have some raw SQL appearing in your code. 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:
using var context = new DbContext();
var ids = new List<int>() { 1, 2, 3, 4 };
var query = context.Friends.Where(_ => ids.Contains(_.id)).Select(_ => _.id);
var sql = $"UPDATE Friends SET msgSentBy = {{0}} WHERE id IN ({query.ToQueryString()})";
context.Database.ExecuteSqlRaw(sql, "1234");
The major drawback of this approach is the use of raw SQL. 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 like https://github.com/yangzhongke/Zack.EFCore.Batch.
Using DbContext.SaveChanges() which will update records one at a time rather than doing a bulk update.
If (when) the following issue is addressed in the future then we are likely to get a better answer here: Bulk (i.e. set-based) CUD operations (without loading data into memory) #795

var idList=new int[]{1, 2, 3, 4};
var friendsToUpdate = await Context.Friends.Where(f =>
idList.Contains(f.Id).ToListAsync();
foreach(var item in previousEReceipts)
{
item.msgSentBy = "1234";
}
You can use foreach to update each element that meets your condition.
Here is an example in a more generic way:
var itemsToUpdate = await Context.friends.Where(f => f.Id == <someCondition>).ToListAsync();
foreach(var item in itemsToUpdate)
{
item.property = updatedValue;
}
Context.SaveChanges()
In general you will most probably use async methods with await for db queries.

I have created a library to batch delete or update records with a round trip on EF Core 5.
Sample code as follows:
await ctx.DeleteRangeAsync(b => b.Price > n || b.AuthorName == "zack yang");
await ctx.BatchUpdate()
.Set(b => b.Price, b => b.Price + 3)
.Set(b=>b.AuthorName,b=>b.Title.Substring(3,2)+b.AuthorName.ToUpper())
.Set(b => b.PubTime, b => DateTime.Now)
.Where(b => b.Id > n || b.AuthorName.StartsWith("Zack"))
.ExecuteAsync();
Github repository: https://github.com/yangzhongke/Zack.EFCore.Batch
Report: https://www.reddit.com/r/dotnetcore/comments/k1esra/how_to_batch_delete_or_update_in_entity_framework/

The best way to do a masive update with Entity Framework 7 is like this:
context.Friends
.Where(f => f.Id <= 1_000)
.ExecuteUpdate(f => f.SetProperty(x => x.Name, x => $"Updated {x.Name}"));
Referenece: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates

Related

LINQ troubles in C# using Entity Framework

I have a few tables and this is what I need to achieve.
This gets all the rows from one table
var FRA = from prod in _cctDBContext.Fra
where prod.ActTypeId == 1
From within that, I get all the rows where ActTypeID.
Then I need to query another table from with the ID's get from that
foreach (var item in FRA)
{
var FRSA = _cctDBContext.Frsa
.Select(p => new { p.Fraid, p.Frsa1,
p.Frsaid, p.CoreId,
p.RelToEstId, p.ScopingSrc,
p.Mandatory })
.Where(p => p.Fraid == item.Fraid)
.ToList();
}
I then need to push each one of these to Entity Framework. I usually do it this way:
foreach (var item in FRA)
{
var FinanicalReportingActivity = new FinancialReportingActivity { FinancialReportingActivityId = item.Fraid, ScopingSourceType = item.ScopingSrc, Name = item.Fra1, MandatoryIndicator = item.Mandatory, WorkEffortTypeId = 0 };
_clDBContext.FinancialReportingActivity.AddRange(FinanicalReportingActivity);
}
But because I have used 2 for each loops, I cannot get the variables to work because I cannot find a way to get local variables as the entity context.
Can anyone think of a better way to code this?
Thanks
It looks like you can do this as a single join:
var query =
from prod in _cctDBContext.Fra
where prod.ActTypeId == 1
join p in _cctDBContext.Frsa on prod.Fraid equals p.Fraid
select new
{
p.Fraid,
p.Frsa1,
p.Frsaid,
p.CoreId,
p.RelToEstId,
p.ScopingSrc,
p.Mandatory
};
It looks like you are loading data from one set of entities from one database and want to create matching similar entities in another database.
Navigation properties would help considerably here. Frsa appear to be a child collection under a Fra, so this could be (if not already) wired up as a collection within the Fra entity:
Then you only need to conduct a single query and have access to each Fra and it's associated Frsa details. In your case you look to be more interested in the associated FRSA details to populate this ReportingActivity:
var details = _cctDBContext.Fra
.Where(x => x.ActTypeId == 1)
.SelectMany(x => x.Frsa.Select(p => new
{
p.Fraid,
p.Frsa1,
p.Frsaid,
p.CoreId,
p.RelToEstId,
p.ScopingSrc,
p.Mandatory
}).ToList();
though if the relationship is bi-directional where a Fra contains Frsas, and a Frsa contains a reference back to the Fra, then this could be simplified to:
var details = _cctDBContext.Frsa
.Where(x => x.Fra.ActTypeId == 1)
.Select(p => new
{
p.Fraid,
p.Frsa1,
p.Frsaid,
p.CoreId,
p.RelToEstId,
p.ScopingSrc,
p.Mandatory
}).ToList();
Either of those should give you the details from the FRSA to populate your reporting entity.

Use the option IsolationLevel.ReadUncommited for only one query in DbContext

In a .NET Core Project I'm using EntityFramework and I have a DbContext (shopContext) injected in my class repository. I have the next query:
var res = shopContext.Orders.Where(x => x.Status == 1).Sum(p => p.Total);
Occasionally, the Orders table is doing maintenance tasks and the table is locked. For this query, I can't wait to the maintenance tasks and I need access to the table with the IsolationLevel.ReadUncommited option in the transaction:
using (var transaction = mutuaContext.Database.BeginTransaction(IsolationLevel.ReadUncommitted))
{
var res = shopContext.Orders.Where(x => x.Status == 1).Sum(p => p.Total);
}
The problem is that I only want that the context execute the query with this IsolationLevel configuration in these query, but the next queries continue executing although the table is locked yet.
Why are the following queries not waiting for the Table to be unlocked?
Example of my code:
using (var transaction = mutuaContext.Database.BeginTransaction(IsolationLevel.ReadUncommitted))
{
var res = shopContext.Orders.Where(x => x.Status == 1).Sum(p => p.Total); // this code would be executed
}
var total = shopContext.Orders.Where(x => x.Status == 0).Sum(p => p.Total); // this code would NOT be executed but is executed
I don't understand how the context get the transaction configuration. I would like that someone explain it to me.
I tried call to transaction.Commit() after fist query, but still not working.
use
yourContext.Database.BeginTransaction(IsolationLevel.ReadUncommitted)
// your normal queries via yourContext goes here
// do not forget to end the transaction
You can use raw SQL query (there is a similar SqlQuery() method for EF6, as well) and specify with (nolock) table hint. Something like this:
var res = shopContext.Orders.FromSqlRaw("select sum(Total) from dbo.Orders with (nolock) where Status = 1").ToList();
However, once you will deploy this into production environment and put your code under a decent concurrent load, most probably you will not like the outcome.
UPD: For EF Core 2.2, the syntax is a bit different:
var res = shopContext.Orders.FromSql("select * from Orders with(nolock)")
.Where(x => x.Status == 1).Sum(p => p.Total);

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);
}
Disclaimer: I'm the owner of the project Entity Framework Plus
Our library has a Batch Update feature which I believe is what you are looking for
This feature supports EF Core
// Is there anything like? YES!!!
context.TimeEntries
.Where(te => timeEntryIds.Contains(te.Id))
.Update(te => new TimeEntry() { InvoiceId = invoice.Id });
Wiki: EF Batch Update
EDIT: Answer comment
does it supports contains as in your example? I think this is coming from EF Core which is not supported feature in 3.1 version even
EF Core 3.x support contains: https://dotnetfiddle.net/DAdIO2
EDIT: Answer comment
this is great but this requires to have zero parameter public constructors for classes. which is not a great. Any way to get around this issue?
Anonymous type is supported starting from EF Core 3.x
context.TimeEntries
.Where(te => timeEntryIds.Contains(te.Id))
.Update(te => new { InvoiceId = invoice.Id });
Online example: https://dotnetfiddle.net/MAnPvw
As of EFCore 7.0 you will see the built-in BulkUpdate() and BulkDelete methods:
context.Customers.Where(...).ExecuteDelete();
context.Customers.Where(...).ExecuteUpdate(c => new Customer { Age = c.Age + 1 });
context.Customers.Where(...).ExecuteUpdate(c => new { Age = c.Age + 1 });
context.Customers.Where(...).ExecuteUpdate(c => c.SetProperty(b => b.Age, b => b.Age + 1));
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.
If TimeEntry has an association to Invoice (check the navigation properties), you can probably do something like this:
var timeEntries = context.TimeEntries.Where(t => timeEntryIds.Contains(te.Id)).ToArray();
foreach(var timeEntry in timeEntries)
invoice.TimeEntries.Add(timeEntry);
context.Invoices.Add(invoice);
//save the entire context and takes care of the ids
context.SaveChanges();
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.
In entity framework core , you can do with update range method. you can see some samples usage here .
using (var context = new YourContext())
{
context.UpdateRange(yourModifiedEntities);
// or the followings are also valid
//context.UpdateRange(yourModifiedEntity1, yourModifiedEntity2, yourModifiedEntity3);
//context.YourEntity.UpdateRange(yourModifiedEntities);
//context.YourEntity.UpdateRange(yourModifiedEntity1, yourModifiedEntity2,yourModifiedEntity3);
context.SaveChanges();
}
Bulk update supported with EF 7:
context
.TimeEntries
.Where(te => timeEntryIds.Contains(te.Id))
.ExecuteUpdate(s => s.SetProperty(
i => te.InvoiceId,
i => invoice.Id));
Also there is async version for this method ExecuteUpdateAsync.
in EF Core 7, use ExecuteUpdate(), what's new
var multipleRows = TableA.Where(t=>t.Id < 99);
multipleRows.ExecuteUpdate(t=>
t.SetProperty(
r => r.Salary,
r => r.Salary * 2));
//SQL already sent to database, do not run below
//SaveChanges();
SQL being generated by EF
UPDATE [t]
SET [t].[Salary] = [t].[Salary] * 2
FROM [TableA] AS [t]
WHERE [t].[ID] < 99

Inefficient entity framework queries

I have a following foreach statement:
foreach (var articleId in cleanArticlesIds)
{
var countArt = context.TrackingInformations.Where(x => x.ArticleId == articleId).Count();
articleDictionary.Add(articleId, countArt);
}
Database looks like this
TrackingInformation(Id, ArticleId --some stuff
Article(Id, --some stuff
what I want to do is to get all the article ids count from TrackingInformations Table.
For example:
ArticleId:1 Count:1
ArticleId:2 Count:8
ArticleId:3 Count:5
ArticleId:4 Count:0
so I can have a dictionary<articleId, count>
Context is the Entity Framework DbContext. The problem is that this solution works very slow (there are > 10k articles in db and they should rapidly grow)
Try next query to gather grouped data and them add missing information. You can try to skip Select clause, I don't know if EF can handle ToDictionary in good manner.
If you encounter Select n + 1 problem (huge amount of database requests), you can add ToList() step between Select and ToDictionary, so that all required information will be brought into memory.
This depends all your mapping configuration, environment, so in order to get good performance, you need to play a little bit with different queries. Main approach is to aggregate as much data as possible at database level with few queries.
var articleDictionary =
context.TrackingInformations.Where(trackInfo => cleanArticlesIds.Contains(trackInfo.ArticleId))
.GroupBy(trackInfo => trackInfo.ArticleId)
.Select(grp => new{grp.Key, Count = grp.Count()})
.ToDictionary(info => "ArticleId:" + info.Key,
info => info.Count);
foreach (var missingArticleId in cleanArticlesIds)
{
if(!articleDictionary.ContainsKey(missingArticleId))
articleDictionary.add(missingArticleId, 0);
}
If TrackingInformation is a navigatable property of Article, then you can do this:
var result=context.Article.Select(a=>new {a.id,Count=a.TrackingInformation.Count()});
Putting it into a dictionary is simple as well:
var result=context.Article
.Select(a=>new {a.id,Count=a.TrackingInformation.Count()})
.ToDictionary(a=>a.id,a=>a.Count);
If TrackingInforation isn't a navigatable property, then you can do:
var result=context.Article.GroupJoin(
context.TrackingInformation,
foo => foo.id,
bar => bar.id,
(x,y) => new { id = x.id, Count = y.Count() })
.ToDictionary(a=>a.id,a=>a.Count);

What is the recommended practice to update or delete multiple entities in EntityFramework?

In SQL one might sometimes write something like
DELETE FROM table WHERE column IS NULL
or
UPDATE table SET column1=value WHERE column2 IS NULL
or any other criterion that might apply to multiple rows.
As far as I can tell, the best EntityFramework can do is something like
foreach (var entity in db.Table.Where(row => row.Column == null))
db.Table.Remove(entity); // or entity.Column2 = value;
db.SaveChanges();
But of course that will retrieve all the entities, and then run a separate DELETE query for each. Surely that must be much slower if there are many entities that satisfy the criterion.
So, cut a long story short, is there any support in EntityFramework for updating or deleting multiple entities in a single query?
EF doesn't have support for batch updates or deletes but you can simply do:
db.Database.ExecuteSqlCommand("DELETE FROM ...", someParameter);
Edit:
People who really want to stick with LINQ queries sometimes use workaround where they first create select SQL query from LINQ query:
string query = db.Table.Where(row => row.Column == null).ToString();
and after that find the first occurrence of FROM and replace the beginning of the query with DELETE and execute result with ExecuteSqlCommand. The problem with this approach is that it works only in basic scenarios. It will not work with entity splitting or some inheritance mapping where you need to delete two or more records per entity.
Take a look to Entity Framework Extensions (Multiple entity updates). This project allow set operations using lambda expressions. Samples from doc:
this.Container.Devices.Delete(o => o.Id == 1);
this.Container.Devices.Update(
o => new Device() {
LastOrderRequest = DateTime.Now,
Description = o.Description + "teste"
},
o => o.Id == 1);
Digging EFE project source code you can see how automatize #Ladislav Mrnka second approach also adding setting operations:
public override string GetDmlCommand()
{
//Recover Table Name
StringBuilder updateCommand = new StringBuilder();
updateCommand.Append("UPDATE ");
updateCommand.Append(MetadataAccessor.GetTableNameByEdmType(
typeof(T).Name));
updateCommand.Append(" ");
updateCommand.Append(setParser.ParseExpression());
updateCommand.Append(whereParser.ParseExpression());
return updateCommand.ToString();
}
Edited 3 years latter
Take a look to this great answer: https://stackoverflow.com/a/12751429
Entity Framework Extended Library helps to do this.
Delete
//delete all users where FirstName matches
context.Users.Delete(u => u.FirstName == "firstname");
Update
//update all tasks with status of 1 to status of 2
context.Tasks.Update(
t => t.StatusId == 1,
t2 => new Task {StatusId = 2});
//example of using an IQueryable as the filter for the update
var users = context.Users.Where(u => u.FirstName == "firstname");
context.Users.Update(users, u => new User {FirstName = "newfirstname"});
https://github.com/loresoft/EntityFramework.Extended

Categories

Resources