无法跟踪实体类型的实例,因为已经跟踪了具有相同键的此类型的另一个实例
我有一个服务对象Update
public bool Update(object original, object modified) { var originalClient = (Client)original; var modifiedClient = (Client)modified; _context.Clients.Update(originalClient); //<-- throws the error _context.SaveChanges(); //Variance checking and logging of changes between the modified and original }
这是我从以下方法调用此方法的地方:
public IActionResult Update(DetailViewModel vm) { var originalClient = (Client)_service.GetAsNoTracking(vm.ClientId); var modifiedClient = (Client)_service.Fetch(vm.ClientId.ToString()); // Changing the modifiedClient here _service.Update(originalClient, modifiedClient); }
这是GetAsNotTracking
方法:
public Client GetAsNoTracking(long id) { return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault(); }
Fetch
方法:
public object Fetch(string id) { long fetchId; long.TryParse(id, out fetchId); return GetClientQueryableObject(fetchId).FirstOrDefault(); }
GetClientQueryableObject
:
private Microsoft.Data.Entity.Query.IIncludableQueryable GetClientQueryableObject(long searchId) { return _context.Clients .Where(x => x.Id == searchId) .Include(x => x.Opportunities) .ThenInclude(x => x.BusinessUnit) .Include(x => x.Opportunities) .ThenInclude(x => x.Probability) .Include(x => x.Industry) .Include(x => x.Activities) .ThenInclude(x => x.User) .Include(x => x.Activities) .ThenInclude(x => x.ActivityType); }
有任何想法吗?
我查看了以下文章/讨论。 无济于事: ASP.NET GitHub问题3839
更新:
以下是GetAsNoTracking
的更改:
public Client GetAsNoTracking(long id) { return GetClientQueryableObjectAsNoTracking(id).FirstOrDefault(); }
GetClientQueryableObjectAsNoTracking
:
private IQueryable GetClientQueryableObjectAsNoTracking(long searchId) { return _context.Clients .Where(x => x.Id == searchId) .Include(x => x.Opportunities) .ThenInclude(x => x.BusinessUnit) .AsNoTracking() .Include(x => x.Opportunities) .ThenInclude(x => x.Probability) .AsNoTracking() .Include(x => x.Industry) .AsNoTracking() .Include(x => x.Activities) .ThenInclude(x => x.User) .AsNoTracking() .Include(x => x.Activities) .ThenInclude(x => x.ActivityType) .AsNoTracking(); }
如果不覆盖EF轨道系统,您还可以在保存之前分离“本地”条目并附加更新的条目:
// var local = _context.Set() .Local .FirstOrDefault(entry => entry.Id.Equals(entryId)); // check if local is not null if (!local.IsNull()) // I'm using a extension method { // detach _context.Entry(local).State = EntityState.Detached; } // set Modified flag in your entry _context.Entry(entryToUpdate).State = EntityState.Modified; // save _context.SaveChanges();
更新:为避免代码冗余,您可以执行扩展方法:
public static void DetachLocal(this DbContext context, T t, string entryId) where T : class, IIdentifier { var local = context.Set () .Local .FirstOrDefault(entry => entry.Id.Equals(entryId)); if (!local.IsNull()) { context.Entry(local).State = EntityState.Detached; } context.Entry(t).State = EntityState.Modified; }
我的IIdentifier
接口只有一个Id
字符串属性。
无论您的实体是什么,您都可以在您的上下文中使用此方法:
_context.DetachLocal(tmodel, id); _context.SaveChanges();
听起来你真的只是想跟踪对模型所做的更改,而不是在内存中实际保留未跟踪的模型。 我是否可以建议一种可以完全解决问题的替代方法?
EF将自动跟踪您的变化。 如何利用内置的逻辑?
在DbContext中使用Ovverride SaveChanges()
。
public override int SaveChanges() { foreach (var entry in ChangeTracker.Entries()) { if (entry.State == EntityState.Modified) { // Get the changed values. var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties(); var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues; foreach (var propName in modifiedProps) { var newValue = currentValues[propName]; //log changes } } } return base.SaveChanges(); }
很好的例子可以在这里找到:
entity framework6:审计/跟踪变更
使用MVC和entity framework实现审核日志/更改历史记录
编辑: Client
可以轻松更改为界面。 让我们说ITrackableEntity
。 这样,您可以集中逻辑并自动将所有更改记录到实现特定接口的所有实体。 界面本身没有任何特定属性。
public override int SaveChanges() { foreach (var entry in ChangeTracker.Entries()) { if (entry.State == EntityState.Modified) { // Same code as example above. } } return base.SaveChanges(); }
另外,看看eranga的一个很好的建议是订阅而不是实际覆盖SaveChanges()。