Search code examples
c#entity-frameworkconcurrencyrollback

Rollback not restoring child entity records during exception in Entity Framework transaction


Question:

I am encountering an issue with the rollback behavior in Entity Framework transactions. I have a scenario where I am deleting a parent entity along with its child entities within a transaction. However, if an exception occurs before the transaction is committed, the rollback successfully restores the parent entity, but the child entity records remain deleted.

Here's the simplified code structure:

PaymentController.cs

try
{
  uow.BeginTransaction();
  var moneyTransaction = uow.MoneyTransactions.GetById(id);
  var log = new LogRecord(LogRecordType.Delete)
  {
    TransId = moneyTransaction.Id,
    TransType = moneyTransaction.Type,
    Data1 = moneyTransaction.Splits[0].Account.ToString(),
    Data2 = moneyTransaction.Splits[1].Account.ToString(),
    Notes = moneyTransaction.Notes,
  };
  uow.LogRecords.Add(log);
  uow.MoneyTransactions.Delete(id);
  deleted = uow.Save();
}
catch (Exception ex)
{
    uow.Rollback();
    throw ex;
}

UoW.cs

public bool Save()
{
    var saved = _context.SaveChanges();
    transaction.Commit();
    return saved > 0;
}

public void BeginTransaction()
{
    transaction = _context.Database.BeginTransaction();
}

public void Rollback()
{
    transaction.Rollback();
}

The issue is that the Splits child entity records are not restored during the rollback, while the parent entity MoneyTransaction is successfully restored when an exception is thrown.

How can I ensure that the child entity records (Splits) are also rolled back when an exception occurs within the transaction scope? Is there a better approach to handle these scenarios effectively?

My try:

  1. Removing the line {{ uow.LogRecords.Add(log); }} fixes the rollback behavior
  2. Working without UoW did not solve the problem.

Edit:

I have this code in BbContext Class:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Transaction>()
                .HasMany(t => t.Splits)
                .WithRequired(s => s.Transaction)
                .HasForeignKey(s => s.Transaction_Id)
                .WillCascadeOnDelete(true);

            modelBuilder.Entity<Transaction>()
                .Property(t => t.RowVersion)
                .IsRowVersion();

            base.OnModelCreating(modelBuilder);
        }
    }

Solution

  • I solve this problem by initializing DbContext instance in each form separately, as previously I was initializing it only in Program.cs file and passing it same to all the program, like this:

    using (DBContext cxt = new DBContext())
    {
        Application.Run(new FormLogin(cxt));
    }
    

    Now become like this:

    Program.cs

    Application.Run(new FormLogin());
    

    FormLogin.cs

    public FormLogin()
    {
        cxt = new DBContext();
    }