Please navigate to the bottom of the page for Table of Contents

Thursday, June 7, 2012

Entity Framework transaction scope examples

Transactions as a core building block of entity framework. The DbContext object which we heavily use for interacting with the database uses transactions internally without you having to do anything extra. In case you need to manually provide transaction support, here is how you can do it.
In this post, I cover three cases in which transaction scope is being used to show rollback when an error occurs during an update of multiple entities:
  1. when you have multiple save calls to the context;
  2. when you have  single save with multiple object; and
  3. transactions across multiple contexts.
Let’s first review our simple model and context object. To re-create this project, use Visual Studio 2010 console project with Entity Framework 5.0 RC bits from nuget.
Our model is a simplified version of a “Product” object and it’s definition look like this:
using System;
using System.ComponentModel.DataAnnotations;
 
namespace EFTransactionsDemo.Model
{
    public class Product
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        [Required]
        public DateTime DateAdded { get; set; }
    }
}



Our context definition looks like:

using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
 
namespace EFTransactionsDemo.Model
{
    public class EFTDbContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
         
        public EFTDbContext() : 
            base("EFTransactionsDemo")
        {
            // disable proxy creation 
            this.Configuration.ProxyCreationEnabled = false;
        }
         
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // Tell Code First to ignore PluralizingTableName convention
            // If you keep this convention then the generated tables will have pluralized names.
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
    }
}



The main method (this is demo code; in production environments, please make sure to use proper programming paradigms, do error handling, parameter checks, etc.), calls three functions that demonstrate the use of transaction scope for the three scenarios I listed above.

using System;
using System.Linq;
using System.Transactions;
using EFTransactionsDemo.Model;

namespace EFTransactionsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // setup
            CreateDatabase();

            // transaction demo - 1
            // wrap multiple save calls within one transaction
            // and roll back on a bad one
            TestMultipleSaveCalls();

            // transaction demo - 2
            // wrap multiple adds and one save within one transaction
            // and roll back on a bad one
            TestMultipleAddWithOneSaveCall();

            // transaction demo - 3
            // multiple contexts in same transaction
            TestMultipleContexts();
        }

        private static void TestMultipleSaveCalls()
        {
            // good product
            Product goodProduct = new Product()
            {
                Name = "My awesome book",
                DateAdded = DateTime.UtcNow
            };

            // invalid product
            Product invalidProd = new Product()
            {
                Name = "My awesome book part 2"
                // note - no date added specified
            };

            // define our transaction scope
            var scope = new TransactionScope(
                // a new transaction will always be created
                TransactionScopeOption.RequiresNew,
                // we will allow volatile data to be read during transaction
                new TransactionOptions()
                {
                    IsolationLevel = IsolationLevel.ReadUncommitted
                }
            );

            try
            {
                // use the scope we just defined
                using (scope)
                {
                    // create a new db context
                    using (var ctx = new EFTDbContext())
                    {
                        // add the product
                        ctx.Products.Add(goodProduct);
                        // save
                        ctx.SaveChanges();

                        // add the invalid product
                        ctx.Products.Add(invalidProd);
                        // save
                        ctx.SaveChanges();
                    }

                    // everything good; complete
                    scope.Complete();
                }
            }
            catch { }

            // verify that we actually rolled back
            using (var ctx = new EFTDbContext())
            {
                Console.WriteLine(ctx.Products.Count());
            }
        }

        private static void TestMultipleAddWithOneSaveCall()
        {
            // good product
            Product goodProduct = new Product()
            {
                Name = "My awesome book",
                DateAdded = DateTime.UtcNow
            };

            // invalid product
            Product invalidProd = new Product()
            {
                Name = "My awesome book part 2"
                // note - no date added specified
            };

            // define our transaction scope
            var scope = new TransactionScope(
                // a new transaction will always be created
                TransactionScopeOption.RequiresNew,
                // we will allow volatile data to be read during transaction
                new TransactionOptions()
                {
                    IsolationLevel = IsolationLevel.ReadUncommitted
                }
            );

            try
            {
                // use the scope we just defined
                using (scope)
                {
                    // create a new db context
                    using (var ctx = new EFTDbContext())
                    {
                        // add the product
                        ctx.Products.Add(goodProduct);

                        // add the invalid product
                        ctx.Products.Add(invalidProd);
                        // save
                        ctx.SaveChanges();
                    }

                    // everything good; complete
                    scope.Complete();
                }
            }
            catch { }

            // verify that we actually rolled back
            using (var ctx = new EFTDbContext())
            {
                Console.WriteLine(ctx.Products.Count());
            }
        }

        private static void TestMultipleContexts()
        {
            // good product
            Product goodProduct = new Product()
            {
                Name = "My awesome book",
                DateAdded = DateTime.UtcNow
            };

            // invalid product
            Product invalidProd = new Product()
            {
                Name = "My awesome book part 2"
                // note - no date added specified
            };

            // define our transaction scope
            var scope = new TransactionScope(
                // a new transaction will always be created
                TransactionScopeOption.RequiresNew,
                // we will allow volatile data to be read during transaction
                new TransactionOptions()
                {
                    IsolationLevel = IsolationLevel.ReadUncommitted
                }
            );

            try
            {
                // use the scope we just defined
                using (scope)
                {
                    // create a new db context
                    var firstCtx = new EFTDbContext();
                    // create a second context
                    var secondCtx = new EFTDbContext();

                    // add the product to first context
                    firstCtx.Products.Add(goodProduct);
                    // save
                    firstCtx.SaveChanges();

                    // add the invalid product to second context
                    secondCtx.Products.Add(invalidProd);
                    // save
                    secondCtx.SaveChanges();

                    // everything good; complete
                    scope.Complete();
                }
            }
            catch { }

            // verify that we actually rolled back
            using (var ctx = new EFTDbContext())
            {
                Console.WriteLine(ctx.Products.Count());
            }
        }

        private static void CreateDatabase()
        {
            using (var ctx = new EFTDbContext())
            {
                ctx.Database.CreateIfNotExists();
            }
        }
    }
}

