Published
Monday, August 27, 2007 4:49 PM
by
mtaulty
How do transactions play with the Entity Framework? As a starting point, if I do something like;
static void Main(string[] args)
{
using (NorthwindContext ctx = new NorthwindContext("Name=NorthwindEntities"))
{
foreach (Shippers s in ctx.Shippers)
{
s.Phone = "Bar";
}
ctx.SaveChanges(true);
}
}
Then, from the SQL Profiler I can see that a transaction is being created for me by SaveChanges and is committed at the point where SaveChanges succeeds (it'll be rolled back in the case where SaveChanges fails).
Here's the complete profiler trace;
So, that's reasonably obvious - the query executes on its own and then the updates are all bounded for me by a transaction created by the SaveChanges method. If I wanted the transaction to also include the query then I could use a TransactionScope to control that. The chances are that I'd be doing that because I wanted to change the transaction isolation level so I could do something like;
static void Main(string[] args)
{
using (NorthwindContext ctx = new NorthwindContext("Name=NorthwindEntities"))
{
TransactionOptions options = new TransactionOptions()
{
IsolationLevel = System.Transactions.IsolationLevel.Serializable
};
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, options))
{
foreach (Shippers s in ctx.Shippers)
{
s.Phone = "Foo";
}
ctx.SaveChanges(true);
Console.WriteLine("Commit (c) or rollback (b)?");
if (Console.ReadLine() == "c")
{
scope.Complete();
}
}
}
}giving a profiler output like;
And so now I've got my Serializable transaction spanning from before the initial query until after the call to SaveChanges. Note that also the SaveChanges is smart enough to realise that because it did not create the transaction then it has no real right trying to commit it and so it doesn't commit it. It leaves that decision to the calling code and (in this instance) I pressed "b" and rolled it back as you can see in the trace.
One last point here that I'm not very sure about at all...
What if you've already got a transaction kicking around somewhere and you want to bring some Entity Framework code into it? It's easy enough if you've got an existing System.Transactions.Transaction because you can just grab that from Transaction.Current but what if you have (e.g.) a SqlTransaction? That is, you've written some code against SqlClient in the past and you've passed around a SqlTransaction or an IDbTransaction explicitly and now you're writing a new bit of code with Entity Framework and you want to plug in to that IDbTransaction that you've got?
At the time of writing, I'm not sure whether you can do this or not. Some things look promising;
- EntityConnection.Transaction looks promising until I figured out it's a read-only property for an EntityTransaction.
- EntityConnection.EnlistTransaction looks promising until I figured out that it wants a System.Transactions.Transaction.
So, I'm not 100% as to how you do this. In the LINQ to SQL world, it's easier because you just set the DataContext.Transaction property. However, it's seems like it should be a lot easier for the LINQ to SQL case because it knows to expect a SqlTransaction whereas the Entity Framework can't deal with a specific transaction type (although it could perhaps accept an IDbTransaction?).
So, that last point remains a big "TBD" for me - not sure how/whether you can do this but I'll update the post if I find out one way or another.