Entity Framework updates virtual property directly without creating a new record

Here's a simple object:

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

      

Now, suppose we already have a client:

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

      

and now we want to update its 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 "China" location record has not been deleted: there are now two location record records in the database with one customer record.

The reason for this problem is that I am using a virtual keyword in the Customer.Location property , and when I request a customer object from the database, I did not use Include to load the Location property, and I also did not use any lazy load accesses. Thus, EF cannot track and know the object China

. The location object must be removed.

I think the approach I took for the virtual update property is intuitive. I want to update a property and then just use the update statement "entity.xxx = ...", adding force some access to the property or method call when loading "entity.xxx" is not intuitive.

So, I'm looking for the best way to replace the entity's virtual property directly. Any suggestions?


Updating solutions

I found two ways to do this easily,

You can use Identify Relationship ( recommend ) first.

Another way to use ObjectContext.DeleteObject method : below is some sample 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 just write code like:

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

      

+3


source to share


2 answers


Not really sure what you want, but this is what I got.

The reason you are currently getting two locations is because you are using new Location("American");

, you are actually adding a link to the new location (EF does not know if China is being used by another client and will never delete it in this type of request)

Now if you said.

customer.Location.Country = "America"

      



China will be overwritten by America as we are now working with a special property Location

.

Read the comments on the matter, so a little extra

If you want to completely update the location ( new Location("Some new location")

). Then you will 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();
}

      

+4


source


Another way to update an object is to use the method 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();

      

+1


source







All Articles