Varun Sharma
Varun Sharma

Reputation: 2761

Entity Framework Core Read & Update Multiple rows - Composite Primary Key

I have a table structure like this:

ID Code  Value
1  2     text1
2  3     text3
2  4     text4

Here ID and Code form the composite primary key i.e. in the above table you cannot have another row with ID = 2 and Code = 4.

Now we are using entity framework core and I have a class called Branch (representing the composite key) and that looks like this:

public class Branch
{
 public int ID {get; set;}
 public int Code {get; set;}
}

Secondly I have a List<Branch> as well. Now I want to do two things:

First make one database call and get the full object (ID, Code, Value) for the entire list of Branch.

After that, I will change the 'Value' for each object.

Then I want to make one database call and save the updated 'Value' for each row.

Currently I am doing this in a loop so it's inefficient.

foreach(var x in listOfBranch)
{
  var record = context.Table.Find(x.ID, x.Code);
  record.Value = "new value";
  context.SaveChanges();
}

How can we do this in one call? Thanks.

Upvotes: 1

Views: 2271

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205779

Now I want to do two things:

First make one database call and get the full object (ID, Code, Value) for the entire list of Branch.

After that, I will change the 'Value' for each object.

Then I want to make one database call and save the updated 'Value' for each row.

The second part is easy - just move SaveChanges() call outside of the loop.

The first part is tricky. At the time of writing (EF Core 2.0.2), Contains can be used only with single property, and joins to in memory collections, Concat based queries, or filters like

.Where(e => listOfBranch.Any(b => b.ID == e.ID && b.Code == e.Code))

all these are not translated to SQL and are evaluated locally.

The only way to get the desired result with a single database query is to build dynamically a filter criteria like this:

.Where(e =>
    (e.ID == listOfBranch[0].ID && e.Code == listOfBranch[0].Code)
    ||
    (e.ID == listOfBranch[1].ID && e.Code == listOfBranch[1].Code)
    // ...
    ||
    (e.ID == listOfBranch[N-1].ID && e.Code == listOfBranch[N-1].Code)
)

You can build the above predicate using Expression class methods as follows (just replace Record with the type of entity):

var parameter = Expression.Parameter(typeof(Record));

var body = listOfBranch
    .Select(b => Expression.AndAlso(
        Expression.Equal(Expression.Property(parameter, "ID"), Expression.Constant(b.ID)),
        Expression.Equal(Expression.Property(parameter, "Code"), Expression.Constant(b.Code))))
    .Aggregate(Expression.OrElse);

var predicate = Expression.Lambda<Func<Record, bool>>(body, parameter);

and the usage:

foreach (var record in context.Table.Where(predicate))
{
    record.Value = "new value";
}
context.SaveChanges();

Upvotes: 6

Related Questions