Я пишу семенной метод, используя EntityFramework.Core 7.0.0-rc1-final.
Что случилось с методом AddOrUpdate для DbSet?
Я пишу семенной метод, используя EntityFramework.Core 7.0.0-rc1-final.
Что случилось с методом AddOrUpdate для DbSet?
Это ждет, чтобы быть реализованным. Смотрите выпуски # 629 и # 4526.
Обновление: согласно комментариям ниже (не проверено) - эта функция, наконец, намечена к выпуску в .NET Core 2.1!
Я думаю, что это то, что вы хотите.
public static class DbSetExtension
{
public static void AddOrUpdate<T>(this DbSet<T> dbSet, T data) where T : class
{
var context = dbSet.GetContext();
var ids = context.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.Select(x => x.Name);
var t = typeof(T);
List<PropertyInfo> keyFields = new List<PropertyInfo>();
foreach (var propt in t.GetProperties())
{
var keyAttr = ids.Contains(propt.Name);
if (keyAttr)
{
keyFields.Add(propt);
}
}
if (keyFields.Count <= 0)
{
throw new Exception($"{t.FullName} does not have a KeyAttribute field. Unable to exec AddOrUpdate call.");
}
var entities = dbSet.AsNoTracking().ToList();
foreach (var keyField in keyFields)
{
var keyVal = keyField.GetValue(data);
entities = entities.Where(p => p.GetType().GetProperty(keyField.Name).GetValue(p).Equals(keyVal)).ToList();
}
var dbVal = entities.FirstOrDefault();
if (dbVal != null)
{
context.Entry(dbVal).CurrentValues.SetValues(data);
context.Entry(dbVal).State = EntityState.Modified;
return;
}
dbSet.Add(data);
}
public static void AddOrUpdate<T>(this DbSet<T> dbSet, Expression<Func<T, object>> key, T data) where T : class
{
var context = dbSet.GetContext();
var ids = context.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.Select(x => x.Name);
var t = typeof(T);
var keyObject = key.Compile()(data);
PropertyInfo[] keyFields = keyObject.GetType().GetProperties().Select(p=>t.GetProperty(p.Name)).ToArray();
if (keyFields == null)
{
throw new Exception($"{t.FullName} does not have a KeyAttribute field. Unable to exec AddOrUpdate call.");
}
var keyVals = keyFields.Select(p => p.GetValue(data));
var entities = dbSet.AsNoTracking().ToList();
int i = 0;
foreach (var keyVal in keyVals)
{
entities = entities.Where(p => p.GetType().GetProperty(keyFields[i].Name).GetValue(p).Equals(keyVal)).ToList();
i++;
}
if (entities.Any())
{
var dbVal = entities.FirstOrDefault();
var keyAttrs =
data.GetType().GetProperties().Where(p => ids.Contains(p.Name)).ToList();
if (keyAttrs.Any())
{
foreach (var keyAttr in keyAttrs)
{
keyAttr.SetValue(data,
dbVal.GetType()
.GetProperties()
.FirstOrDefault(p => p.Name == keyAttr.Name)
.GetValue(dbVal));
}
context.Entry(dbVal).CurrentValues.SetValues(data);
context.Entry(dbVal).State = EntityState.Modified;
return;
}
}
dbSet.Add(data);
}
}
public static class HackyDbSetGetContextTrick
{
public static DbContext GetContext<TEntity>(this DbSet<TEntity> dbSet)
where TEntity : class
{
return (DbContext)dbSet
.GetType().GetTypeInfo()
.GetField("_context", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(dbSet);
}
}
Это решение, я думаю, более простое решение этой проблемы, если предположить, что базовый класс сущности является законным вариантом. Простота происходит от ваших доменных объектов, реализующих DomainEntityBase, что облегчает многие сложности в других предлагаемых решениях.
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; }
}
Вы можете использовать этот метод расширения, который я создал для исправления нашей базы кода для перехода к 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);
}
Есть метод расширения Upsert.
context.Upsert(new Role { Name = "Employee", NormalizedName = "employee" })
.On(r => new { r.Name })
.Run();
Я начал с ответа Тьяарта и изменил две вещи:
Я включил отслеживание изменений и получал сообщение об ошибке, о котором упоминали другие, что EF уже отслеживает его. Это выполняет поиск уже отслеженного объекта и копирует в него значения из входящего объекта, а затем обновляет исходный объект.
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;
}
До сих пор мне удавалось получить приличный пробег без проблем.
Я использую это на EFCore 2.1
Я нашел хорошее решение, которое позволяет указать свойство, которое должно соответствовать. Однако он не принимает ни одного объекта, а список в каждом вызове. Это может дать вам несколько советов о том, как реализовать лучшую версию, которая работает как хорошо-старая.
(Код не мой)
Ни один из ответов не работал для меня, используя Entity Framework Core (2.0), поэтому вот решение, которое работало для меня:
public static class DbSetExtensions
{
public static void AddOrUpdate<T>(this DbSet<T> dbSet, Expression<Func<T, object>> identifierExpression, params T[] entities) where T : class
{
foreach (var entity in entities)
AddOrUpdate(dbSet, identifierExpression, entity);
}
public static void AddOrUpdate<T>(this DbSet<T> dbSet, Expression<Func<T, object>> 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<Func<T, bool>>(
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<KeyAttribute>() != 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<Expression>();
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);
}
}
}
Вам может потребоваться обновить метод ReplaceParameter(), если у вас есть более сложный identifierExpression. Простые методы доступа к свойствам будут хорошо работать с этой реализацией. например:
context.Projects.AddOrUpdate(x => x.Name, new Project { ... })
context.Projects.AddOrUpdate(x => new { x.Name, x.Description }, new Project { ... })
Затем context.SaveChanges() передаст данные в базу данных.
Основываясь на ответе Tjaart, мне нужно было работать с составным первичным ключом. Вам нужно будет добавить атрибут Key в свойства составного ключа вашей сущности.
public static void AddOrUpdate<T>(this DbSet<T> dbSet, T data) where T : class
{
var t = typeof(T);
var keyProperties = t.GetProperties().Where(o => o.GetCustomAttribute<KeyAttribute>() != null).ToList();
if (!keyProperties.Any())
{
throw new Exception($"{t.FullName} does not any a KeyAttribute fields. Unable to exec AddOrUpdate call.");
}
var keyVals = keyProperties.Select(o => o.GetValue(data)).ToArray();
var dbVal = dbSet.Find(keyVals);
if (dbVal != null)
{
dbSet.Update(data);
return;
}
dbSet.Add(data);
}