I would like to be 100% that when I use such pattern
using (var scope = new TransactionScope())
{
db1.Foo.InsertOnSubmit(new Foo()); // just an example
db1.SubmitChanges();
scope.Commit();
}
the transaction scope will only refer to db1 not db2 (db1 and db2 are DataBaseContext objects). So how to limit scope to db1 only?
Thank you in advance.
The strength of TransactionScope is that it automatically catches everything within it, down the call stack (unless another TransactionScope with RequiresNew or Supress). If this is not what you want, you should use another mechanism for the transaction handling.
One way is to open SqlConnection and call BeginTransaction to get a transaction, then use that DB connection when creating your data context. (Maybe you'll have to set the Transaction propery on the data context, I'm not sure).
In the example you have given above, the use of a TransactionScope is totally redundant. There is only one function call that actually modifies the database: SubmitChanges and it always creates its own transaction if there isn't already one existing. The reason is that when you're doing several operations SubmitChanges should either succeed in them all or fail all. If you're only after transactional integrity for a single SubmitChanges call for a single data context, then you can just drop the TransactionScope.
As far as I know, what you are after is exactly what you have written, the compiler will ensure that transaction is completed when you commit.
If you use:
using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
db1.Foo.InsertOnSubmit(new Foo()); // just an example
db1.SubmitChanges();
scope.Commit();
}
You can be sure a new transcation is created for this scope, so everyting you do inside the scope has its own transaction, and will never use any existing ambient transactions.
Related
I'm implementing a DAL abstraction layer on top of the C# Mongo DB driver using the Repository + Unit of Work pattern.
My current design is that every unit of work instance opens (and closes) a new Mongo DB session.
The problem is that Mongo DB only allows a 1:1 ratio between a session and a transaction, so multiple units of work under the same .NET transaction will not be possible.
The current implementation is:
public class MongoUnitOfWork
{
private IClientSessionHandle _sessionHandle;
public MongoUnitOfWork(MongoClient mongoClient)
{
_sessionHandle = mongoClient.StartSession();
}
public void Dispose()
{
if (_sessionHandle != null)
{
// Must commit transaction, since the session is closing
if (Transaction.Current != null)
_sessionHandle.CommitTransaction();
_sessionHandle.Dispose();
}
}
}
And because of this, the following code won't work. The first batch of data will be committed ahead of time:
using (var transactionScope = new TransactionScope())
{
using (var unitOfWork = CreateUnitOfWork())
{
//... insert items
unitOfWork.SaveChanges();
} // Mongo DB unit of work implementation will commit the changes when disposed
// Do other things
using (var unitOfWork = CreateUnitOfWork())
{
//... insert some more items
unitOfWork.SaveChanges();
}
transactionScope.Complete();
}
Obviously the immediate answer would be to bring all of the changes into one unit of work, but it's not always possible, and also this leaks the Mongo DB limitation.
I thought about session pooling, so that multiple units of work will use the same session, and commit/rollback when the transient transaction completes/aborts.
What other solutions are possible?
Clarification:
The question here is specifically regarding Unit-Of-Work implementation over MongoDB using the MongoDB 4.0 (or later) built-in transaction support.
I never used MongoDB; do not know anything about it. I am only answering in terms of TransactionScope; so not sure if this will help you.
Please refer the Magic Of TransactionScope. IMO, there are three factors you should look for:
Connection to the database should be opened inside the TransactionScope.
So remember, the connection must be opened inside the TransactionScope block for it to enlist in the ambient transaction automatically. If the connection was opened before that, it will not participate in the transaction.
Not sure but it looks that you can manually enlist the connection opened outside the scope using connection.EnlistTransaction(Transaction.Current).
Looking at your comment and the edit, this is not an issue.
All operations should run on same thread.
The ambient transaction provided by the TransactionScope is a thread-static (TLS) variable. It can be accessed with static Transaction.Current property. Here is the TransactionScope code at referencesource.microsoft.com. ThreadStatic ContextData, contains the CurrentTransaction.
and
Remember that Transaction.Current is a thread static variable. If your code is executing in a multi-threaded environments, you may need to take some precautions. The connections that need to participate in ambient transactions must be opened on the same thread that creates the TransactionScope that is managing that ambient transaction.
So, all the operations should run on same thread.
Play with TransactionScopeOption (pass it to constructor of TransactionScope) values as per your need.
Upon instantiating a TransactionScope by the new statement, the transaction manager determines which transaction to participate in. Once determined, the scope always participates in that transaction. The decision is based on two factors: whether an ambient transaction is present and the value of the TransactionScopeOption parameter in the constructor.
I am not sure what your code expected to do. You may play with this enum values.
As you mentioned in the comment, you are using async/await.
Lastly, if you are using async/await inside the TransactionScope block, you should know that it does not work well with TransactionScope and you might want to look into new TransactionScope constructor in .NET Framework 4.5.1 that accepts a TransactionScopeAsyncFlowOption. TransactionScopeAsyncFlowOption.Enabled option, which is not the default, allows TransactionScope to play well with asynchronous continuations.
For MongoDB, see if this helps you.
The MongoDB driver isn't aware of ambient TransactionScopes. You would need to enlist with them manually, or use JohnKnoop.MongoRepository which does this for you: https://github.com/johnknoop/MongoRepository#transactions
What is difference of these codes:
using (TransactionScope tran = new TransactionScope ())
{
using (Entities ent = new Entities())
{
and
using (Entities ent = new Entities())
{
using (TransactionScope tran = new TransactionScope ())
{
Does line order matter?
Thanks
In this case it wouldn't matter, since the Entities seems like Model / Data class, which cannot / needn't enlist into a transaction, so you can have any order, it would not create a difference. Now if the there's any issue in the Entities operation, Read / DML then transaction would also abort, assuming that operation takes place in the ambient transaction context, though class / object doing it like IDBConnection would anyway automatically enlisted into a transaction (provided not set to false), however even if Connection is created outside transaction context, then would not automatically enlist, needs explicit enlisting for Transaction Context
In Short
For your current code, it doesn't matter till the point you are dealing with an object that needs Transaction enlistment like IDBConnection. Out of two code snippets though my preference would be first one, where ambient transaction is all encompassing all the objects need to be enlist automatically does, we don't leave any object by chance.
Important Difference
What you though may want to be careful about whether you want to access Entities outside the Transaction Context, since that is not possible in the Option 1 but would not be an issue in the Option 2
Yes the order matters. Or rather we can't say it doesn't matter without looking at your code.
DbConnection instances will enist in an ambient transaction if one exists when they are Open()ed.
Your DbContext constructor might open the underlying DbConnection, in which case the two patterns differ.
The first one is the normal pattern, and you should stick to that.
Also if you are using SQL Server don't use the default constructor of TransactionScope. See Using new TransactionScope() Considered Harmful
I have the following code below with different calls to stored procedures.
My problem is that I want this to be in a transaction, so if one of the DB methods fails then it will automatically rollback the pending changes.
I have read some articles on Stackoverflow/Microsoft pages which states that the SubmitChanges will wrap these in transactions for me.
The problem is that the spClearTablesForReplication method clears the tables and later on the code fails on purpose (for testing) and then all my tables are empty (is not rolled back)
XalSqlDataContext db = new XalSqlDataContext();
db.spClearTablesForReplication();
db.spUpdateStockItemGroup(ConvertToXElement(typeof(List<StockItemGroup>), stockItemGroups));
db.spUpdateStockItemSubGroup(ConvertToXElement(typeof(List<StockItemSubGroup>), stockItemSubGroups));
db.SubmitChanges();
Any clues/solutions for this.
Wrap your code in a TransactionScope using block and then that will rollback unless it reaches the point where you call transactionScope.Complete().
Example:
using (TransactionScope transactionScope = new TransactionScope())
{
//code here
transactionScope.Complete();
}
How to do transaction control on data access using DataAdapter and Stored Procedure in C#? Currently I want to execute 2 stored procedure calls via DataAdapter, but I want to do transaction control on it. Is there any way to do it?
The preferred way of doing so is to use transaction scopes to handle this for you. Just surround the body of code that invoke both stored procedure calls with a new TransactionScope:
using(TransactionScope scope = new TransactionScope())
{
// your ADO.NET code that calls sprocs ...
}
Any calls to the database that occur on the same connection will automatically be combined into a single transaction. You can also specify whether the code within the transaction scope should enlist in an existing transaction or start its own via the optional TransactionScopeOption parameter.
This is the preferred manner of combining calls into a single transaction. The alternative, is to manually acquire a DBTransaction by calling Connection.BeginTransaction() - performing your work and then calling tran.Commit().
I believe that the easiest way to do this would be to wrap both calls in a TransactionScope. See the example on this page:
http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx
I'm writing an integration test where I will be inserting a number of objects into a database and then checking to make sure whether my method retrieves those objects.
My connection to the database is through NHibernate...and my usual method of creating such a test would be to do the following:
NHibernateSession.BeginTransaction();
//use nhibernate to insert objects into database
//retrieve objects via my method
//verify actual objects returned are the same as those inserted
NHibernateSession.RollbackTransaction();
However, I've recently found out about TransactionScope which apparently can be used for this very purpose...
Some example code I've found is as follows:
public static int AddDepartmentWithEmployees(Department dept)
{
int res = 0;
DepartmentAdapter deptAdapter = new DepartmentAdapter();
EmployeeAdapter empAdapter = new EmployeeAdapter();
using (TransactionScope txScope = new TransactionScope())
{
res += deptAdapter.Insert(dept.DepartmentName);
//Custom method made to return Department ID
//after inserting the department "Identity Column"
dept.DepartmentID = deptAdapter.GetInsertReturnValue();
foreach(Employee emp in dept.Employees)
{
emp.EmployeeDeptID = dept.DepartmentID;
res += empAdapter.Insert(emp.EmployeeName, emp.EmployeeDeptID);
}
txScope.Complete();
}
return res;
}
I believe that if I don't include the line txScope.Complete() that the data inserted will be rolled back. But unfortunately I don't understand how that is possible... how does the txScope object keep a track of the deptAdapter and empAdapter objects and their transactions on the database.
I feel like I'm missing a bit of information here...am I really able to replace my BeginTransaction() and RollbackTransaction() calls by surrounding my code using TransactionScope?
If not, how then does TransactionScope work to roll back transactions?
Essentially TransactionScope doesn't track your Adapter's, what it does is it tracks database connections. When you open a DB connection the connections will looks if there is an ambient transaction (Transaction Scope) and if so enlist with it. Caution if there are more the one connection to the same SQL server this will escalate to a Distribtued Transaction.
What happens since you're using a using block you are ensuring dispose will be called even if an exception occurs. So if dispose is called before txScope.Complete() the TransactionScope will tell the connections to rollback their transactions (or the DTC).
The TransactionScope class works with the Transaction class, which is thread-specific.
When the TransactionScope is created, it checks to see if there is a Transaction for the thread; if one exists then it uses that, otherwise, it creates a new one and pushes it onto the stack.
If it uses an existing one, then it just increments a counter for releases (since you have to call Dispose on it). On the last release, if the Transaction was not comitted, it rolls back all the work.
As for why classes seem to magically know about transactions, that is left as an implementation detail for those classes that wish to work with this model.
When you create your deptAdapter and emptAdapter instances, they check to see if there is a current transaction on the thread (the static Current property on the Transaction class). If there is, then it registers itself with the Transaction, to take part in the commit/rollback sequence (which Transaction controls, and might propogate to varying transaction coordinators, such as kernel, distributed, etc.).