Pau*_*hal 30 entity-framework-core
我正在使用EntityFramework.Core 7.0.0-rc1-final编写种子方法.
DbSet的AddOrUpdate方法发生了什么变化?
bri*_*lam 22
更新:根据下面的评论(未经验证) - 此功能最终将在.NET Core 2.1中发布!
par*_*gls 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);
}
}
Run Code Online (Sandbox Code Playgroud)
以下 MS Docs 文章Disconnected entity说,只要数据库中的主键列具有自动生成的(例如身份)值,从 EF Core 2.0 开始,仅使用 Update 将充当 AddOrUpdate。
引用文章:
如果知道是否需要插入或更新,则可以适当地使用添加或更新。
但是,如果实体使用自动生成的键值,则 Update 方法可用于两种情况。
Update 方法通常将实体标记为更新,而不是插入。但是,如果实体具有自动生成的键,并且未设置键值,则实体会自动标记为插入。
此行为是在 EF Core 2.0 中引入的。对于早期版本,始终需要明确选择添加或更新。
如果实体未使用自动生成的密钥,则应用程序必须决定是否应插入或更新实体。
我已经在一个测试项目中尝试了这个,并且可以确认 Update 适用于在 EF Core 2.2 中添加和更新实体,并使用自动生成的密钥。
上面链接的断开连接的实体文章还包括自制 InsertOrUpdate 方法的示例代码、EF Core 的早期版本或实体没有自动生成的密钥。示例代码特定于特定实体类,需要修改以使其通用。
我从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;
}
Run Code Online (Sandbox Code Playgroud)到目前为止,我已经能够从中获得不错的里程数,没有任何问题。
我在 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; }
}
Run Code Online (Sandbox Code Playgroud)
有一个扩展方法Upsert。
context.Upsert(new Role { Name = "Employee", NormalizedName = "employee" })
.On(r => new { r.Name })
.Run();
Run Code Online (Sandbox Code Playgroud)
我不明白为什么人们试图在其他答案中找到主键。只需在调用方法时传递它,就像在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;
}
}
Run Code Online (Sandbox Code Playgroud)
稍后像这样使用它:
dbContext.MyModels.AddOrUpdate(dbContext, model => model.Id, new MyModel() { Id = 3 });
Run Code Online (Sandbox Code Playgroud)
干净且高性能。
| 归档时间: |
|
| 查看次数: |
21938 次 |
| 最近记录: |