4.8. Working with Transactions

Whenever we call the SaveChanges() method while adding, updating or deleting, Entity Framework starts and ends an implicit transaction. Since we use disconnected data access, all operations are carried out within one transaction. Entity Framework starts and ends a transaction automatically for each data retrieval. We will take the following example to illustrate how automatic transactions work.

Suppose we need to make a discount on goods selected in the grid. Without explicit transaction management, the code would be as follows:

  1. var dbContext = AppVariables.getDbContext();
  2. foreach (DataGridViewRow gridRows in dataGridView.SelectedRows) {
  3. int id = (int)gridRows.Cells["Id"].Value;
  4. // here there is an implicit start and the completion of the transaction
  5. var product = dbContext.PRODUCTS.Find(id);
  6. // discount 10%
  7. decimal discount = 10.0m;
  8. product.PRICE = product.PRICE * (100 - discount) /100;
  9. }
  10. // here there is an implicit start and the completion of the transaction
  11. // all changes occur in one transaction
  12. dbContext.SaveChanges();

Let’s say we select 10 products. Ten implicit transactions will be used for finding the products by their identifiers. One more transaction will be used to save the changes.

If we control transactions explicitly, we can use just one transaction for the same piece of work. For example:

  1. var dbContext = AppVariables.getDbContext();
  2. // explicit start of a default transaction
  3. using (var dbTransaction = dbContext.Database.BeginTransaction()) {
  4. string sql =
  5. "UPDATE PRODUCT " +
  6. "SET PRICE = PRICE * ROUND((100 - @DISCOUNT)/100, 2) " +
  7. "WHERE PRODUCT_ID = @PRODUCT_ID";
  8. try {
  9. // create query parameters
  10. var idParam = new FbParameter("PRODUCT_ID", FbDbType.Integer);
  11. var discountParam = new FbParameter("DISCOUNT", FbDbType.Decimal);
  12. // create a SQL command to update records
  13. var sqlCommand = dbContext.Database.Connection.CreateCommand();
  14. sqlCommand.CommandText = sql;
  15. // specify which transaction to use
  16. sqlCommand.Transaction = dbTransaction.UnderlyingTransaction;
  17. sqlCommand.Parameters.Add(discountParam);
  18. sqlCommand.Parameters.Add(idParam);
  19. // prepare query
  20. sqlCommand.Prepare();
  21. // for all selected records in the grid
  22. foreach (DataGridViewRow gridRows in dataGridView.SelectedRows) {
  23. int id = (int)gridRows.Cells["Id"].Value;
  24. // initialize query parameters
  25. idParam.Value = id;
  26. discountParam.Value = 10.0m; // discount 10%
  27. // execute sql statement
  28. sqlCommand.ExecuteNonQuery();
  29. }
  30. dbTransaction.Commit();
  31. }
  32. catch (Exception ex) {
  33. dbTransaction.Rollback();
  34. MessageBox.Show(ex.Message, "error");
  35. }
  36. }

Our code starts the transaction with the default parameters. To specify your own parameters for a transaction, you should use the UseTransaction method.

  1. private void btnDiscount_Click(object sender, EventArgs e) {
  2. DiscountEditorForm editor = new DiscountEditorForm();
  3. editor.Text = "Enter discount";
  4. if (editor.ShowDialog() != DialogResult.OK)
  5. return;
  6. bool needUpdate = false;
  7. var dbContext = AppVariables.getDbContext();
  8. var connection = dbContext.Database.Connection;
  9. // explicit start of transaction
  10. using (var dbTransaction = connection.BeginTransaction(IsolationLevel.Snapshot)) {
  11. dbContext.Database.UseTransaction(dbTransaction);
  12. string sql =
  13. "UPDATE PRODUCT " +
  14. "SET PRICE = ROUND(PRICE * (100 - @DISCOUNT)/100, 2) " +
  15. "WHERE PRODUCT_ID = @PRODUCT_ID";
  16. try {
  17. // create query parameters
  18. var idParam = new FbParameter("PRODUCT_ID", FbDbType.Integer);
  19. var discountParam = new FbParameter("DISCOUNT", FbDbType.Decimal);
  20. // create a SQL command to update records
  21. var sqlCommand = connection.CreateCommand();
  22. sqlCommand.CommandText = sql;
  23. // specify which transaction to use
  24. sqlCommand.Transaction = dbTransaction;
  25. sqlCommand.Parameters.Add(discountParam);
  26. sqlCommand.Parameters.Add(idParam);
  27. // prepare statement
  28. sqlCommand.Prepare();
  29. // for all selected records in the grid
  30. foreach (DataGridViewRow gridRows in dataGridView.SelectedRows) {
  31. int id = (int)gridRows.Cells["PRODUCT_ID"].Value;
  32. // initialize query parameters
  33. idParam.Value = id;
  34. discountParam.Value = editor.Discount;
  35. // execute SQL statement
  36. needUpdate = (sqlCommand.ExecuteNonQuery() > 0) || needUpdate;
  37. }
  38. dbTransaction.Commit();
  39. }
  40. catch (Exception ex) {
  41. dbTransaction.Rollback();
  42. MessageBox.Show(ex.Message, "error");
  43. needUpdate = false;
  44. }
  45. }
  46. // refresh grid
  47. if (needUpdate) {
  48. // for all selected records in the grid
  49. foreach (DataGridViewRow gridRows in dataGridView.SelectedRows) {
  50. var product = (PRODUCT)bindingSource.List[gridRows.Index];
  51. dbContext.Refresh(RefreshMode.StoreWins, product);
  52. }
  53. bindingSource.ResetBindings(false);
  54. }
  55. }

That’s it. Now only one transaction is used for the entire set of updates and there are no unnecessary commands for finding data.

All that is left to do is to add a dialog box for entering the value of the discount and code to update data in the grid. Try to do it on your own.