As always, please do leave your thoughts and comments. It is the user feedback that drives the product and blog better.

35 comments:

  1. Thanks, intuitive and clear examples!

    ReplyDelete
  2. The "TestMultipleContexts" approach fails for me every time. I keep getting "System.Transactions.TransactionManagerCommunicationException: Network access for Distributed Transaction Manager (MSDTC) has been disabled." errors. Do you have any thoughts on this process? If so, I'd be interested in hearing them. Cheers, Landon (campbelllandon@hotmail.com)

    ReplyDelete
    Replies
    1. for multiple context (if they are from different databases) then they requires MSDTC to be enabled on the DB Server.

      Delete
  3. Thanks & Good work. I was a example with multiple context in single TransactionScope but unable to find . This give me a better understanding.
    Now i have a question, Do i need to call BeginTransaction method of DbContext or TransactionScope will take care that internally?
    U can send me email on harshmah@gmail.com.

    Thanks Again.

    ReplyDelete
  4. Is there a way to start transaction scope in one function and call another function that has its own transaction scope? For example...

    Method1{
    New Scope1{
    do something...
    }
    }

    Method2{
    new Scope1{
    Call Method1()
    do something...
    }
    }

    Thanks,
    AJ

    ReplyDelete
  5. Hi Nikhil,

    Can we call multiple save on same context object, I do not require rollback.

    ReplyDelete
    Replies
    1. Yes, as long as you are tracking the changes using the same context object (for example in a desktop app)

      Delete
  6. hi,
    For test no. 3 (TestMultipleContexts();), i randomly hit the error "System.Data.SqlClient.SqlException (0x80131904): Transaction context in use by another session.". Any idea?

    ReplyDelete
  7. Nice one!!!!

    Anil Singh
    http://www.code-sample.com/

    ReplyDelete
  8. The blog or and best that is extremely useful to keep I can share the ideas
    of the future as this is really what I was looking for, I am very comfortable and pleased to come here. Thank you very much.
    tanki online | 2048 game |
    tanki online game

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. The purpose of this communication is to advance a few of the techniques available in structuring small business sale transactions and to emphasize the value an experienced team brings in structuring the transaction.소액결제현금화

    ReplyDelete
  11. Successful e-commerce websites have one thing in common. They not only have a secure online transaction system in place but they display proof of the security measures in place to assure the customer at every stage of the transaction.소액결제현금화

    ReplyDelete
  12. 먹튀검증
    you for your efforts, bringing you interesting information. Thank you

    ReplyDelete
  13. 사설토토
    ur efforts, bringing you interesting information. Thank you

    ReplyDelete
  14. 안전놀이터
    efforts, bringing you interesting information. Thank y

    ReplyDelete
  15. whoah this blog is excellent i like studying
    your posts. Stay up the good work! You recognize, a lot of people are hunting around for
    this info, you could help them greatly.

    Also visit my page :::: 부산오피
    (jk)

    ReplyDelete
  16. Melodyne Studio 5 Crack allows you to work with audio in a completely new way. One that is musical, spiritual, crystalline, and almost magical.
    Slate Digital VMR Crack-mac
    Serum VST Crack
    Adobe XD Crack

    ReplyDelete
  17. Fl Studio Mac Crack has a selection of tools to aid in grading manipulation and editing. The digital audio platform cannot match the versatility of FL Studio’s playlist.
    Winsnap Crack License Key
    Microsoft Office 2019 Crack
    Corel Draw X7 Crack

    ReplyDelete
  18. Such a Nice post.#Thanks For winsnap-crack You can also visit my Website activecrack.com

    ReplyDelete
  19. Hi buddies, it is great written piece entirely defined, continue the good work constantly.
    top UI design companies

    ReplyDelete