C# 实体框架直接更新虚拟财产而不创建新记录
下面是一个简单的实体:C# 实体框架直接更新虚拟财产而不创建新记录,c#,entity-framework,lazy-loading,C#,Entity Framework,Lazy Loading,下面是一个简单的实体: 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
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属性上使用了virtual关键字,当我从数据库查询客户实体时,我没有使用Include方法来加载Location属性,我也没有使用任何访问来延迟加载它。因此EF无法跟踪并知道应删除中国
位置实体
我认为我用来更新虚拟财产的方法符合直觉。我想更新一个属性,然后只使用更新指令“entity.xxx=…”,添加在加载“entity.xxx”时被迫使用一些属性或方法调用的访问是不直观的
因此,我正在寻找更好的方法来直接替换实体的虚拟财产。有什么建议吗
解决方案更新 我想找到两个简单的方法 首先,您可以使用(推荐) 您可以使用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));
}
}
}
不完全确定你想要什么,但这是我得到的 您现在获得两个位置的原因是因为您使用了
新位置(“美国”)代码>您实际添加了对新位置的引用(EF不知道另一个客户是否使用了China,并且在该类型的查询中永远不会删除它)
现在如果你说
customer.Location.Country = "America"
中国将被美国覆盖,因为我们现在正在处理一个特定的位置
的属性
阅读关于这个问题的内容,因此有一些额外内容
如果要完全更新位置(新位置(“某个新位置”)
)。那你就这样做
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();
当你说“位置记录”中国“没有被删除:数据库现在有两个位置记录”你的意思是数据库现在有两个客户记录,一个是中国,一个是美国?或者你的意思是现在有两个位置记录,就像你说的那样。如果现在有两个位置记录,这并不一定是错误的。如果您的位置表是一个应该有可能的位置的查找类型表,那么您现在在表中有两个可能的位置,并且您的客户记录现在是正确的,因为它指向美国(American)。@DWright,很抱歉混淆,数据库现在有两个位置记录与一个客户记录关联,是的,customer.Location将指向美国,但在数据库中,每当我使用“customer.Location=new Location()”更新客户位置时,它都会生成一个位置记录。另外,如果虚拟财产是一个集合,则使用“customer.collection=xxx…”将一个新集合插入数据库,下一次你们从数据库中得到客户时,“customer.collection”实际上是两个项目。是的,我有你们的建议,实际上,它工作得很好。但我真正想要的是“替换”而不仅仅是“更新”,如果我想更新customer的集合属性,因为新集合项的计数可能不等于旧集合,此解决方案将不起作用。数据库不替换,这将要求数据库查找数据并对其运行删除SQL查询。没错!所以,当我使用“entity.xxxx=…”编辑答案时,有没有办法让EF生成delete SQL查询。可以在更改位置之前临时修改实体,然后将其删除。(有点未经测试,只是从我的头脑中写出来,但它应该会起作用)是的,它应该会起作用,现在我正在尝试研究一种更简洁的方法来做到这一点,非常感谢:)
var currentLocation = context.Customers.First(x => x.Location.Country == "China").Select(c => c.Location);
context.Entry(currentLocation).OriginalValues.SetValues(newLocation) ;
context.SaveChanges();