1

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

Dinerdo
  • 560
  • 7
  • 27
  • bad copypasta.. sorry. But yes, this is all cut from the actual code. I just meant to rename the variables. Though technically I think that would work if I was passing that into a constructor and explicitly specifying the parameter names for some reason :) – Dinerdo Aug 04 '17 at 18:09
  • No. I don't really know what you're implying. I have a class called Entity. That class has a property called ID. If I don't find a match for the ID I'm looking for, I create a new Entity and set the ID property to the id that I'm looking for. – Dinerdo Aug 04 '17 at 18:24
  • ok - I get it now. I thought you meant to do a named parameter into the constructor... `new Entity(ID:id);` would match the constructor method `public Entity(string ID) { }` – Svek Aug 04 '17 at 18:25

2 Answers2

2

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.

Community
  • 1
  • 1
Svek
  • 12,350
  • 6
  • 38
  • 69
  • The reason I use the LoadWithIncludes method is because I can generically use this for any of the 30 entity types in my database. For that same reason, I was hoping to avoid being super specific (e.g., `db.Subset1...`). when saving entities with navigation properties as well. I'll try refactoring it a bit and see what I can come up with based on this info. Thanks – Dinerdo Aug 04 '17 at 19:13
0

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");
    }
}
AngelBlueSky
  • 575
  • 4
  • 10