DbSet的AddOrUpdate方法去哪了?
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);
}
}
我从Tjaart的答案开始,做了两个修改:
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;
}
到目前为止,我已经成功地使用它并且没有遇到任何问题。
我正在EFCore 2.1上使用这个。
我认为,如果假设基础实体类是一个合法的选项,那么这个解决方案是一个更简单的解决方案。这种简单性来自于你的领域实体实现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; }
}
_entities.UpsertRange(entities);
,但是没有任何东西被插入到 Sqlite 数据库中。 - Endri我不明白为什么其他回答中的人们要尝试寻找主键。 就像在EF 6的AddOrUpdate方法中一样,在调用该方法时传递它即可。
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;
}
}
然后像这样稍后使用:
dbContext.MyModels.AddOrUpdate(dbContext, model => model.Id, new MyModel() { Id = 3 });
简洁高效。
context.Entry(result).CurrentValues.SetValues(entity);
这个不起作用。当尝试更新主键时失败了。 - Matthias BurgerAddOrUpdate
方法的。确保你正确地传递了标识符。它应该是主键。 - Bamdad 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);
}
find()
之后开始跟踪实体,并且当调用update
时会抛出一个错误,表示它已经在跟踪该实体了。这很尴尬,因为EF可能已经在跟踪该实体,而find可以抓取到它,而不是去数据库。 - Douglas Gaskell(这不是我的代码)
对于我使用的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);
}
}
}
如果您有更复杂的identifierExpression,则可能需要更新ReplaceParameter()方法。这个实现可以很好地处理简单的属性访问器。例如:
context.Projects.AddOrUpdate(x => x.Name, new Project { ... })
context.Projects.AddOrUpdate(x => new { x.Name, x.Description }, new Project { ... })
然后 context.SaveChanges() 将会把数据提交到数据库