I\'m writing a seed method using EntityFramework.Core 7.0.0-rc1-final.
What happened to the AddOrUpdate method of DbSet?
None of the answers worked for me using Entity Framework Core (2.0) so here's the solution that worked for me:
public static class DbSetExtensions
{
public static void AddOrUpdate(this DbSet dbSet, Expression> identifierExpression, params T[] entities) where T : class
{
foreach (var entity in entities)
AddOrUpdate(dbSet, identifierExpression, entity);
}
public static void AddOrUpdate(this DbSet dbSet, Expression> identifierExpression, T entity) where T : class
{
if (identifierExpression == null)
throw new ArgumentNullException(nameof(identifierExpression));
if (entity == null)
throw new ArgumentNullException(nameof(entity));
var keyObject = identifierExpression.Compile()(entity);
var parameter = Expression.Parameter(typeof(T), "p");
var lambda = Expression.Lambda>(
Expression.Equal(
ReplaceParameter(identifierExpression.Body, parameter),
Expression.Constant(keyObject)),
parameter);
var item = dbSet.FirstOrDefault(lambda.Compile());
if (item == null)
{
// easy case
dbSet.Add(entity);
}
else
{
// get Key fields, using KeyAttribute if possible otherwise convention
var dataType = typeof(T);
var keyFields = dataType.GetProperties().Where(p => p.GetCustomAttribute() != null).ToList();
if (!keyFields.Any())
{
string idName = dataType.Name + "Id";
keyFields = dataType.GetProperties().Where(p =>
string.Equals(p.Name, "Id", StringComparison.OrdinalIgnoreCase) ||
string.Equals(p.Name, idName, StringComparison.OrdinalIgnoreCase)).ToList();
}
// update all non key and non collection properties
foreach (var p in typeof(T).GetProperties().Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null))
{
// ignore collections
if (p.PropertyType != typeof(string) && p.PropertyType.GetInterface(nameof(System.Collections.IEnumerable)) != null)
continue;
// ignore ID fields
if (keyFields.Any(x => x.Name == p.Name))
continue;
var existingValue = p.GetValue(entity);
if (!Equals(p.GetValue(item), existingValue))
{
p.SetValue(item, existingValue);
}
}
// also update key values on incoming data item where appropriate
foreach (var idField in keyFields.Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null))
{
var existingValue = idField.GetValue(item);
if (!Equals(idField.GetValue(entity), existingValue))
{
idField.SetValue(entity, existingValue);
}
}
}
}
private static Expression ReplaceParameter(Expression oldExpression, ParameterExpression newParameter)
{
switch (oldExpression.NodeType)
{
case ExpressionType.MemberAccess:
var m = (MemberExpression)oldExpression;
return Expression.MakeMemberAccess(newParameter, m.Member);
case ExpressionType.New:
var newExpression = (NewExpression)oldExpression;
var arguments = new List();
foreach (var a in newExpression.Arguments)
arguments.Add(ReplaceParameter(a, newParameter));
var returnValue = Expression.New(newExpression.Constructor, arguments.ToArray());
return returnValue;
default:
throw new NotSupportedException("Unknown expression type for AddOrUpdate: " + oldExpression.NodeType);
}
}
}
You may need to update the ReplaceParameter() method if you have a more complicated identifierExpression. Simple property accessors will work fine with this implementation. e.g.:
context.Projects.AddOrUpdate(x => x.Name, new Project { ... })
context.Projects.AddOrUpdate(x => new { x.Name, x.Description }, new Project { ... })
Then context.SaveChanges() will commit the data to the database