It is well known that the database locks taken inside the transaction are released on the end of that transaction. So, in this code..
public static TransactionScope CreateTransactionScope()
{
return new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted });
}
Actually, in this one...
using (DataContext dataContext = new DataContext())
using (TransactionScope rootScope = CreateTransactionScope())
{
using (TransactionScope nested = CreateTransactionScope())
{
Ticket ticket = dataContext.ExecuteQuery<Ticket>(
"SELECT * FROM Tickets WITH (UPDLOCK) WHERE id={0}", ticketId).First();
nested.Complete();
}
// Will the lock be still ON here? Because I don't need him to be!
}
When exactly the row/page lock (UPDLOCK) will be released - after the disposing the nested transaction or the root one?
It will be released only after the root scope will exit the using block and will dispose.
The best way to learn this kind of stuff is by getting your hands dirty.
Create a simple database with table A and table B, each contains single column, name it "TimeStamp" and create simple console application that inserting timestamp (or any kind of value) and you can play with the transaction options and learn the behaviour.
Related
Here is a very simplistic example of my current state:
using(var scope = new TransactionScope(TransactionScopeOption.Required, transactionScopeTimeout, TransactionScopeAsyncFlowOption.Enabled))
{
const string ConnectionString="server=localhost;username=;password=;enlist=false;Initial Catalog=blubber"
using(var myContext = new MyContext(ConnectionString))
{
var myTestObject = new TestObjectBuilder().WithId(1).Build();
myContext.Add(myTestObject);
myContext.SaveChanges();
}
// ...
using(var myContext = new MyContext(ConnectionString))
{
var myObj = myContext.MyObjects.Single(s => s.Id == 1); // This is the same object as above
}
}
I have a TransactionScope which will be used here, but in my connectionstring I explicitly say I don't wanna enlist my Entity Framework (6.2) Transactions.
The current behavior is that on the Single(s => s.Id == 1) Expression I get an error after 30 seconds (the default timeout) that the element could not be found.
First of all: Why do I not get a timeout-Exception or any SqlException? Second: Why is my data-row locked in the database?
Also via Sql Server Management Studio I can not query that exact row (only with the NOLOCK hint).
If I remove the TransactionScope or set the ISOLATION LEVEL to READ UNCOMMITED before the Single query everything works fine.
Also because of the Dispose of the transaction-scope at the end all data will be removed / rollbacked which also should not happen if I didn't enlist to this AmbientTransaction.
So my expected behavior is, that I get no lock and my data is persisted even the transactionscope is disposed and rollbacked. EF should ignore the transactionscope here.
Do I miss something critical here?
EDIT: I tried the same thing with NHibernate and here it works like a charm.
I got some method:
public SaveMyData(...)
{
var transactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot };
using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
{
var dataInDb = dbDataService.LoadData();
dataInDb.SomeField = someNewValue;
dbDataService.SaveData(dataInDb);
transactionScope.Complete();
}
}
So it takes string serialized Dto from database and changes field. Than save it.
SaveMyData can be called from different threads in same time, so i got error:
You cannot use snapshot isolation to access table 'some table'
directly or indirectly in database 'some db' to update, delete, or
insert the row that has been modified or deleted by another
transaction. Retry the transaction or change the isolation level for
the update/delete statement
How can i avoid this error? I need to use different isolation level?
Inside SaveData() method i create new EF context and save changes.
The way i want to make it work is:
CallerA call SaveMyData, it locks and if CallerB calls it in same time he will wait untill CallerA commit. So i need to not allow CallerB read data before CallerA write changes.
I have multiple methods inside a Parallel.Invoke() that need to run inside of a transaction. These methods all invoke instances of SqlBulkCopy The use-case is "all-or-none", so if one method fails nothing gets committed. I am getting a TransactionAbortedException ({"Transaction Timeout"}) when I call the Complete() method on the parent transaction.
This is the parent transaction:
using (var ts = new TransactionScope())
{
var saveClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
var saveErrorsClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
var saveADClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
var saveEnrollmentsClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
Parallel.Invoke(_options, () =>
{
Save(data, saveClone);
},
() =>
{
SaveErrors(saveErrorsClone);
},
() =>
{
SaveEnrollments(data, saveEnrollmentsClone);
});
ts.Complete();
}//***** GET THE EXCEPTION HERE *****
Here's a dependent transaction that makes use of SqlBulkCopy (they're all the same structure). I'm passing-in the parent and assigning it to the child's TransactionScope
private void Save(IDictionary<string, string> data, Transaction transaction)
{
var dTs = (DependentTransaction)transaction;
if (transaction.TransactionInformation.Status != TransactionStatus.Aborted)
{
using (var ts = new TransactionScope(dTs))
{
_walmartData.Save(data);
Debug.WriteLine("Completed Processing XML - {0}", _stopWatch.Elapsed);
ts.Complete();
}
}
else
{
Debug.WriteLine("Save Not Executed - Transaction Aborted - {0}", _stopWatch.Elapsed);
dTs.Complete();
}
dTs.Complete();
}
EDIT (added my SqlBulkCopy method...notice null for the transaction param)
private void SqlBulkCopy(DataTable dt, SqlBulkCopyColumnMappingCollection mappings)
{
try
{
using (var sbc = new SqlBulkCopy(_conn, SqlBulkCopyOptions.TableLock, null))
{
sbc.BatchSize = 100;
sbc.BulkCopyTimeout = 0;
sbc.DestinationTableName = dt.TableName;
foreach (SqlBulkCopyColumnMapping mapping in mappings)
{
sbc.ColumnMappings.Add(mapping);
}
sbc.WriteToServer(dt);
}
}
catch (Exception)
{
throw;
}
}
Besides fixing the error, I'm open to alternatives. Thanks.
You're creating a form of deadlock with your choice of DependentCloneOption.BlockCommitUntilComplete.
Parallel.Invoke blocks the calling thread until all of its processing is complete. The jobs trying to be completed by Parallel.Invoke are all blocking while waiting for the parent transaction to complete (due to the DependentCloneOption). So the 2 are waiting on each other... deadlock. The parent transaction eventually times out and releases the dependent transactions from blocking, which unblocks your calling thread.
Can you use DependentCloneOption.RollbackIfNotComplete ?
http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.complete.aspx says that TransactionScope.Complete only commits the transaction it contains if it was the one that created it. Since you are creating the scope from an existing transaction I believe you will need to commit the transaction before calling complete on the scope.
From MSDN:
The actual work of commit between the resources manager happens at the
End Using statement if the TransactionScope object created the
transaction. If it did not create the transaction, the commit occurs
whenever Commit is called by the owner of the CommittableTransaction
object. At that point the Transaction Manager calls the resource
managers and informs them to either commit or rollback, based on
whether this method was called on the TransactionScope object
.
After a lot of pain, research, and lack of a valid answer, I've got to believe that it's not possible with the stack that I described in my question. The pain-point, I believe, is between TransactionScope and SqlBulkCopy. I put this answer here for the benefit of future viewers. If someone can prove that it can be done, I'll gladly remove this as the answer.
I believe that how you create your _conn-instance matters a lot, if you create it and open it within your TransactionScope-instance any SqlBulkCopy-related issues should be solved.
Have a look at Can I use SqlBulkCopy inside Transaction and Is it possible to use System.Transactions.TransactionScope with SqlBulkCopy? and see if it helps you.
void MyMainMethod()
{
using (var ts = new TransactionScope())
{
Parallell.InvokeOrWhatNotOrWhatEver(() => DoStuff());
}
}
void DoStuff()
{
using (var sqlCon = new SqlConnection(conStr))
{
sqlCon.Open(); // ensure to open it before SqlBulkCopy can open it in another transactionscope.
using (var bulk = new SqlBulkCopy(sqlCon))
{
// Do you stuff
bulk.WriteToServer...
}
ts.Complete(); // finish the transaction, ie commit
}
}
In short:
Create transaction scope
Create sql-connection and open it under the transaction scope
Create and use SqlBulkCopy-instance with above created conncection
Call transaction.Complete()
Dispose of everything :-)
I have pretty much standard EF 6.1 'create object in a database' code wrapped in transaction scope. For whatever reason the data persists in db after the transaction fails (to complete).
Code:
using (var db = this.Container.Resolve<SharedDataEntities>()) // << new instance of DbContext
{
using (TransactionScope ts = new TransactionScope())
{
SubscriptionTypes st = this.SubscriptionType.Value;
if (st == SubscriptionTypes.Lite && this.ProTrial)
st = SubscriptionTypes.ProTrial;
Domain domain = new Domain()
{
Address = this.Address.Trim(),
AdminUserId = (Guid)user.ProviderUserKey,
AdminUserName = user.UserName,
Description = this.Description.TrimSafe(),
DomainKey = Guid.NewGuid(),
Enabled = !masterSettings.DomainEnableControlled.Value,
Name = this.Name.Trim(),
SubscriptionType = (int)st,
Timezone = this.Timezone,
Website = this.Website.TrimSafe(),
IsPrivate = this.IsPrivate
};
foreach (var countryId in this.Countries)
{
domain.DomainCountries.Add(new DomainCountry() { CountryId = countryId, Domain = domain });
}
db.Domains.Add(domain);
db.SaveChanges(); // << This is the Saving that should not be commited until we call 'ts.Complete()'
this.ResendActivation(domain); // << This is where the Exception occurs
using (TransactionScope ts2 = new TransactionScope(TransactionScopeOption.Suppress))
{
this.DomainMembership.CreateDomainUser(domain.Id, (Guid)user.ProviderUserKey, user.UserName, DomainRoles.DomainSuperAdmin | DomainRoles.Driver);
ts2.Complete();
}
this.Enabled = domain.Enabled;
ts.Complete(); // << Transaction commit never happens
}
}
After SaveChanges() exception is thrown inside ResendActivation(...) so the changes should not be saved. However the records stay in database.
There is no other TransactionScope wrapping the code that I've pasted, it's triggered by an MVC Action call.
after more investigations, turns out that something - probably Entity Framework upgrade or database update process had put
Enlist=false;
into the database connection string. That effectively stops EF from picking up Transaction Scope.
So the solution is to set it to true, or remove it, I think by default it's true
Try using the transaction from the db instance it self, db.Database.BeginTransaction() if I recall it correctly instead of using the transaction scope.
using (var ts = db.Database.BeginTransaction())
{
..
}
Assuming that db is your entity framework context.
Context class by default support transactions. but with every new instance of context class, a new transaction will be created. This new transaction is a nested transaction and it will get committed once the SaveChanges() on the associated context class gets called.
In the given code it seems, we are calling a method that is responsible for creating a domain user i.e. CreateDomainUser and perhaps that has its own context object. thus this problem.
If this is the case(that this method has own context) the perhaps we don't even need TransactionScope here. We can simply pass the same context(that we are using before this call) to the function that is creating the domain user. We can then check for result of both operations and if they are successful, we simply need to call SaveChanges() method.
TransactionScope is usually needed when we are mixing ADO.NET calls with Entity framework. We can use it with context also, but it would be an overkill as the context class already has a transaction and we can simply use the same context to manage the transaction. If this is the case in the above code then the trick to fix the issue is to let the context class know that you want to use it with your own transaction. Since the transaction gets associated with the connection object in the scope, we need to use the context class with the connection that is being associated with the transaction scope. Also, we need to let the context class know that it cannot own the connection as it is being owned by the calling code. so we can do something like:
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText =
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
var query = context.Posts.Where(p => p.Blog.Rating > 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
}
scope.Complete();
}
See: http://msdn.microsoft.com/en-us/data/dn456843.aspx
You should be able to use TransactionScope with EF, I know our projects do. However, I think you want the EF context instantiated inside the transaction scope -- that is, I believe you need to swap the outermost / first two using statements, as the answer from #rahuls suggests.
Even if it works the other way ... if you have a service / app / business layer method that needs to update several tables, and you want those updates to be atomic, you'd need to do it this way. So for the sake of consistency (and your own sanity), I would recommend transaction scope first, context second.
In this code..
public static TransactionScope CreateTransactionScope(bool createNew = false)
{
return new TransactionScope(
createNew ? TransactionScopeOption.RequiresNew : TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted });
}
Actually, in this one...
using (TransactionScope rootScope = CreateTransactionScope())
{
using (TransactionScope nestedOne = CreateTransactionScope())
{ nestedOne.Complete(); }
using (TransactionScope nestedTwo = CreateTransactionScope(true))
{ nestedTwo.Complete(); }
// No committing, rollback 'rootScope'.
}
What transactions will be rolled back along with the root one - will it be only nestedOne or both nestedOne and nestedTwo?
nestedOne will join the root scope, so if the root scope will rollback, nestedOne will be roll back as well, but not nestedTwo which is a seperate transaction.
like you have the "RequireNew" option that seperate the transaction from the enclosing transaction you can have the "Suppress" option that stops the transaction for that scope.
Take a look at the following list from MSDN that gives a great lesson about transactions behaviour.
http://msdn.microsoft.com/en-us/library/ms172152(v=vs.90).aspx