Entity Framework: Update multiple objects only if all objects exist

Viewed 459

I'm writing a PATCH REST API in C# using Entity Framework that updates a specific field in multiple objects.
Request body:

{
  "ids": [
    "id1",
    "id2"
  ],
  "foo": "bar"
}

I would like to update all objects' foo field to be bar, but only if all objects exist.

I'm trying to keep it clean by not having a preemptive select that checks whether all objects exist (which BTW might not be good enough because if an object exist now it doesn't mean it will still exist few milliseconds later).

I'm looking for a short solution that would rollback and raise an exception if one of the objects didn't successfully update (or doesn't exist).
The only solution I found is to open a transaction and update each object in a loop, which IMHO isn't the best way because I don't want to access the database each row at a time.

What's the best way to implement this?

2 Answers

The DbContext.SaveChanges method returns the number of entries written to.
In case of an update, it will return the number of updated rows.

So what you want to do is:

  1. Start a new transaction
  2. Execute a single update query for all you IDs together
  3. Check the return value of SaveChanges, and Commit if it matches the number of IDs in your query, or Abort otherwise.

The best thing I can come up with is the following:

var ids = new List<int>(){1,2,3,4,5,6,7};
var records = db.Records.Where(x=> ids.Contains(x.Id));
try
{
    foreach(var i in ids)
    {
        var record = records.FirstOrDefault(x=>x.Id == i);
        if(record == null)
        {
            throw new Exception($"Record with Id {i} not found");
        }
        record.Foo = "Bar";
    }
    db.saveChanges();
}
catch(Exception ex)
{
    //roll back changes
    var changedEntries = db.ChangeTracker.Entries()
    .Where(x => x.State != EntityState.Unchanged).ToList();
    foreach(var entry in changedEntries)
    {
        db.Entry(entry).State = EntityState.Unchanged;
    }
}

The reasoning here is that EF implicitly uses a transaction, which is "committed" when you call .SaveChanges(). If something goes wrong, you simply reset the entities' state to Unchanged and never call SaveChanges().

Related