I'm trying to use a TransactionScope at Domain level, so I can manipulate data across (potentially) several repositories, yet save all that in the same transaction.
I have the following save method:
public void Save(MyEntity source)
{
using (var scope = new TransactionScope())
{
var context = new MyEFEntities(environment.ConnectionString);
this.Repository.Add(source.ToDbMyEntity(), context);
context.SaveChanges();
scope.Complete();
}
}
But I get the following error on the .SaveChanges():
The transaction specified for TransactionScope has a different
IsolationLevel than the value requested for the scope. Parameter name:
transactionOptions.IsolationLevel
What's causing this?
I think that default isolation level for Entity framework is the one which is default for the database, for instance default isolation level of SqlServer is READ COMMITTED, while for TransactionScope default is Serializable so when you try to change it inside using (var scope = new TransactionScope()) block it throws an error. Try something like this:
var transactionOptions = new TransactionOptions();
transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions));
Related
As mentioned here
using Entity Framework 6 if we begin a transaction using Database.BeginTransaction()
we can check whether a context has a transaction using this statement:
var transaction = db.Database.CurrentTransaction;
Unfortunately this way is not working if we used TrasctionScope
to begin transaction :
var transactionScope = new TransactionScope();
I'm just wondering if there is any way to check whether a context has a transaction when I'm using TrasctionScope ?
Using entity framework 6 you can use transactions in two ways:
The first way, using Database.BeginTransaction() method:
using (var context = new Context())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
//Some EF Statments
Context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
The second way, using TransactionScope :
using (var scope = new TransactionScope())
{
//Some EF Statments
Context.SaveChanges();
scope.Complete();
}
If you used first way you can get transaction instance using statement:
`var transaction = context.Database.CurrentTransaction;`
On the other hand if you begin transaction using TrasctionScope you have to use:
var transaction = System.Transactions.Transaction.Current;
to get transaction instance or check whether a context has a transaction or not
I have use transaction scope when inserting data into two table in a single call. But transaction scope held the database or lock the database for a particular scope that time another user not able to perform any operation on the database.So please tell me the alternative solution for avoiding such situation.
using (TransactionScope scope = new TransactionScope())
{
CamphorTray_OrderDetails ct = new CamphorTray_OrderDetails();
ct.CamphorTray_ID = ctv.CamphorTray_ID;
ct.Temple_ID = ctv.Temple_ID;
ct.For_Date = ctv.For_Date;
ct.Deity = ctv.Deity;
ct.Note_Cash = ctv.Note_Cash;
ct.Coin = ctv.Coin;
ct.Total_amount = ctv.Total_amount;
ct.Created_By = ctv.Created_By;
ct.Created_Date = DateTime.Now;
ct.Modified_By = ctv.Created_By;
ct.Modified_Date = DateTime.Now;
CTOD.Insert(ct);
InsertAudit_CamphorTray_OrderDetails(ct, "Insert");
scope.Complete();
}
The ability of other users to access the data of your transaction and your ability to access theirs and the databases' attempts to make sure this works (by locking tables and/or rows) can be controlled by the TransactionOptions.
For example this would be the most unsafe, but probably most "allowing" way:
var options = new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted };
using (var tranScope = new TransactionScope(TransactionScopeOption.Required, options))
{
But please consider before you simply use this: an Isolation level is a good thing. It helps keeping your data in a stable state. Ignoring it will lead to all kinds of strange errors. Your database has tremendous power to help you, don't just turn it off because it seems hard to do it correctly.
I've looked around but couldn't find an appropriate (or one that is satisfactory to me) on how to address an issue we are having.
I use Dapper and ODP.NET 12 Managed drivers. The problem is not encountered when TransactionScope is not used.
When performing commands under a transaction scope, I get an error "Transaction has aborted" via the TransactionAbortedException thrown.
Observed behaviors:
1) TransactionAbortedException is thrown if and only if the transaction is completed and the TransactionScope is disposed. The point at when the exception is thrown is during dispose.
2) Despite the exception, the transaction concept actually works! After Complete() is invoked, the changes are committed into the database.
Below is the code snippet.
// Conn string: "Data Source=OraDB;Persist Security Info=True;User ID=userxxx;Password=passwordxxx;" providerName="Oracle.ManagedDataAccess.Client
// Note: GetDbFactory().Create() returns a DbConnection object
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
using (var dbConn = GetDbFactory().Create())
{
foreach (MyDTO dto in dtoList)
{
var tableDAO= new TableDAO(dbConn);
MyEntity entity = new MyEntity()
{
Field1 = dto.Field1,
Field2 = dto.Field2
};
tableDAO.AddOrUpdate(entity);
}
// Commit changes
scope.Complete();
}
// This method is under the DAO class
public void AddOrUpdate(MyEntity entity)
{
// Verify arguments
entity.AsArgumentThrowExceptionIfNull("entity");
// build param
OracleDynamicParameters parameters = new OracleDynamicParameters();
parameters.Add("P_FIELD1", entity.Field1);
parameters.Add("P_FIELD2", entity.Field2);
// execute SP
dbConnection.Execute("PRC_MY_ENTITY_ADDORUPDATE", parameters, commandType: CommandType.StoredProcedure);
}//-- end AddOrUpdate()
==================================================================
UPDATE (09-Apr-15)
I have changed my approach and use the following pattern for now for Oracle. Our code deals with connections in both Oracle and SQL Server so I'd much prefer that the coding pattern is consistent, but until a solution is found with using Oracle+TransactionScope, we'll use the pattern below for Oracle command execution:
using (var dbConnection = dbConnFactory.Create())
{
// Open db connection
dbConnection.Open();
using (var trans = dbConnection.BeginTransaction())
{
bool isSuccess = false;
try
{
// Perform DB operations here
trans.Commit();
isSuccess = true;
}
finally
{
if(!isSuccess) trans.Rollback();
}
}
}
First, the exception is documented as possible:
A call to the Dispose method marks the end of the transaction scope. Exceptions that occur after calling this method may not affect the transaction.
This is in the documentation on the TransactionScope class (https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28v=vs.110%29.aspx).
One other thing I noticed is that the complete on the transaction is being before the connection is closed. I'd change this to close the connection and then complete the transaction.
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
{
using (var dbConn = GetDbFactory().Create())
{
foreach (MyDTO dto in dtoList)
{
var tableDAO= new TableDAO(dbConn);
MyEntity entity = new MyEntity()
{
Field1 = dto.Field1,
Field2 = dto.Field2
};
tableDAO.AddOrUpdate(entity);
}
}
// Commit changes
scope.Complete();
}
I don't see anything wrong with that code. However, if that loop runs long enough, the transaction is going to timeout. You'll then get the exception in question the next time you do an operation against the database. I would try increasing the timeout - Timeout is a property on the TransactionScopeOption class.
I am resorting to using BeginTransaction() as the final approach (refer to my update in my original post). I have read more about why TransactionScope() was failing.
1) ODP.Net promotes to distributed transaction even when using a single DB connection when connecting to Oracle 10g and below (source). Lo and behold, the database I'm connecting to is indeed 10g.
2) You'll need Oracle MTS Service installed. This I didn't have setup on my dev machine.
I'm using one TransactionScope per request, something like this:
public ActionResult Login(string user, string pass)
{
using (ServerContext context = new ServerContext ())
{
TransactionOptions transOptions = new TransactionOptions();
transOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
transOptions.Timeout = TransactionManager.MaximumTimeout;
using (var scope = new TransactionScope(TransactionScopeOption.Required, transOptions))
{
// Some logic and Linq queries here
}
}
I perform some inserts, updates, deletes and proc calls inside the transaction scope
But, I got some rare DeadLock expceptions with high loads. So, i'm doing it right?
Or it's better to open a TransactionScope per operation? (But I need to rollback all operation if one of the set fails.)
Thnak you.
AFAIK, in L2S When you call SubmitChanges, LINQ to SQL checks to see whether the call is in the scope of a Transaction or if the Transaction property (IDbTransaction) is set to a user-started local transaction. If it finds neither transaction, LINQ to SQL starts a local transaction (IDbTransaction) and uses it to execute the generated SQL commands. When all SQL commands have been successfully completed, LINQ to SQL commits the local transaction and returns.
I have no ideas on MVC/EF but try wrapping like this :
using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
using (ServerContext context = new ServerContext ())
{
// Some logic and Linq queries here
}
scope.Complete();
}
I would like to run multiple insert statements on multiple tables. I am using dapper.net. I don't see any way to handle transactions with dapper.net.
Please share your ideas on how to use transactions with dapper.net.
Here the code snippet:
using System.Transactions;
....
using (var transactionScope = new TransactionScope())
{
DoYourDapperWork();
transactionScope.Complete();
}
Note that you need to add reference to System.Transactions assembly because it is not referenced by default.
I preferred to use a more intuitive approach by getting the transaction directly from the connection:
// This called method will get a connection, and open it if it's not yet open.
using (var connection = GetOpenConnection())
using (var transaction = connection.BeginTransaction())
{
connection.Execute(
"INSERT INTO data(Foo, Bar) values (#Foo, #Bar);", listOf5000Items, transaction);
transaction.Commit();
}
There are 3 approaches to doing transactions in Dapper.
Simple Transaction
Transaction from Transaction Scope
Using Dapper Transaction (additional nuget package and most favored approach)
You can find out more about these transaction approaches from the official tutorial website here
For reference here's a breakdown of the transaction approaches
1. Simple Transaction
In this example, you create a transaction on an existing db connection, and then pass in the transaction to the Execute method on dapper (which is an optional parameter).
Once you've done all your work, simply commit the transaction.
string sql = "INSERT INTO Customers (CustomerName) Values (#CustomerName);";
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
connection.Execute(sql, new {CustomerName = "Mark"}, transaction: transaction);
connection.Execute(sql, new {CustomerName = "Sam"}, transaction: transaction);
connection.Execute(sql, new {CustomerName = "John"}, transaction: transaction);
transaction.Commit();
}
}
2. Transaction from Transaction Scope
If you'd like to create a transaction scope, you will need to do this before the db connection is created. Once you've created the transaction scope, you can simply perform all your operations and then do a single call to complete the transaction, which will then commit all the commands
using (var transaction = new TransactionScope())
{
var sql = "INSERT INTO Customers (CustomerName) Values (#CustomerName);";
using (var connection = My.ConnectionFactory())
{
connection.Open();
connection.Execute(sql, new {CustomerName = "Mark"});
connection.Execute(sql, new {CustomerName = "Sam"});
connection.Execute(sql, new {CustomerName = "John"});
}
transaction.Complete();
}
3. Using Dapper Transaction
In my opinion, this is the most favorable approach to achieve transaction in code, because it makes the code easy to read and easy to implement. There is an extended implementation of SQL Transaction called Dapper Transaction (which you can find here), which allows you to run the SQL executes off the transactions directly.
string sql = "INSERT INTO Customers (CustomerName) Values (#CustomerName);";
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
transaction.Execute(sql, new {CustomerName = "Mark"});
transaction.Execute(sql, new {CustomerName = "Sam"});
transaction.Execute(sql, new {CustomerName = "John"});
transaction.Commit();
}
}
You should be able to use TransactionScope since Dapper runs just ADO.NET commands.
using (var scope = new TransactionScope())
{
// open connection
// insert
// insert
scope.Complete();
}
Considering all your tables are in single database, I disagree with TransactionScope solution suggested in some answers here. Refer this answer.
TransactionScope is generally used for distributed transactions; transaction spanning different databases may be on different system. This needs some configurations on operating system and SQL Server without which this will not work. This is not recommended if all your queries are against single instance of database.
But, with single database this may be useful when you need to include the code in transaction that is not under your control. With single database, it does not need special configurations either.
connection.BeginTransaction is ADO.NET syntax to implement transaction (in C#, VB.NET etc.) against single database. This does not work across multiple databases.
So, connection.BeginTransaction() is better way to go.
Even the better way to handle the transaction is to implement UnitOfWork as explained in this answer.
Daniel's answer worked as expected for me. For completeness, here's a snippet that demonstrates commit and rollback using a transaction scope and dapper:
using System.Transactions;
// _sqlConnection has been opened elsewhere in preceeding code
using (var transactionScope = new TransactionScope())
{
try
{
long result = _sqlConnection.ExecuteScalar<long>(sqlString, new {Param1 = 1, Param2 = "string"});
transactionScope.Complete();
}
catch (Exception exception)
{
// Logger initialized elsewhere in code
_logger.Error(exception, $"Error encountered whilst executing SQL: {sqlString}, Message: {exception.Message}")
// re-throw to let the caller know
throw;
}
} // This is where Dispose is called