.netentity-frameworkcode-firstnavigation-properties

How to rollback Entity Delete without losing Navigational Properties


We are using Entity Framework Code First, and I am running into issues trying to rollback entity changes for Insert, Update, and Delete when we don't want to SaveChanges().

Specifically, I have a datagridview which I am using as a TableEditor, to make changes to some auxiliary tables. The datagridview is bound to DbSet<TEntity>.

My rollback for Update seems to work fine, I just set the currentValues to back to their OriginalValues, and change state to unchanged.

When a record is inserted to the gridview (but no changes saved), it never shows up in the entity class, and I never see it again... So I guess it doesn't make it to the dbSet, and no rollback is needed for this?

But my main problem lies with Delete:

From what I understand, when a record is "deleted" (eg.tableData.Remove(currentItem);), it is simply marked for deletion until SaveChanges is called. So if I change the State from deleted back to unchanged, that should handle the rollback, right?

Well, the record does show back up again, BUT the navigational properties of the record are gone! (ie. the columns containing foreign keys and required relationships to other entities). Why is this??!

Here is what I have so far:

    public void RollbackChanges(DbEntityEntry entry)
    {
        if (entry.State == EntityState.Modified)
        {
            foreach (var propertyName in entry.OriginalValues.PropertyNames)
            {
                entry.CurrentValues[propertyName] = entry.OriginalValues[propertyName];
            }
            entry.State = EntityState.Unchanged;
        }
        else if (entry.State == EntityState.Deleted)
        {
            entry.State = EntityState.Unchanged;
        }
        else if ((entry.State == EntityState.Added) || (entry.State == EntityState.Detached))
        {
            MessageBox.Show("I don't think this ever happens?");
        }
    }

Example usage:

    foreach (var entity in db.CertificationDecisions)
                {
                    DbEntityEntry entry = db.Entry(entity );
                    if (entry.State != EntityState.Unchanged)
                    {
                        RollbackChanges(entry);
                    }
                }

Any ideas why the navigational properties would disappear from the record? (Or what I can do to get them back?)


EDIT: @Chris regarding using Refresh:

I am using a DbContext, so I replaced my rollback methods with this line:

((IObjectContextAdapter)db).ObjectContext.Refresh(RefreshMode.StoreWins, db.CertificationDecisions);

However, this does not seem to reload the context, as the record is still missing... Am I using Refresh wrong?

This sounds like a possible solution to my problem, but I am still wondering why the navigation properties would be removed?


Solution

  • This is not really an answer to my question, but is the alternative that seems to be working for me so far (in case anyone is looking for an answer to this question):

    Basically, I create a new context and pass an entity from the new context to my form. If I cancel the form, I simply dispose of the context, so no changes are saved (no need to rollback). If I save the form, then I save the changes from the new context, but then I need to make my original context aware of the changes.

    So I use .Refresh, which seems to successfully find new entities, but does not remove deleted ones. So then I loop through the entities and compare them to the database values.

    If the database returns 'null', we know the entity has been deleted in the database and needs to be removed from the context. (However, I can't remove it in the loop, as trying to change the contents of a context while enumerating through it will cause an error, so instead I add the entries to a list to remove from the context in a seperate loop afterwards.)

    Below is the code:
    (note: I give no guarantee that this is a good solution, but it seems to be working for me)

                using (FmlaManagementContext auxDb = new FmlaManagementContext())
                {
                    AuxiliaryTableEditor<State> statesTableEditor = new AuxiliaryTableEditor<State>(auxDb.States);
                    if (statesTableEditor.ShowDialog() != DialogResult.OK)
                    {
                        auxDb.Dispose();
                    }
                    else
                    {
                        auxDb.SaveChanges();
                        auxDb.Dispose();
    
                        //refresh to pick up any new
                        ((IObjectContextAdapter)db).ObjectContext.Refresh(RefreshMode.StoreWins, db.States);
    
                        //loop through to remove deleted
                        List<State> nulls = new List<State>();
                        foreach (State state in db.States.Local)
                        {
                            DbEntityEntry entry = db.Entry(state);
                            DbPropertyValues databaseValues = entry.GetDatabaseValues();
                            if (databaseValues == null)
                            {
                                nulls.Add(state); 
                            }
                        }
                        foreach (State state in nulls)
                        {
                            db.States.Remove(state);
                        }
                    }
                }
    

    If I end up changing this or finding problems with it, I will try to remember to come back and update...