2

I'm using EF Core and .NET 6 and I would like to essentially upsert an entity to a table - a fairly simple ask.

I have the following code:

var countries = GetCountries();

using (var scope = scopeFactory.CreateScope())
{
    var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

    foreach (var c in countries)
    {
        // check if the country already exists.
        var exists = dbContext.Countries.Where(d => d.Id == c.Id).FirstOrDefault();

        // if already exists - update (rather than add).
        if (exists != null) 
        {
            exists.Name = c.Name;
            exists.DisplayName = c.DisplayName;
            ... // omitted other prop updates.

            dbContext.Countries.Update(exists);
        } 
        else
        {
            dbContext.Countries.Add(c);
        }
    }

    await dbContext.SaveChangesAsync();
}

I was wondering - is there was a more efficient way to update without manually looking up then updating (it's not very performant).

Preferably, I was hoping there was a Merge method in EF Core but can't find anything useful (that's free...). Rather than doing the manual lookup and update in this way.

I'm probably missing something very obvious here - thanks for any pointers in advance!

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Rob
  • 6,819
  • 17
  • 71
  • 131
  • 2
    EF6 had [`IDbSetExtensions.AddOrUpdate()`](https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.migrations.idbsetextensions.addorupdate?view=entity-framework-5.0.0) which, according to [what is the Alternate for AddorUpdate method in EF Core?](https://stackoverflow.com/questions/62449078/what-is-the-alternate-for-addorupdate-method-in-ef-core), is replaced by `context.Update(entity);`. In your case, `dbContext.Countries.Update(c);`. (There are notes on the linked question about autogenerated keys and concurrency; this may not fit your situation). – D M May 23 '22 at 16:42

3 Answers3

5

EF Core do not have Merge, or similar for Upsert. You can improve performance of your query by selecting existng items in one batch. Also you do not need to call Update, just change properties.

var countries = GetCountries();

using (var scope = scopeFactory.CreateScope())
{
    var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

    var countyIds = countries.Select(c => c.Id);
    var existing = (await dbContext.Countries.Where(d => countyIds.Contains(d.Id))
        .ToListAsync())
        .ToDictionary(c => c.Id);

    foreach (var c in countries)
    {
        // check if the country already exists.

        if (existing.TryGetValue(c.Id, out var toUpdate))
        {
            // if already exists - update (rather than add).
            toUpdate.Name = c.Name;
            toUpdate.DisplayName = c.DisplayName;
            ... // omitted other prop updates.
        } 
        else
        {
            dbContext.Countries.Add(c);
        }
    }

    await dbContext.SaveChangesAsync();
}
Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32
  • Agreed. Go get "all of the possible already existing" with a SINGLE call (where the answer has "contains"). and delay the .SaveChangesAsync to the end. – granadaCoder May 23 '22 at 17:30
0

The answer by user19087368 seems the most straight forward. I tested it on .NET 6 and it worked perfectly - adapted a little bit to my usecase:

Here is my version of it with the "guid" as the primary key:

public async Task CreateOrUpdateApplication(Application application)
{
    var itemExists = _dbContext
        .Application
        .Any(i => i.ApplicationGuid == application.ApplicationGuid);
    _dbContext.Entry(application).State = itemExists ? 
        EntityState.Modified : EntityState.Added;
    await _dbContext.SaveChangesAsync();
}
Nimantha
  • 6,405
  • 6
  • 28
  • 69
-2
public void InsertOrUpdate(Entity entity) 
{ 
    using (var context = new dbContext.Countries()) 
    { 
        context.Entry(entity).State = entity.Id == 0 ? 
                                   EntityState.Added : 
                                   EntityState.Modified; 

        context.SaveChanges(); 
    } 
}