3

I created very a simple database using Entity Framework 4. I'd like to be able to use transactions on entities, but I can't seem to keep changes from rolling back. I really just need a way to abandon temporary changes to entities before they are saved to the database.

For example, the following code uses an entity framework object context "MusicContainer". Inside a TransactionScope, an Artist entity is created. The transaction then ends without being completed; so I'd expect the transaction to be rolled back. But, the program runs as if I'd never created the TransactionScope in the first place; after the TransactionScope ends, the line music.SaveChanges() saves the object to the database.

class Program
{
    static void Main(string[] args)
    {
        using (MusicContainer music = new MusicContainer())
        {
            using (TransactionScope transaction = new TransactionScope())
            {
                Artist artist = new Artist { Name = "Test" };
                music.Artists.AddObject(artist);
            }
            // The transaction ended without Complete(); shouldn't the changes be abandoned?
            music.SaveChanges();
        }
    }
}

If entity framework doesn't use TransactionScope the way I'm expecting it to here, how can I get the functionality I'm looking for? I have several circumstances where the caller of a function passes in the MusicContainer, and I need to leave the MusicContainer in a clean state before I return from the function (i.e. rolling back changes so they don't accidently get saved in with another SaveChanges called on the same MusicContainer object).

Jay Sullivan
  • 14,694
  • 8
  • 54
  • 79

3 Answers3

7

You don't need a TransactionScope at all in this scenario, SaveChanges() is all that's needed - if you have another using block with MusicContainer, this will be in a separate transaction and won't save any changes within your current using block. TransactionScope is only needed for transactions spanning multiple DB contexts.

In general a context should only be used for a unit of work consisting of related operations, once they are completed call SaveChanges(). Open a new context for each separate, unrelated unit of work. Having said that, just use this in your scenario:

        using (MusicContainer music = new MusicContainer())
        {
                Artist artist = new Artist { Name = "Test" };
                music.Artists.AddObject(artist);
                music.SaveChanges();
        }
BrokenGlass
  • 149,257
  • 27
  • 271
  • 318
  • Are you suggesting I use a separate "using (MusicContainer music = new MusicContainer())" for each transaction? My first suspicion was that this would be a bottleneck if I have dozens of these going on in parallel--am I wrong to think that? – Jay Sullivan Mar 07 '11 at 17:22
  • 2
    @notfed I believe the overhead is minimal - DB connections are only used by EF when needed, i.e. when calling `SaveChanges()` or doing a query, also check out this MSDN article: http://msdn.microsoft.com/en-us/library/cc853327.aspx – BrokenGlass Mar 07 '11 at 17:42
  • 2
    @notfed: You must use new container for each transaction. Read here: http://stackoverflow.com/questions/3653009/entity-framework-and-connection-pooling/3653392#3653392 – Ladislav Mrnka Mar 07 '11 at 18:00
  • Thanks, guys. I knew I was approaching some aspect of this from the wrong angle, and this helped smack some sense back into me. – Jay Sullivan Mar 07 '11 at 18:18
2

You have your SaveChanges in the wrong place.

class Program
{
    static void Main(string[] args)
    {
        using (MusicContainer music = new MusicContainer())
        {
            using (TransactionScope transaction = new TransactionScope())
            {
                Artist artist = new Artist { Name = "Test" };
                music.Artists.AddObject(artist);
                music.SaveChanges();
            }
            // The transaction ended without Complete(); the changes are abandoned?
        }
    }
}

You shouldn't reuse your MusicContainer if the transaction fails. Create a new one for each Unit of Work

Brian Cauthon
  • 5,484
  • 2
  • 21
  • 26
  • I have the SaveChanges in the wrong place on purpose. My point is: in the code I've given, why is SaveChanges saving those changes? – Jay Sullivan Mar 07 '11 at 17:16
  • 1
    You have to declare the TransactionScope before the MusicContainer. – edze Mar 07 '11 at 17:24
0

Entity framework should be able to use TransactionScope. You need to start a New TransactionScope if you need to rollback the changes apllied only by this method.

 using (MusicContainer music = new MusicContainer())
   {
     using (TransactionScope transaction = new TransactionScope(TransactionScopeOptions.RequiresNew))
          {
              Artist artist = new Artist { Name = "Test" };
              music.Artists.AddObject(artist);
              music.SaveChanges();
              scope.Complete(); 
          }
    }
Amitabh
  • 51,891
  • 40
  • 102
  • 154
  • Well, if I'm being passed a MusicContainer from a caller function, I don't have the option the create the MusicContainer inside the TransactionScope. – Jay Sullivan Mar 07 '11 at 17:18
  • Then you don't need to call SaveChanges in your method. Let the top level function call the SaveChanges – Amitabh Mar 07 '11 at 17:22
  • But, I need to _abandon_ changes that have been made to the object context, and I don't want the caller to save those changes. – Jay Sullivan Mar 07 '11 at 17:24
  • Thanks a lot for helping out, but that still doesn't work :/. I think, as the other answers are pointing out, that I should really just use the container itself as my transaction. – Jay Sullivan Mar 07 '11 at 18:21