0

I'm having Issues with entity framework using transactions and ASP MVC, for example, if at some point during the transaction an exception is thrown, the DbContext remains outdated because the rollback did not return the navigation collection to its original state.

Entity Framework version: 6.1

Related model:

public Action {
    public int Id;
    public string property;
}

Main Model:

public Model1 {
   public int Id;
   public virtual ICollection<Action> Actions
}

and the try/catch block to rollback

//There is only one instance of DbContext, and is shared with all controllers and models
DbContext context = GetSingletonContext();
DbContextTransaction transaction = context.Database.BeginTransaction();    

Model1 instance = null;
try
{
    instance = context.Model1.Find(31);
    instance.Actions.Remove(instance.Actions.First());
    throw new ExceptionOfAnyKind();
    context.SaveChanges();
    transaction.Commit();
}
catch (Exception)
{
    transaction.Rollback();
    throw;
}

I did something like this to 'clear' the entity tracker, so the next time Entity Framework theorically will use the database, but i'm not sure if this can lead to some kind of issues in other actions of controllers that should be being used at the same time:

var entities = (from entity in entityManager.ChangeTracker.Entries() select entity).ToList();
foreach (var item in entities)
{
    item.State = EntityState.Detached;
}

My question is, how can i ensure that even when an error is thrown, the DbContext is always updated or returned to the previous state, or if there is a way to force the reload of navigation collections of a single model instance.

already tried this:

var entry = context.Entry<Model1>(instance).Collection("Actions");
entry.IsLoaded = false;
entry.Load();

and this one just reload properties of the model, but no the navigation properties

var objectContext = ((IObjectContextAdapter)context).ObjectContext;
objectContext.Refresh(RefreshMode.StoreWins, instance);

same as previous:

var entry = context.Entry<Model1>(instance);
entry.Reload();

When using AsNoTracking, the instance has the correct values, but cannot be updated or deleted until attached to the context, and if already an entity with the same PK an exception is thrown

Cristhian J.
  • 3
  • 1
  • 5
  • Unrelated but you need to close your transaction properly, either call close or wrap that in a using – johnny 5 Jun 26 '17 at 17:13
  • you write `//There is only one instance of DbContext, and is shared with all controllers and models`, imho, this is the worst pattern. You will finish to have all your data in memory. Scope you context: one per query. – tschmit007 Jun 26 '17 at 20:22
  • you can use the below link for reference: https://stackoverflow.com/questions/22486489/entity-framework-6-transaction-rollback – Tapan Khimani Jun 27 '17 at 07:24
  • @tschmit007, you are right, i was trying to avoid the exception “An entity object cannot be referenced by multiple instances of IEntityChangeTracker.”, and worked, but in the process, I forgot about the cache of Entity Framework – Cristhian J. Jun 27 '17 at 15:28
  • @TapanKhimani I will check, I wrote a generic class to wrap common methods update, create and delete, it works, but the only issue is the cache – Cristhian J. Jun 27 '17 at 15:29

1 Answers1

1

You can use code transactions, instead of database transactions. And using using statements to dispose of dbcontext.

using (TransactionScope scope = new TransactionScope())
{
    using (MyDBContext db = new MyDBContext())
    {
       //...
       db.SaveChanges()
    }
       scope.Complete();
    }

    using (MyDBContext db = new MyDBContext())
    {
       //...
       db.SaveChanges()
    }
       scope.Complete();
    }

/* end of adding transaction scope*/
}
kblau
  • 2,094
  • 1
  • 8
  • 20
  • This won't work for me. I have a Transaction Scope in my Service method, that calls multiple Repo methods. Repo methods open their own Context and call SaveChanges(). I have some checks in place, inside TrasnactionScope, but calling Transaction.Current.Rollback() and/or scope.Dispose() won't do a Rollback. The changes persist in database. What am I doing wrong? – Cubelaster Aug 14 '18 at 07:46
  • I think what we need to try (despite my answer), what is the behavior of using one, two, ... different instances of the same dbcontext. Finally, what happens when we use two different new of different dbcontexts. All this inside of the transaction. – kblau Aug 14 '18 at 18:34