Entity Framework update virtual property directly without creating new record

Here is a simple entity:

public class Customer : Entity
{
    public virtual Location Location { get; set; }
}

Now suppose we have a customer already:

var customer = new Customer() {Location = new Location("China")};

and now we want to update his location:

var customer = context.Customers.First(x => x.Location.Country == "China");
customer.Location = new Location("America");
context.SaveChanges();

and now when I look at the database, the location record "China" has not been deleted: the database now has two location records association with one customer record.

The reason for this issue is that I'm using virtual keyword on the Customer.Location property, and when I query the customer entity from database I didn't use Include method to load the Location property, and I also did not use any accesses to lazy load it. So EF can't track and know the China Location entity should be deleted.

I think the approach I used to the update virtual property is in line with intuition. I want update a property, then just use a update instruction "entity.xxx=...", add being forced to use some access of the property or method call while loading "entity.xxx" is not intuitive.

So I am looking for some better way to replace an entity's virtual property directly. Any suggestions?


Solutions update

I'm find two way to do this easy,

First you can use Identifying relation( recommend ).

Another way you can Use ObjectContext.DeleteObject method, below is the example code:

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

And then you can simply write the code like:

customer.UpdateCollection(x => x.Locations, null);

Not completely sure what you want, but this is what i got.

The reason for you now getting two locations are because you use new Location("American"); you actually add a reference to a new location (EF don't know if China is used by another customer, and would never delete it, in that type of query)

Now if you said.

customer.Location.Country = "America"

The China would be overwritten by America , as we are now working with a specific Location 's property.

Read the coments on the question so a little extras

If you want to update the location fully ( new Location("Some new location") ). Then you would do it like this.

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();
链接地址: http://www.djcxy.com/p/33586.html

上一篇: 实体框架Linq更新数据不起作用

下一篇: 实体框架直接更新虚拟财产而不创建新记录