entity framework不保存修改后的子项

这令人沮丧。 这是一对相关对象,由数据库优先entity framework生成:

public partial class DevelopmentType { public DevelopmentType() { this.DefaultCharges = new HashSet(); } public System.Guid RowId { get; set; } public string Type { get; set; } public virtual ICollection DefaultCharges { get; set; } } public partial class DefaultCharge { public System.Guid RowId { get; set; } public decimal ChargeableRate { get; set; } public Nullable DevelopmentType_RowId { get; set; } public virtual DevelopmentType DevelopmentType { get; set; } } 

这是我要调用以保存DevelopmentType的代码 – 它涉及到automapper,因为我们将实体对象与DTO区分开来:

  public void SaveDevelopmentType(DevelopmentType_dto dt) { Entities.DevelopmentType mappedDevType = Mapper.Map(dt); _Context.Entry(mappedDevType).State = System.Data.EntityState.Modified; _Context.DevelopmentTypes.Attach(mappedDevType); _Context.SaveChanges(); } 

在我的用户界面中,最常见的操作是让用户查看DevelopmentTypes列表并更新其DefaultCharge。 因此,当我使用上面的代码测试它时,它运行没有错误,但实际上没有任何变化。

如果我在调试器中暂停,很明显已将更改的DefaultCharge传递给函数,并且它已附加到要保存的DevelopmentType。

单步执行它,如果我在visual studio中手动更改值,它保存更新的值。 这更令人困惑。

使用SQL Server Profiler监视数据库显示为父对象发出更新命令,而不是为任何附加对象发出更新命令。

我在其他地方有其他类似的代码,按预期运行。 我在这做错了什么?

编辑:

我发现如果你在调用SaveDevelopmentType之前这样做:

  using (TransactionScope scope = new TransactionScope()) { dt.Type = "Test1"; dt.DefaultCharges.First().ChargeableRate = 99; _CILRepository.SaveDevelopmentType(dt); scope.Complete(); } 

对Type的更改保存,但对ChargeableRate的更改不会。 我认为它没有大的帮助,但我想我会加上它。

问题是,EF不知道更改的DefaultCharges。

通过将DevelopmentType的State设置为EntityState.Modified ,EF仅知道对象DevelopmentType已更改。 但是,这意味着EF只会更新DevelopmentType而不会更新它的导航属性。

解决方法 – 这不是最佳实践 – 将迭代当前DevelopmentType所有DefaultCharge并将实体状态设置为EntityState.Modified

此外,我建议首先将实体附加到上下文,然后更改状态。

评论后编辑

当您使用DTO时,我想您正在通过不同的层或不同的机器传输这些对象。

在这种情况下,我建议使用自我跟踪实体,因为无法共享一个上下文。 这些实体还保持其当前状态(即,新的,更新的,删除的等)。 关于自我跟踪实体,网上有很多教程。

例如MSDN – 使用自我跟踪实体

Context.Entry()已在内部“附加”实体,以使上下文更改其EntityState

通过调用Attach()您将EntityState UnchangedUnchanged 。 尝试注释掉这一行。

据我所知,只有在使用尝试保存它的相同Context检索父对象时,EF才能保存子实体。 这是将一个上下文检索到的对象附加到另一个上下文,允许您保存对父对象而不是子对象的更改。 这是我们切换到NHibernate的旧搜索的结果。 如果内存服务正常,我能够找到EF团队成员确认此链接的链接,并且没有计划更改此行为。 不幸的是,所有与该搜索相关的链接都已从我的PC中删除。

由于我不知道你是如何检索你的情况下的对象,我不确定这与你的情况有关,但是把它放在那里以防万一它有所帮助。

这是一个将分离的对象附加到上下文的链接。

http://www.codeproject.com/Articles/576330/Attaching-detached-POCO-to-EF-DbContext-simple-and

Graphdiff库对我来说是一个很好的帮助来处理所有这些复杂性。

您只需要设置要插入/更新/删除的导航属性(使用流利的语法),Graphdiff将负责处理

