I\'m writing a seed method using EntityFramework.Core 7.0.0-rc1-final.
What happened to the AddOrUpdate method of DbSet?
I've found a nice solution that allows you specify the property that should match. However, it doesn't take a single entity, but a list in each call. It may give you some hints on how to implement a better version that works like the good-olde one.
https://github.com/aspnet/MusicStore/blob/7787e963dd0b7293ff95b28dcae92407231e0300/samples/MusicStore/Models/SampleData.cs#L48
(Code isn't mine)
I started with Tjaart's answer and modified two things:
I have change tracking turned on and was getting the error others have mentioned regarding that EF is already tracking it. This does a find on the already tracked entity and copies the values from the incoming entity to it, then updates the original entity
public TEntity AddOrUpdate(TEntity entity)
{
var entityEntry = Context.Entry(entity);
var primaryKeyName = entityEntry.Context.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey().Properties
.Select(x => x.Name).Single();
var primaryKeyField = entity.GetType().GetProperty(primaryKeyName);
var t = typeof(TEntity);
if (primaryKeyField == null)
{
throw new Exception($"{t.FullName} does not have a primary key specified. Unable to exec AddOrUpdate call.");
}
var keyVal = primaryKeyField.GetValue(entity);
var dbVal = DbSet.Find(keyVal);
if (dbVal != null)
{
Context.Entry(dbVal).CurrentValues.SetValues(entity);
DbSet.Update(dbVal);
entity = dbVal;
}
else
{
DbSet.Add(entity);
}
return entity;
}
I've been able to get decent mileage out of it so far without any problems.
I'm using this on EFCore 2.1
You can use this extension method I created to patch our codebase for the migration to EF Core:
public static void AddOrUpdate<T>(this DbSet<T> dbSet, T data) where T : class
{
var t = typeof(T);
PropertyInfo keyField = null;
foreach (var propt in t.GetProperties())
{
var keyAttr = propt.GetCustomAttribute<KeyAttribute>();
if (keyAttr != null)
{
keyField = propt;
break; // assume no composite keys
}
}
if (keyField == null)
{
throw new Exception($"{t.FullName} does not have a KeyAttribute field. Unable to exec AddOrUpdate call.");
}
var keyVal = keyField.GetValue(data);
var dbVal = dbSet.Find(keyVal);
if (dbVal != null)
{
dbSet.Update(data);
return;
}
dbSet.Add(data);
}
This solution is, I think, a simpler solution to this problem, if assuming a base entity class is a legit option. The simplicity comes from your domain entities implementing DomainEntityBase, which alleviates a lot of the complexities in the other suggested solutions.
public static class DbContextExtensions
{
public static void AddOrUpdate<T>(this DbSet<T> dbSet, IEnumerable<T> records)
where T : DomainEntityBase
{
foreach (var data in records)
{
var exists = dbSet.AsNoTracking().Any(x => x.Id == data.Id);
if (exists)
{
dbSet.Update(data);
continue;
}
dbSet.Add(data);
}
}
}
public class DomainEntityBase
{
[Key]
public Guid Id { get; set; }
}
It's waiting to be implemented. See issues #629 & #4526.
Update: according to comments below (unverified) - this feature is finally slated for release in .NET Core 2.1!
I don't understand why people are trying to find the primary key in the other answers. Just pass it when you're calling the method as it's done in EF 6 AddOrUpdate method.
public static TEntity AddOrUpdate<TEntity>(this DbSet<TEntity> dbSet, DbContext context, Func<TEntity, object> identifier, TEntity entity) where TEntity : class
{
TEntity result = dbSet.Find(identifier.Invoke(entity));
if (result != null)
{
context.Entry(result).CurrentValues.SetValues(entity);
dbSet.Update(result);
return result;
}
else
{
dbSet.Add(entity);
return entity;
}
}
and use it later like this:
dbContext.MyModels.AddOrUpdate(dbContext, model => m.Id, new MyModel() { Id = 3 });
Clean and performant.