Entity Framework 直接更新虚拟 属性 而不创建新记录
Entity Framework update virtual property directly without creating new record
这是一个简单的实体:
public class Customer : Entity
{
public virtual Location Location { get; set; }
}
现在假设我们已经有了一个客户:
var customer = new Customer() {Location = new Location("China")};
现在我们要更新他的位置:
var customer = context.Customers.First(x => x.Location.Country == "China");
customer.Location = new Location("America");
context.SaveChanges();
现在当我查看数据库时,位置记录 "China" 没有被删除:数据库现在有两个位置记录与一个客户记录相关联。
出现此问题的原因是我在 Customer.Location 属性 上使用了虚拟关键字,并且当我从数据库中查询客户实体时我没有使用 Include 方法来加载 Location 属性,我也没有使用任何访问来延迟加载它。因此 EF 无法跟踪并知道应该删除 China
位置实体。
我认为我用于更新虚拟 属性 的方法符合直觉。我想更新 属性,然后只使用更新指令 "entity.xxx=...",添加在加载 "entity.xxx" 时被迫使用 属性 或方法调用的某些访问是不直观的。
所以我正在寻找一些更好的方法来直接替换实体的虚拟 属性。有什么建议吗?
解决方案更新
我找到了两种简单的方法,
首先可以使用Identifying relation(推荐).
另一种方法可以使用ObjectContext.DeleteObject方法,下面是示例代码:
public static class EFCollectionHelper
{
public static void UpdateCollection<T, TValue>(this T target,
Expression<Func<T, IEnumerable<TValue>>> memberLamda, TValue value)where T : Entity
{
var memberSelectorExpression = (MemberExpression)memberLamda.Body;
var property = (PropertyInfo)memberSelectorExpression.Member;
var oldCollection = memberLamda.Compile()(target);
oldCollection.ClearUp();
property.SetValue(target, value, null);
}
public static void ClearUp<T>(this IEnumerable<T> collection)
{
//Convert your DbContext to IObjectContextAdapter
var objContext = ((IObjectContextAdapter) Program.DbContext).ObjectContext;
for (int i = 0; i < collection.Count(); i++)
{
objContext.DeleteObject(collection.ElementAt(i));
}
}
}
然后您可以简单地编写如下代码:
customer.UpdateCollection(x => x.Locations, null);
不太确定你想要什么,但这就是我得到的。
你现在得到两个位置的原因是因为你使用 new Location("American");
你实际上添加了对新位置的引用(EF 不知道 China 是被另一个客户使用,并且永远不会删除它,在那种类型的查询中)
现在如果你说。
customer.Location.Country = "America"
中国 将被 美国 覆盖,因为我们现在正在处理特定的 Location
的 属性.
阅读问题的评论所以有点额外
如果您想完全更新位置 (new Location("Some new location")
)。那你就这样吧。
Location oldLocation = customer.Location;
Location newLocation = new Location("America");
//Check if the new location country !exist
if(!context.Locations.Any(a=> a.Country == newLocation.Country))
{
//If it don't exist then add it (avoiding the location duplicate)
customer.Location = newLocation;
//If its important to delete the old location then do this
//(important to do after you removed the dependency,
//thats why its after the new location is added)
context.Locations.Remove(oldLocation)
//Finally Save the changes
context.SaveChanges();
}
另一种更新实体的方法是使用 Entry.OriginalValues.SetValues
方法:
var currentLocation = context.Customers.First(x => x.Location.Country == "China").Select(c => c.Location);
context.Entry(currentLocation).OriginalValues.SetValues(newLocation) ;
context.SaveChanges();
这是一个简单的实体:
public class Customer : Entity
{
public virtual Location Location { get; set; }
}
现在假设我们已经有了一个客户:
var customer = new Customer() {Location = new Location("China")};
现在我们要更新他的位置:
var customer = context.Customers.First(x => x.Location.Country == "China");
customer.Location = new Location("America");
context.SaveChanges();
现在当我查看数据库时,位置记录 "China" 没有被删除:数据库现在有两个位置记录与一个客户记录相关联。
出现此问题的原因是我在 Customer.Location 属性 上使用了虚拟关键字,并且当我从数据库中查询客户实体时我没有使用 Include 方法来加载 Location 属性,我也没有使用任何访问来延迟加载它。因此 EF 无法跟踪并知道应该删除 China
位置实体。
我认为我用于更新虚拟 属性 的方法符合直觉。我想更新 属性,然后只使用更新指令 "entity.xxx=...",添加在加载 "entity.xxx" 时被迫使用 属性 或方法调用的某些访问是不直观的。
所以我正在寻找一些更好的方法来直接替换实体的虚拟 属性。有什么建议吗?
解决方案更新
我找到了两种简单的方法,
首先可以使用Identifying relation(推荐).
另一种方法可以使用ObjectContext.DeleteObject方法,下面是示例代码:
public static class EFCollectionHelper
{
public static void UpdateCollection<T, TValue>(this T target,
Expression<Func<T, IEnumerable<TValue>>> memberLamda, TValue value)where T : Entity
{
var memberSelectorExpression = (MemberExpression)memberLamda.Body;
var property = (PropertyInfo)memberSelectorExpression.Member;
var oldCollection = memberLamda.Compile()(target);
oldCollection.ClearUp();
property.SetValue(target, value, null);
}
public static void ClearUp<T>(this IEnumerable<T> collection)
{
//Convert your DbContext to IObjectContextAdapter
var objContext = ((IObjectContextAdapter) Program.DbContext).ObjectContext;
for (int i = 0; i < collection.Count(); i++)
{
objContext.DeleteObject(collection.ElementAt(i));
}
}
}
然后您可以简单地编写如下代码:
customer.UpdateCollection(x => x.Locations, null);
不太确定你想要什么,但这就是我得到的。
你现在得到两个位置的原因是因为你使用 new Location("American");
你实际上添加了对新位置的引用(EF 不知道 China 是被另一个客户使用,并且永远不会删除它,在那种类型的查询中)
现在如果你说。
customer.Location.Country = "America"
中国 将被 美国 覆盖,因为我们现在正在处理特定的 Location
的 属性.
阅读问题的评论所以有点额外
如果您想完全更新位置 (new Location("Some new location")
)。那你就这样吧。
Location oldLocation = customer.Location;
Location newLocation = new Location("America");
//Check if the new location country !exist
if(!context.Locations.Any(a=> a.Country == newLocation.Country))
{
//If it don't exist then add it (avoiding the location duplicate)
customer.Location = newLocation;
//If its important to delete the old location then do this
//(important to do after you removed the dependency,
//thats why its after the new location is added)
context.Locations.Remove(oldLocation)
//Finally Save the changes
context.SaveChanges();
}
另一种更新实体的方法是使用 Entry.OriginalValues.SetValues
方法:
var currentLocation = context.Customers.First(x => x.Location.Country == "China").Select(c => c.Location);
context.Entry(currentLocation).OriginalValues.SetValues(newLocation) ;
context.SaveChanges();