Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework not saving modified children

Frustrating, this. Here's a pair of related objects, as generated by database-first Entity Framework:

public partial class DevelopmentType
{
    public DevelopmentType()
    {
        this.DefaultCharges = new HashSet<DefaultCharge>();
    }

    public System.Guid RowId { get; set; }
    public string Type { get; set; }

    public virtual ICollection<DefaultCharge> DefaultCharges { get; set; }
}

public partial class DefaultCharge
{
    public System.Guid RowId { get; set; }
    public decimal ChargeableRate { get; set; }
    public Nullable<System.Guid> DevelopmentType_RowId { get; set; }

    public virtual DevelopmentType DevelopmentType { get; set; }
}

Here's the code that I'm calling to save a DevelopmentType - it involves automapper since we differentiate entity objects from DTOs:

    public void SaveDevelopmentType(DevelopmentType_dto dt)
    {
        Entities.DevelopmentType mappedDevType = Mapper.Map<DevelopmentType_dto, Entities.DevelopmentType>(dt);
        _Context.Entry(mappedDevType).State = System.Data.EntityState.Modified;

        _Context.DevelopmentTypes.Attach(mappedDevType);
        _Context.SaveChanges();
    }

In my user interface, the most common operation will be for a user to look at a list of DevelopmentTypes and update their DefaultCharge. So when I test this using the above code, it runs without error, but nothing actually changes.

If I pause in the debugger it's clear that the changed DefaultCharge is being passed into the function, and that it's attached to the DevelopmentType to be saved.

Stepping through it, if I change the value manually inside visual studio, it does save the updated value. Which is just even more confusing.

Monitoring the database with SQL Server Profiler reveals that update commands are issued only for the parent object and not for any attached objects.

I have other similar code elsewhere that functions as expected. What am I doing wrong here?

EDIT:

I have discovered that if you do this prior to the call to SaveDevelopmentType:

        using (TransactionScope scope = new TransactionScope())
        {
            dt.Type = "Test1";
            dt.DefaultCharges.First().ChargeableRate = 99;
            _CILRepository.SaveDevelopmentType(dt);
            scope.Complete();
        }

The change to Type saves, but the change to ChargeableRate does not. I don't think it helps, massively, but thought I'd add it.

like image 530
Bob Tway Avatar asked Aug 05 '13 09:08

Bob Tway


3 Answers

The problem is, that EF is not aware of the changed DefaultCharges.

By setting the State of the DevelopmentType to EntityState.Modified, EF only knows that the object DevelopmentType has been changed. However, this means that EF will only update DevelopmentType but not it's navigation properties.

A workaround - which isn't best practice - would be to iterate over all DefaultCharge of the current DevelopmentType and set the entity state to EntityState.Modified.

Additionally I would recommend to attach the entity to the context first, and change the state afterwards.

EDIT after comment

As you are using DTOs I suppose you are transfering these objects either through different layers or different machines.

In this case I would recommend to use self tracking entities, because it is not possible to share one context. These entities additionally holds their current state (ie. new, updated, deleted etc). There are many tutorials on the net about self tracking entities.

e.g. MSDN - Working with Self-Tracking Entities

like image 157
boindiil Avatar answered Nov 09 '22 13:11

boindiil


As far as I know EF can save child entities only if the parent object was retrieved with the same Context that is trying to save it. That is attaching an object that was retrieved by one context to another context, will allow you to save changes to parent objects but not children. This was the result of a on old search based on which we switched to NHibernate. If memory serves correctly I was able to find a link where EF team member(s) confirmed this and that there WAS no plan to change this behavior. Unfortunately all links related to that search have been erased from my PC since.

As I am not aware of how you are retrieving the objects in your case, I am not sure this is relevant to your case, but put it out there just in case it helps.

Here is a link on attaching detached objects to a context.

http://www.codeproject.com/Articles/576330/Attaching-detached-POCO-to-EF-DbContext-simple-and

like image 5
JTMon Avatar answered Nov 09 '22 14:11

JTMon


Context.Entry() already "Attaches" the Entity internally in order to have the context change its EntityState.

By calling Attach() you're changing the EntityState back to Unchanged. Try to comment out this line.

like image 5
haim770 Avatar answered Nov 09 '22 15:11

haim770