Good day,
I have a method which contains several database commits (about 15) in a different classes, I need a way to make all the db changes only if the the method did not throw any exception, I am thinking about using a Transaction Scope, and my question is weather i can use a single instance of that Transaction Scope in all the different classes and if not what is the best practice to perform a rollback in case of an exception?
Thanks!
You usually don't need to perform rollback explicitly, methods that perform database operations might not even be aware of ambient transaction (that is - you don't need to pass TransactionScope to them). Just do:
using (var tran = new TransactionScope()) {
FirstDatabaseOperation();
SecondDatabaseOperation();
// etc
tran.Complete();
}
If exception happens in any operation - transaction will be rolled back for you, because TransactionScope will be disposed.
Related
I try to use TransactionScope in this way:
using (TransactionScope ts = new TransactionScope())
{
DAL.delete();
DAL.create();
ts.Complete();
}
where I have independent DAL(Data Access Layer) module to do database operations. Each operation like delete(), create() is atomic, i.e. They are all committed by calling.
I have tried this code in order to wrap this two operation together as a transaction. And no matter whether I wrote ts.Complete();, they are all committed to database and no rollback happens.
How can I do in this case? Thanks.
The TS is creating an ambient transaction which your DAL layer will automatically pick up on. Your code implies you want the delete and create to be considered an atomic operation. If you want them independent create another TS block after the first and move your create statement there.
If you want to rollback you need to leave the using block without called Complete on the scope, normally this happens because one of your DAL methods throws an exception.
I always want to try to use TransactionScope but I just can't figure out what people see about it that is useful. So let's take an example:
using(TransactionScope tran = new TransactionScope()) {
CallAMethodThatDoesSomeWork1();
CallAMethodThatDoesSomeWork2();
tran.Complete();
}
So the most basic question: How do I write "CallAMethodThatDoesSomeWork1()" so that it knows how to roll its actions back if let's say "CallAMethodThatDoesSomeWork2()" throws an exception?
The code within the methods you call need to be transaction aware and enlist in the active transaction. This means creating or using classes which are resource managers (see Implement Your Own Resource Manager.
You do this by implementing IEnlistmentNotification and enlisting in the transaction. When the transaction is completed, the transaction manager will call methods as defined on that interface so that your code can do/undo the work.
Here is a quote " So, if you are working with only one object context then you have already built-in support for database transactions when using the ObjectContext.SaveChanges method." I found here http://www.luisrocha.net/2011/08/managing-transactions-with-entity.html
So according to that, I don't have to use TransactionScope in a code below, right?
if (isLastCallSuccess)
{
if (condition1) //it's clear, no transaction needed
{
product.Property1 = true;
context.SaveChanges();
}
else if (condition2)
{
using (TransactionScope scope = new TransactionScope()) //do I need it?
{
context.DeleteObject(item); //deleting
context.AddObject("product", new product //adding
{
Id = oldObject.Id,
Property1 = true
});
context.SaveChanges(System.Data.Objects.SaveOptions.DetectChangesBeforeSave);
scope.Complete();
context.AcceptAllChanges();
}
}
What the quote means is that a single call to SaveChanges is automatically wrapped in a transaction, so it's atomic. You, however, are calling SaveChanges multiple times, so in order for the larger operation to be atomic you'll need to use the transaction as you currently have it.
So yes, you need it.
I would personally keep TransactionScope in so everything commits as a whole unit or rollsback upon an error (I.e. your save or add fails). If concurrency is a major part of your application using this will benefit your users, ensuring the integrity of the data is consistent.
I believe in your scenario you do need to use a transaction. SaveChanges creates an implicit transaction such that when it goes to persist a change to any of the objects, and that change cannot be persisted, it rolls back all other changes it attempted to make. But the transaction created by SaveChanges only lives as long as the call itself. If you are calling SaveChanges twice and want the actions of the first call to rollback if the second call fails, then yes, you need a transaction that wraps both calls, which the code you posted does just that.
I disagree; because you have multiple operations on your data, and you would want to make sure that the operations either succeed completely or fail completely (atomic). It also is good practice to make sure you are atomic.
If your delete worked, but your add failed, you would be left with a database in a bad state. At least if you had a transaction, the database would be back to the original state before you attempted the first operation.
EDIT:
Just for completion, inside a transaction, having the ability to rollback a transaction at any point is crucial, when you start to manipulate multiple tables in the same method/process.
From how I am reading it, you are worried about the delete and adding not committing to the database and if there is a fail then rolling the transaction back.
I dont think you need to wrap your insert and delete in a transaction, because as mentioned above it is all happening on one savechanges() which implicitly has transaction management. so if it did fail the changes would be rolled back.
Does TransactionScope make any difference when there is just one insert in the table?
Is
MyObjectContext.Messages.Save( message, m => m.ID == message.ID);
MyObjectContext.SaveChanges();
any different from
using( var ts = new TransactionScope() )
{
MyObjectContext.Messages.Save( message, m => m.ID == message.ID);
MyObjectContext.SaveChanges();
ts.Complete();
}
and how exactly?
There is a difference. If you use just SaveChanges you still have a transaction but it has default isolation level for database server - in case of SQL server it is Read committed. If you use TransactionScope with default configuration you have Serialized transaction isolation level but you can change it if you use other constructor of TransactionScope.
So it makes a difference if you need control over transaction isolation level.
It doesn't matter if you are saving one item or multiple items, the use of a TransactionScope is redundant here.
From the documentation for ObjectContext.SaveChanges:
SaveChanges operates within a
transaction. SaveChanges will roll
back that transaction and throw an
exception if any of the dirty
ObjectStateEntry objects cannot be
persisted.
So you are layering on a TransactionScope in your example with no added benefit.
Now, if you had two separate ObjectContext instances with separate sets of data that you wanted to ensure that both were saved, then you would absolutely need the TransactionScope around both calls to ObjectContext.SaveChanges.
No, there is no difference.
SaveChanges operates within a transaction. SaveChanges will roll back that transaction and throw an exception if any of the dirty ObjectStateEntry objects cannot be persisted.
I want to create a transaction, writing some data in a sub-transaction, reading the data back, and rollback the transaction.
using(var transaction = new TransactionScope())
{
using(var transaction = new TransactionScope())
{
// save data via LINQ / DataContext
transaction.Complete();
}
// Get back for assertions
var tempItem = // read data via LINQ / DataContext THROWS EXCEPTION
}
But while reading I get "System.Transactions.TransactionException : The operation is not valid for the state of the transaction.".
How should I set transaction properties to avoid this?
This exception cannot be debugged without the full stack trace. It has a different meaning depending on the context. Usually it means you're doing something you shouldn't inside the transaction, but without seeing db calls or stack trace all anybody can do is guess. Some common causes I know of (and this is by no means comprehensive I'm sure) include:
Accessing multiple data sources (ie different connection strings) within a nested TransactionScope. This causes promotion to a distributed transaction and if you do are not running DTC it will fail. The answer is usually not to enable DTC, but to clean up your transaction or wrap the other data access with a new TransactionScope(TransactionOptions.RequiresNew).
Unhandled exceptions within the TransactionScope.
Any operation that violates the isolation level, such as trying to read rows just inserted/updated.
SQL deadlocks; transactions can even deadlock themselves in certain cases, but if #1 applies, isolating other ops into new transactions can cause deadlocks if you're not careful.
Transaction timeouts.
Any other error from the database.
I definitely don't know every possible cause, but if you post the complete stack trace and the actual db calls in your code I'll take a look and let you know if I see anything.
You have two nested TransactionScope objects??
And no try catch block.
http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx
I think you'll find the specific answer is that you cannot complete a transaction that hasn't begun anything, it's in an invalid state. Do you actually have any code where your LINQ comments are? does a connection actually get established?