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();