简体   繁体   中英

Entity Framework will only save navigation properties for new entities, not updates

I tried loading the navigation properties first by using Include , and I ensured that the children were loaded.

For new items, I can add a child navigation set with no issues. For updated items, I tried to pass in a completely new collection to overwrite the list, but I read that I needed to work with the loaded collection. Even after clearing the collection and adding to it, I do not see any changes reflected.

For all entities in my project, I am calling a generic method which has these lines and some error handling logic, but no errors are thrown:

Save() method:

context.Set<T>().AddOrUpdate(entities);
context.SaveChanges();

Use:

    var entities = repository.LoadWithIncludes(x =>.Subset1);
    var entity = entities.FirstOrDefault(x => x.ID == id) ?? new Entity{ID = id};

    entity.Subset1.Clear();

    var IDs = GetIDs(id) ?? Array.Empty<int?>();
    foreach (var id in IDs)
       {
           entity.Subset1.Add(new Subset
             {
               ParentId = id,
               Value = part1;
             });
        }

// then I pass the new and updated items into an array and call the Save() method above

The LoadWithIncludes method is taken from this answer: https://stackoverflow.com/a/18805096/7096114

The AddOrUpdate Method

This method is supposed to be used when seeding your database. Which leads me to think you're going to get some poor behaviors.

Let's go start with correcting the way you are updating the context...

public void Edit(Entity entity)
{
    var db = context
        .Include(i => i.Subset1)
        .Where(i => i.ID == id)
        .Single();

    // update entity
    context.Entry(db).CurrentValues.SetValues(entity);

    // delete / clear subset1 from database
    foreach (var dbSubset1 in db.Subset1.ToList())
    {
        if (!entity.Subset1.Any(i => i.ID == dbSubset1.ID))
            context.Subset1.Remove(dbSubset1);
    }

    foreach (var newSubset1 in entity.Subset1)
    {
        var dbSubset1 = db.Subset1.SingleOrDefault(i => i.ID == newSubset1.Id);
        if (dbSubset1 != null)
            // update Subset1
            context.Entry(dbSubset1).CurrentValues.SetValues(newSubset1);
        else
            db.Subset1.Add(newSubset1);
    }

    // save
    db.SaveChanges();
}

There is a great article on how to get this done in a much cleaner way, which involves making a method just to deal with the navigation property in the Microsoft Docs here (look about 3/4 of the way down the article for the example).

Here is also a quote from the docs.

For most relationships, this can be done by updating either foreign key fields or navigation properties. For many-to-many relationships, the Entity Framework doesn't expose the join table directly, so you add and remove entities to and from the appropriate navigation properties.

From the book: Programming Entity Framework-DbContext

Have your Entities implement IObjectWithState

public interface IObjectWithState
{
    ObjectState ObjectState { get; set; }
}


public enum ObjectState
{
    Unchanged,
    Added,
    Modified
    Deleted
}

Person is a DISCONNECTED Entity.

When you change, delete, add... set the Correct State:

Person.Name = "Foo";
Person.ObjectState = ObjectState.Modified;

Phone toBeDeleted = myObject.Phones.FirstOrDefault(x => x.Number == "555");
if(toBeDeleted!=null)
    toBeDeleted.ObjectState = ObjectState.Deleted;

Phone toBModified = myObject.Phones.FirstOrDefault(x => x.Number == "444");
if(toBModified!=null)
{
    toBModified.Number = "333";
    toBeDeleted.ObjectState = ObjectState.Modified;
}


ApplyChanges(myObject);

private void ApplyChanges<TEntity>(TEntity root) where TEntity : class, IObjectWithState
{
    using (var context = new MyContext(connectionString))
    {
        context.Set<TEntity>().Add(root);

        CheckForEntitiesWithoutStateInterface(context);

        foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
        {
            IObjectWithState stateInfo = entry.Entity;
            entry.State = ConvertState(stateInfo.ObjectState);
        }
        context.SaveChanges();
    }
}

private System.Data.Entity.EntityState ConvertState(ObjectState state)
{
    switch (state)
    {
        case ObjectState.Added:
            return System.Data.Entity.EntityState.Added;
        case ObjectState.Modified:
            return System.Data.Entity.EntityState.Modified;
        case ObjectState.Deleted:
            return System.Data.Entity.EntityState.Deleted;
        default:
            return System.Data.Entity.EntityState.Unchanged;
    }
}

private void CheckForEntitiesWithoutStateInterface(MyContext context)
{
    var entitiesWithoutState = context.ChangeTracker.Entries().Where(x => !(x.Entity is IObjectWithState));
    if (entitiesWithoutState.Any())
    {
        throw new NotSupportedException("All entities must implement IObjectWithState");
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM