C# 实体框架不保存修改的子项
这令人沮丧。以下是由database first Entity Framework生成的一对相关对象:C# 实体框架不保存修改的子项,c#,entity-framework,C#,Entity Framework,这令人沮丧。以下是由database first Entity Framework生成的一对相关对象: public partial class DevelopmentType { public DevelopmentType() { this.DefaultCharges = new HashSet<DefaultCharge>(); } public System.Guid RowId { get; set; } publ
public partial class DevelopmentType
{
public DevelopmentType()
{
this.DefaultCharges = new HashSet<DefaultCharge>();
}
public System.Guid RowId { get; set; }
public string Type { get; set; }
public virtual ICollection<DefaultCharge> DefaultCharges { get; set; }
}
public partial class DefaultCharge
{
public System.Guid RowId { get; set; }
public decimal ChargeableRate { get; set; }
public Nullable<System.Guid> DevelopmentType_RowId { get; set; }
public virtual DevelopmentType DevelopmentType { get; set; }
}
对类型的更改会保存,但对应收费率的更改不会保存。我不认为它有很大帮助,但我认为我应该添加它。Context.Entry()
已经在内部“附加”了实体,以便让上下文更改其EntityState
通过调用
Attach()。试着注释掉这一行。据我所知,只有在父对象与试图保存它的上下文相同的情况下,EF才能保存子实体。也就是说,将一个上下文检索到的对象附加到另一个上下文,将允许您保存对父对象的更改,但不保存对子对象的更改。这是一个基于旧搜索的结果,在此基础上我们切换到NHibernate。如果记忆正常,我能够找到一个链接,EF团队成员证实了这一点,并且没有计划改变这种行为。不幸的是,所有与搜索相关的链接都从我的电脑上删除了
由于我不知道您是如何检索案例中的对象的,我不确定这是否与您的案例相关,但请将其放在那里,以防有所帮助
下面是一个关于将分离的对象附加到上下文的链接
问题是,EF不知道已更改的默认费用
通过将DevelopmentType
的状态设置为EntityState.Modified
,EF只知道对象DevelopmentType
已更改。但是,这意味着EF将只更新DevelopmentType
,而不更新其导航属性
一个不是最佳实践的解决方法是迭代当前DevelopmentType
的所有DefaultCharge
,并将实体状态设置为EntityState.Modified
此外,我建议首先将实体附加到上下文,然后更改状态
评论后编辑
当您使用DTO时,我假设您正在通过不同的层或不同的机器传输这些对象
在这种情况下,我建议使用自跟踪实体,因为不可能共享一个上下文。这些实体还保存其当前状态(即新的、更新的、删除的等)。网上有很多关于自跟踪实体的教程
e、 g.这不是每种情况下的解决方法,但我确实发现可以通过更新对象上的外键而不是更新导航属性对象来解决这一问题
例如。。。而不是:
myObject.myProperty = anotherPropertyObject;
试试这个:
myObject.myPropertyID = anotherPropertyObject.ID;
确保该对象在EF的脑海中被标记为已修改(如其他帖子中所述),然后调用您的save方法
至少对我有用!在处理嵌套属性时,这是不可能的,但也许您可以将上下文分解为更小的块,并在多个部分中处理对象,以避免上下文膨胀
祝你好运!:) 图书馆对我处理所有这些复杂问题有很大帮助
您只需要设置要插入/更新/删除的导航属性(使用流畅的语法),Graphdiff就会处理它
注意:项目似乎不再更新,但我使用它已经一年多了,而且非常稳定如果我理解正确,您在更新子字段时会遇到问题。我对子集合字段有问题。我试过这个,它对我有效。
将对象附加到数据库上下文后,应更新所有子集合更改父对象的修改状态并保存对上下文的更改
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
{
/// <summary>
/// The unique identifier for this BaseEntity.
/// </summary>
[Key]
public Guid Id { get; set; }
}
public class BaseEntityComparer : IEqualityComparer<BaseEntity>
{
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<Manager> Managers { get; set; }
}
public class Manager : BaseEntity
{
[Required(AllowEmptyStrings = false)]
[StringLength(256)]
public string Name { get; set; }
public Event Event{ get; set; }
}
公共抽象类BaseEntity
{
///
///此BaseEntity的唯一标识符。
///
[关键]
公共Guid Id{get;set;}
}
公共类BaseEntityComparer:IEqualityComparer
{
公共布尔等于(BaseEntity左、BaseEntity右)
{
if(ReferenceEquals(null,right)){return false;}
返回ReferenceEquals(left,right)| | left.Id.Equals(right.Id);
}
public int GetHashCode(BaseEntity obj)
{
返回obj.Id.GetHashCode();
}
}
公共类事件:BaseEntity
{
[必需(AllowEmptyStrings=false)]
[StringLength(256)]
公共字符串名称{get;set;}
公共哈希集管理器{get;set;}
}
公共类管理器:BaseEntity
{
[必需(AllowEmptyStrings=false)]
[StringLength(256)]
公共字符串名称{get;set;}
公共事件事件{get;set;}
}
DbContext与helper方法:
public class MyDataContext : DbContext
{
public MyDataContext() : base("ConnectionName") { }
//Tables
public DbSet<Event> Events { get; set; }
public DbSet<Manager> Managers { get; set; }
public async Task AddOrUpdate<T>(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<T>().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>(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>(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, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var children = (childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>()).ToList();
foreach (var child in children) { await AddOrUpdate(child); }
var existingChildren = await Set<T>().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, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
var currentChildren = childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>();
var existingChildren = await Set<T>().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<string> 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<object>(propertyName) == null
? null
: dbEntry.OriginalValues.GetValue<object>(propertyName).ToString();
}
return originalValue;
}
private static string CurrentValue(DbEntityEntry dbEntry, string propertyName)
{
string newValue;
try
{
newValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null
? null
: dbEntry.CurrentValues.GetValue<object>(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;
}
}
公共类MyDataContext:DbContext
{
public MyDataContext():base(“ConnectionName”){}
//桌子
公共数据库集事件{get;set;}
公共数据库集管理器{get;set;}
公共异步任务AddOrUpdate(T实体,参数字符串[]ignoreProperties),其中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;
条目(实体)。状态=状态;
var类型=类型(T);
关系经理关系;
var stateManager=((IObjectContextAdapter)this.ObjectContext.ObjectStateManager;
if(stateManager.TryGetRelationshipManager(实体、外部关系))
{
foreach(关系中的变量end.GetAllRelatedEnds())
{
var isForeignKey=end.GetType().GetProperty(“isForeignKey”,BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(end)作为bool?;
var navigationProperty=end.GetType().GetProperty(“navigationProperty
public abstract class BaseEntity
{
/// <summary>
/// The unique identifier for this BaseEntity.
/// </summary>
[Key]
public Guid Id { get; set; }
}
public class BaseEntityComparer : IEqualityComparer<BaseEntity>
{
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<Manager> Managers { get; set; }
}
public class Manager : BaseEntity
{
[Required(AllowEmptyStrings = false)]
[StringLength(256)]
public string Name { get; set; }
public Event Event{ get; set; }
}
public class MyDataContext : DbContext
{
public MyDataContext() : base("ConnectionName") { }
//Tables
public DbSet<Event> Events { get; set; }
public DbSet<Manager> Managers { get; set; }
public async Task AddOrUpdate<T>(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<T>().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>(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>(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, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var children = (childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>()).ToList();
foreach (var child in children) { await AddOrUpdate(child); }
var existingChildren = await Set<T>().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, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
var currentChildren = childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>();
var existingChildren = await Set<T>().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<string> 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<object>(propertyName) == null
? null
: dbEntry.OriginalValues.GetValue<object>(propertyName).ToString();
}
return originalValue;
}
private static string CurrentValue(DbEntityEntry dbEntry, string propertyName)
{
string newValue;
try
{
newValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null
? null
: dbEntry.CurrentValues.GetValue<object>(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<ActionResult> Edit(Event @event)
{
if (!ModelState.IsValid) { return View(@event); }
await _db.AddOrUpdate(@event);
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}