我正在使用EntityFramework.Core 7.0.0-rc1-final编写种子方法。
DbSet的AddOrUpdate方法发生了什么变化?
答案 0 :(得分:20)
答案 1 :(得分:10)
我认为这就是你想要的。
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);
}
}
答案 2 :(得分:4)
如果假设基本实体类是合法选项,我认为这个解决方案是一个更简单的解决方案。简单性来自您实现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; }
}
答案 3 :(得分:3)
您可以使用我创建的此扩展方法来修补我们的代码库,以便迁移到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);
}
答案 4 :(得分:3)
有一种扩展方法Upsert。
List<String> mData; // your source data
// sort your data ascending order
Collections.sort(mData);
// use your adapter to update your ListView
mArrayAdapter.notifyDataSetChanged();
答案 5 :(得分:3)
我从Tjaart的答案开始,并修改了两件事:
我启用了更改跟踪,并收到其他人提到的有关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上使用它
答案 6 :(得分:1)
我找到了一个很好的解决方案,允许您指定应该匹配的属性。但是,它不需要单个实体,而是每个调用中的列表。它可能会给你一些提示,告诉你如何实现一个更好的版本,就像好的版本一样。
(代码不是我的)
答案 7 :(得分:1)
我不明白为什么人们试图在其他答案中找到主键。 就像在 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 => m.Id, new MyModel() { Id = 3 });
清洁和高效。
答案 8 :(得分:0)
使用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()方法。简单的属性访问器将适用于此实现。 e.g:
context.Projects.AddOrUpdate(x => x.Name, new Project { ... })
context.Projects.AddOrUpdate(x => new { x.Name, x.Description }, new Project { ... })
然后context.SaveChanges()将数据提交到数据库
答案 9 :(得分:0)
以Tjaart's answer为基础,我需要它来使用复合主键。您需要将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);
}
答案 10 :(得分:0)
以下MS Docs文章Disconnected entities表示,只要数据库中的主键列具有自动生成的字符(例如,身份),从EF Core 2.0起,仅使用更新即可充当AddOrUpdate。 )的值。
引用该文章:
如果知道是否需要插入或更新,则 可以适当使用添加或更新。
但是,如果实体使用自动生成的键值,则更新 两种情况下都可以使用该方法。
Update方法通常将实体标记为要更新,而不是插入。 但是,如果实体具有自动生成的键,并且没有键值具有 设置后,该实体会自动标记为插入。
此行为是EF Core 2.0中引入的。对于较早的版本 明确选择添加或更新总是必要的。
如果实体未使用自动生成的密钥,则该应用程序 必须决定是否应插入或更新实体。
我已经在测试项目中进行了尝试,可以确认Update是否可以使用自动生成的密钥在EF Core 2.2中添加和更新实体。
上面链接的断开连接的实体文章还包括用于自制InsertOrUpdate方法,EF Core的早期版本或该实体没有自动生成的密钥的示例代码。该示例代码特定于特定的实体类,需要进行修改以使其通用化。
答案 11 :(得分:0)
这是我基于该线程其他解决方案的解决方案。
public object PrimaryKeyValues<TEntity>(TEntity entity)
{
var properties = _appDb.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey().Properties;
var entry = _appDb.Entry(entity);
var values = properties?.Select(p => entry.Property(p.Name).CurrentValue);
if (values?.Count() == 1)
return values.Single();
return values?.ToArray();
}
public async Task<TEntity> AddOrUpdateAsync<TEntity>(TEntity entity) where TEntity : class
{
var pkValue = PrimaryKeyValues(entity);
if (pkValue == null)
{
throw new Exception($"{typeof(TEntity).FullName} does not have a primary key specified. Unable to exec AddOrUpdateAsync call.");
}
if ((await _appDb.FindAsync(typeof(TEntity), pkValue)) is TEntity dbEntry)
{
_appDb.Entry(dbEntry).CurrentValues.SetValues(entity);
_appDb.Update(dbEntry);
entity = dbEntry;
}
else
{
_appDb.Add(entity);
}
return entity;
}
完整的解决方案。 不支持作为阴影属性的键
DbContextExtensions.cs
// FIND ALL
// ===============================================================
/// <summary>
/// Tries to get all entities by their primary keys. Return all/partial/empty array of database entities.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="dbContext"></param>
/// <param name="args"></param>
/// <returns></returns>
public static async Task<TEntity[]> FindAllAsync<TEntity>(this DbContext dbContext, IEnumerable<TEntity> args) where TEntity : class
{
return await Task.Run(() => {
var dbParameter = Expression.Parameter(typeof(TEntity), typeof(TEntity).Name);
var properties = dbContext.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey()?.Properties;
if (properties == null)
throw new ArgumentException($"{typeof(TEntity).FullName} does not have a primary key specified.");
if (args == null)
throw new ArgumentNullException($"Entities to find argument cannot be null");
if (!args.Any())
return Enumerable.Empty<TEntity>().ToArray();
var aggregatedExpression = args.Select(entity =>
{
var entry = dbContext.Entry(entity);
return properties.Select(p =>
{
var dbProp = dbParameter.Type.GetProperty(p.Name);
var left = Expression.Property(dbParameter, dbProp);
var argValue = entry.Property(p.Name).CurrentValue;
var right = Expression.Constant(argValue);
return Expression.Equal(left, right);
})
.Aggregate((acc, next) => Expression.And(acc, next));
})
.Aggregate((acc, next) => Expression.OrElse(acc, next));
var whereMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Where" && m.GetParameters().Length == 2);
MethodInfo genericWhereMethod = whereMethod.MakeGenericMethod(typeof(TEntity));
var whereLambda = Expression.Lambda(aggregatedExpression, dbParameter);
var set = dbContext.Set<TEntity>();
var func = whereLambda.Compile();
var result = genericWhereMethod.Invoke(null, new object[] { set, func}) as IEnumerable<TEntity>;
return result.ToArray();
});
}
// ADD OR UPDATE - RANGE - ASYNC
// ===============================================================
/// <summary>
/// Foreach entity in a range, adds it when it doesn't exist otherwise updates it. Bases decision on Pk.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="dbContext"></param>
/// <param name="entities"></param>
/// <returns></returns>
public static async Task<(int AddedCount, int UpdatedCount)> AddOrUpdateRangeAsync<TEntity>(this DbContext dbContext, IEnumerable<TEntity> entities) where TEntity : class
{
var existingEntitiesHashes = (await dbContext.FindAllAsync(entities)).Select(x =>
{
dbContext.Entry(x).State = EntityState.Detached;
return dbContext.PrimaryKeyHash(x);
});
var (True, False) = entities.DivideOn(x => existingEntitiesHashes.Contains(dbContext.PrimaryKeyHash(x)));
dbContext.UpdateRange(True);
dbContext.AddRange(False);
return (AddedCount: False.Count(), UpdatedCount: True.Count());
}
// ADD OR UPDATE - ASYNC
// ===============================================================
/// <summary>
/// Adds when not existing otherwise updates an entity. Bases decision on Pk.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="dbContext"></param>
/// <param name="entity"></param>
/// <returns></returns>
public static async Task AddOrUpdateAsync<TEntity>(this DbContext dbContext, TEntity entity) where TEntity : class
=> await dbContext.AddOrUpdateRangeAsync(new TEntity[] { entity });
// PK HASH
// ===============================================================
/// <summary>
/// Returns the compounded hash string of all primary keys of the entity
/// </summary>
/// <typeparam name="TTarget"></typeparam>
/// <param name="dbContext"></param>
/// <param name="entity"></param>
/// <returns></returns>
public static string PrimaryKeyHash<TTarget>(this DbContext dbContext, TTarget entity)
{
var properties = dbContext.Model.FindEntityType(typeof(TTarget)).FindPrimaryKey().Properties;
var entry = dbContext.Entry(entity);
return properties.Select(p => Crypto.HashGUID(entry.Property(p.Name).CurrentValue))
.Aggregate(string.Empty, (acc, next) => acc += next);
}
Crypto.cs
public class Crypto
{
/// <summary>
/// RETURNS A HASH AS A GUID BASED ON OBJECT.TOSTRING()
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static string HashGUID(object obj)
{
string text = string.Empty;
MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();
byte[] bytes = new UTF8Encoding().GetBytes(obj.ToString());
byte[] array = md5CryptoServiceProvider.ComputeHash(bytes);
for (int i = 0; i < array.Length; i++)
{
text += Convert.ToString(array[i], 16).PadLeft(2, '0');
}
md5CryptoServiceProvider.Clear();
return text.PadLeft(32, '0');
}
}
IEnumerableExtensions.cs
/// <summary>
/// Divides into two based on predicate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static (IEnumerable<T> True, IEnumerable<T> False) DivideOn<T>(this IEnumerable<T> source, Func<T, bool> predicate)
=> (source.Where(x => predicate(x)), source.Where(x => !predicate(x)));
注释(if)ノ✲゚ 。⋆