注意:似乎该项目不再更新,但我使用它已超过一年,而且非常稳定

这不是针对每种情况的解决方法,但我确实发现您可以通过更新对象上的外键而不是更新导航属性对象来解决此问题。

例如……而不是:

 myObject.myProperty = anotherPropertyObject; 

试试这个:

 myObject.myPropertyID = anotherPropertyObject.ID; 

确保在EF的脑海中将对象标记为已修改(如其他post中所述),然后调用save方法。

至少为我工作过! 使用嵌套属性时,这将是一个禁忌,但也许您可以将您的上下文分解为更小的块并在多个部分中处理对象以避免上下文膨胀。

祝好运! 🙂

如果我正确理解了这个问题,那么更新子字段时会遇到问题。 我遇到了子集合字段的问题。 我试过这个,它对我有用。 在将对象附加到数据库上下文后,应更新所有子集合,更改父对象的已修改状态并将更改保存到上下文。

 Database.Products.Attach(argProduct); argProduct.Categories = Database.Categories.Where(x => ListCategories.Contains(x.CategoryId)).ToList(); Database.Entry(argProduct).State = EntityState.Modified; Database.SaveChanges(); 

我创建了一个帮助方法来解决这个问题。


考虑一下:

 public abstract class BaseEntity { ///  /// The unique identifier for this BaseEntity. ///  [Key] public Guid Id { get; set; } } public class BaseEntityComparer : IEqualityComparer { public bool Equals(BaseEntity left, BaseEntity right) { if (ReferenceEquals(null, right)) { return false; } return ReferenceEquals(left, right) || left.Id.Equals(right.Id); } public int GetHashCode(BaseEntity obj) { return obj.Id.GetHashCode(); } } public class Event : BaseEntity { [Required(AllowEmptyStrings = false)] [StringLength(256)] public string Name { get; set; } public HashSet Managers { get; set; } } public class Manager : BaseEntity { [Required(AllowEmptyStrings = false)] [StringLength(256)] public string Name { get; set; } public Event Event{ get; set; } } 

使用辅助方法的DbContext:

 public class MyDataContext : DbContext { public MyDataContext() : base("ConnectionName") { } //Tables public DbSet Events { get; set; } public DbSet Managers { get; set; } public async Task AddOrUpdate(T entity, params string[] ignoreProperties) where T : BaseEntity { if (entity == null || Entry(entity).State == EntityState.Added || Entry(entity).State == EntityState.Modified) { return; } var state = await Set().AnyAsync(x => x.Id == entity.Id) ? EntityState.Modified : EntityState.Added; Entry(entity).State = state; var type = typeof(T); RelationshipManager relationship; var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager; if (stateManager.TryGetRelationshipManager(entity, out relationship)) { foreach (var end in relationship.GetAllRelatedEnds()) { var isForeignKey = end.GetType().GetProperty("IsForeignKey", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(end) as bool?; var navigationProperty = end.GetType().GetProperty("NavigationProperty", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(end); var propertyName = navigationProperty?.GetType().GetProperty("Identity", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(navigationProperty) as string; if (string.IsNullOrWhiteSpace(propertyName) || ignoreProperties.Contains(propertyName)) { continue; } var property = type.GetProperty(propertyName); if (property == null) { continue; } if (end is IEnumerable) { await UpdateChildrenInternal(entity, property, isForeignKey == true); } else { await AddOrUpdateInternal(entity, property, ignoreProperties); } } } if (state == EntityState.Modified) { Entry(entity).OriginalValues.SetValues(await Entry(entity).GetDatabaseValuesAsync()); Entry(entity).State = GetChangedProperties(Entry(entity)).Any() ? state : EntityState.Unchanged; } } private async Task AddOrUpdateInternal(T entity, PropertyInfo property, params string[] ignoreProperties) { var method = typeof(EasementDataContext).GetMethod("AddOrUpdate"); var generic = method.MakeGenericMethod(property.PropertyType); await (Task)generic.Invoke(this, new[] { property.GetValue(entity), ignoreProperties }); } private async Task UpdateChildrenInternal(T entity, PropertyInfo property, bool isForeignKey) { var type = typeof(T); var method = isForeignKey ? typeof(EasementDataContext).GetMethod("UpdateForeignChildren") : typeof(EasementDataContext).GetMethod("UpdateChildren"); var objType = property.PropertyType.GetGenericArguments()[0]; var enumerable = typeof(IEnumerable<>).MakeGenericType(objType); var param = Expression.Parameter(type, "x"); var body = Expression.Property(param, property); var lambda = Expression.Lambda(Expression.Convert(body, enumerable), property.Name, new[] { param }); var generic = method.MakeGenericMethod(type, objType); await (Task)generic.Invoke(this, new object[] { entity, lambda, null }); } public async Task UpdateForeignChildren(T parent, Expression>> childSelector, IEqualityComparer comparer = null) where T : BaseEntity where TProperty : BaseEntity { var children = (childSelector.Invoke(parent) ?? Enumerable.Empty()).ToList(); foreach (var child in children) { await AddOrUpdate(child); } var existingChildren = await Set().Where(x => x.Id == parent.Id).SelectMany(childSelector).AsNoTracking().ToListAsync(); if (comparer == null) { comparer = new BaseEntityComparer(); } foreach (var child in existingChildren.Except(children, comparer)) { Entry(child).State = EntityState.Deleted; } } public async Task UpdateChildren(T parent, Expression>> childSelector, IEqualityComparer comparer = null) where T : BaseEntity where TProperty : BaseEntity { var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager; var currentChildren = childSelector.Invoke(parent) ?? Enumerable.Empty(); var existingChildren = await Set().Where(x => x.Id == parent.Id).SelectMany(childSelector).AsNoTracking().ToListAsync(); if (comparer == null) { comparer = new BaseEntityComparer(); } var addedChildren = currentChildren.Except(existingChildren, comparer).AsEnumerable(); var deletedChildren = existingChildren.Except(currentChildren, comparer).AsEnumerable(); foreach (var child in currentChildren) { await AddOrUpdate(child); } foreach (var child in addedChildren) { stateManager.ChangeRelationshipState(parent, child, childSelector.Name, EntityState.Added); } foreach (var child in deletedChildren) { Entry(child).State = EntityState.Unchanged; stateManager.ChangeRelationshipState(parent, child, childSelector.Name, EntityState.Deleted); } } public static IEnumerable GetChangedProperties(DbEntityEntry dbEntry) { var propertyNames = dbEntry.State == EntityState.Added ? dbEntry.CurrentValues.PropertyNames : dbEntry.OriginalValues.PropertyNames; foreach (var propertyName in propertyNames) { if (IsValueChanged(dbEntry, propertyName)) { yield return propertyName; } } } private static bool IsValueChanged(DbEntityEntry dbEntry, string propertyName) { return !Equals(OriginalValue(dbEntry, propertyName), CurrentValue(dbEntry, propertyName)); } private static string OriginalValue(DbEntityEntry dbEntry, string propertyName) { string originalValue = null; if (dbEntry.State == EntityState.Modified) { originalValue = dbEntry.OriginalValues.GetValue(propertyName) == null ? null : dbEntry.OriginalValues.GetValue(propertyName).ToString(); } return originalValue; } private static string CurrentValue(DbEntityEntry dbEntry, string propertyName) { string newValue; try { newValue = dbEntry.CurrentValues.GetValue(propertyName) == null ? null : dbEntry.CurrentValues.GetValue(propertyName).ToString(); } catch (InvalidOperationException) // It will be invalid operation when its in deleted state. in that case, new value should be null { newValue = null; } return newValue; } } 

然后我这样称呼它

  // POST: Admin/Events/Edit/5 [HttpPost] [ValidateAntiForgeryToken] public async Task Edit(Event @event) { if (!ModelState.IsValid) { return View(@event); } await _db.AddOrUpdate(@event); await _db.SaveChangesAsync(); return RedirectToAction("Index"); }