How to rollback the datacontext to the point of the last submitchanges - c#

I am working on an app that is inserting a lot of new objects (rows) and relationships between them. But at a certain point when an error occurs I want all the changes to the DataContext be disgarded and "thrown away". So that after an error I have a clean copy of the DataContext that matches the state of the database.

Edit
Alternatively, you could make use of the DataContext.Transaction, and use that to .Commit() or .Rollback() your changes.
ORIG
Just throw away that DataContext & Re-instantiate it.
Something like...
public void MyMethod(string connStr)
{
try
{
DataClasses1DataContext dc = new DataClasses1DataContext(connStr);
for (int i = 0; i < 100; i++)
{
try
{
//Do Stuff
//Insert Objects
dc.SubmitChanges();
}
catch (Exception ex) //So if it bombs in the loop, log your exception
{
Log(ex);
}
finally //Reinstantiate your DC
{
dc = new DataClasses1DataContext(connStr);
}
}
}
catch (Exception bigEx)
{
Log(bigEx);
}
}

You could also use the TransactionScope in a using statement. If you don't call .Complete() on the TransactionScope, all changes are rolled back when it is disposed (which happens when leaving the using statement).

Related

LINQ to SQL not creating Transactions

I've been searching everywhere, to try and get over this issue but I just can't figure this out.
I'm trying to make many changes to the DB with one single transaction using LINQ to SQL.
I've created a .dbml that represents the SQL Table, then I use basicaly this code:
foreach (var _doc in _r.Docs)
{
try
{
foreach (var _E in _Es)
{
Entity _newEnt = CreateNewEnt(_EListID, _doc, _fileName, _E);
_db.Etable.InsertOnSubmit(_newEnt);
_ECount++;
if (_ECount % 1000 == 0)
{
_db.SubmitChanges();
}
}
}
catch (Exception ex)
{
throw;
}
}
But when I do a SQL Profiler, the commands are all executed individually. It won't even start an SQL Transaction.
I've tried using TransactionScope (using statement and Complete()) and DbTransaction (BeginTransaction() and Commit()), none of them did anything at all, it just keeps on executing all commands individually, inserting everything like it was looping through all the inserts.
TransactionScope:
using(var _tans = new TransactionScope())
{
foreach (var _doc in _r.Docs)
{
try
{
foreach (var _E in _Es)
{
Entity _newEnt = CreateNewEnt(_EListID, _doc, _fileName, _E);
_db.Etable.InsertOnSubmit(_newEnt);
_ECount++;
if (_ECount % 1000 == 0)
{
_db.SubmitChanges();
}
}
}
catch (Exception ex)
{
throw;
}
}
_trans.Complete();
}
DbTransaction:
_db.Transaction = _db.Connection.BeginTransaction();
foreach (var _doc in _r.Docs)
{
try
{
foreach (var _E in _Es)
{
Entity _newEnt = CreateNewEnt(_EListID, _doc, _fileName, _E);
_db.Etable.InsertOnSubmit(_newEnt);
_ECount++;
if (_ECount % 1000 == 0)
{
_db.SubmitChanges();
}
}
}
catch (Exception ex)
{
throw;
}
}
_db.Transaction.Commit();
I also tried commiting transactions everytime I Submit the changes, but still nothing, just keeps on executing everything individually.
Right now I'm at a loss and wasting time :\
GSerg was right and pointed me to the right direction, Transactions do not mean multiple commands in one go, they just allow to "undo" all that was made inside given transaction if need be. Bulk statements do what I want to do.
You can download a Nuget Package directly from Visual Studio called "Z.LinqToSql.Plus" that helps with this. It extends DataContext from LINQ, and allows to do multiple insertions, updates or deletes in bulks, which means, in one single statement, like this:
foreach (var _doc in _r.Docs)
{
try
{
foreach (var _E in _Es)
{
Entity _newEnt = CreateNewEnt(_EListID, _doc, _fileName, _E);
_dictionary.add(_ECount, _newEnt); //or using a list as well
_ECount++;
if (_ECount % 20000 == 0)
{
_db.BulkInsert(_dictionary.Values); //inserts in bulk, there are also BulkUpdate and BulkDelete
_dictionary = new Dictionary<long, Entity>(); //restarts the dictionary to prepare for the next bulk
}
}
}
catch (Exception ex)
{
throw;
}
}
As in the code, I can even insert 20k entries in seconds. It's a very useful tool!
Thank you to everyone who tried helping! :)

Connection must be valid and open to commit transaction for sqllite database while retrying to execute the failed query

public static void SetStatus( Status statusObject,int retryCount)
{
if (statusObject != null)
{
using (SqliteConnection dbConn = new SqliteConnection(dbURL))
{
IDbTransaction dbTransaction = null;
try
{
dbConn.Open();
dbTransaction = dbConn.BeginTransaction();
new SqliteCommand(some_query, dbConn).ExecuteNonQuery();
}
catch (Exception e)
{
dbTransaction.Rollback();
dbConn.Close();
if (retryCount > 0)
{
SetStatus(statusObject, --retryCount);
return;
}
else
throw e;
}
finally
{
try { dbTransaction.Commit(); }
catch (Exception e)
{
}
}
}
}
}
Whenever ExecuteNonQuery fails due due to some exception I have a retry mechanism which will run the same query again.In that case during the second time(while retrying) the following exception comes-
"Connection must be valid and open to commit transaction"
The problem is basically, that you commit the transaction in the finally block—which runs always per design—even though you may already have rolled back the transaction and closed the connection in the catch block.
The easier way would be to commit the transaction while still in the try block, because after all, you only want to commit the transaction if all the code in the try succeeded.
That being said, you are having some odd nesting with the try and using constructs there. You should try to keep things like usings as concise as possible. That way you will also not have to deal with closing connections yourself. Something like this:
public static void SetStatus(Status statusObject, int retryCount)
{
if (statusObject == null)
return;
try
{
using (var dbConn = new SqliteConnection(dbURL))
{
IDbTransaction dbTransaction = null;
try
{
dbConn.Open();
dbTransaction = dbConn.BeginTransaction();
new SqliteCommand(some_query, dbConn).ExecuteNonQuery();
dbTransaction.Commit();
}
catch
{
// transaction might be null if the connection couldn’t be opened
dbTransaction?.Rollback();
throw;
}
}
}
catch (Exception ex)
{
if (retryCount > 0)
SetStatus(statusObject, --retryCount);
else
throw ex;
}
}
One final note, one could argue that using a transaction is absolutely not necessary here: You are only executing a single query on this database connection, so either that works, or it will fail in which case there is not something to rollback anyway. So unless you are having multiple queries there, you don’t need an explicit transaction (but I’ll just assume that you just reduced your example to show just a single query).
My bad,screwed up using "finally" improperly myself.So if the query fails at first attempt it would go to catch block which will fire the call to re run the query.Second time query execution would be successful and transaction would be committed.Then the control goes to finally of first query which will again try to commit the already rolled back transaction. So,it would throw that exception.
So,if the moving the commit to main try block fixed the issue.

Dispose not working, many dead connections

I'm getting strange things since updated to EF6,no sure this is related or not, but used to be good
I'm doing a set of work, then save it to DB , then do another , save another.
after a while,i check SQL server by sp_who2 , i found many dead connections from my computer.
Job is huge then there goes to 700 connections,
I have to kill them all manually in cycle.
program like:
while (jobDone == false)
{
var returnData=doOneSetJob();
myEntity dbconn= new myEntity;
foreach( var one in retrunData)
{
dbconn.targetTable.add(one );
try
{
dbconn.savechange();
/// even i put a dispose() here , still lots of dead connections
}
catch
{
console.writeline("DB Insertion Fail.");
dbconn.dispose();
dbconn= new myEntity();
}
}
dbconn.dispose()
}
You should consider refactoring your code so that your connection is cleaned up after your job is complete. For example:
using (var context = new DbContext())
{
while (!jobDone)
{
// Execute job and get data
var returnData = doOneSetJob();
// Process job results
foreach (var one in returnData)
{
try
{
context.TargetTable.Add(one);
context.SaveChanges();
}
catch (Exception ex)
{
// Log the error
}
}
}
}
The using statement will guarantee that your context is cleaned up properly, even if an error occurs while you are looping through the results.
In this case you should use a using statement. Taken from MSDN:
The using statement ensures that Dispose is called even if an exception occurs while you are calling methods on the object. You can achieve the same result by putting the object inside a try block and then calling Dispose in a finally block; in fact, this is how the using statement is translated by the compiler.
So, your code would look better like this:
using(var dbconn = new DbContext())
{
while (!jobDone)
{
foreach(var one in retrunData)
{
try
{
targetTable row = new TargetTable();
dbconn.TargetTable.add(row);
dbconn.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine("DB Insertion Fail.");
}
}
}
}
This way, even if your code fails at some point, the Context, resources and connections will be properly disposed.

Cant get Exceptions & Dispose() to work in this method

I have the following function which reads from a firebird database. The Function works but does not handle exceptions (Required).
public IEnumerable<DbDataRecord> ExecuteQuery(string Query)
{
var FBC = new FbCommand(Query, DBConnection);
using (FbDataReader DBReader = FBC.ExecuteReader())
{
foreach (DbDataRecord record in DBReader)
yield return record;
}
}
Adding try/catch to this function gives an error regarding yield. I understand why I get the error but any workround I've tried has resulted in DBReader being disposed indirectly via using() too early or Dispose() not being called all. How do I get this code to use Exceptions & Cleanup without having to wrap the method or duplicate DBReader which might contain several thousand record?
Update:
Here is an example of an attempted fix. In this case DBReader is being disposed too early.
public IEnumerable<DbDataRecord> ExecuteQuery(string Query)
{
var FBC = new FbCommand(Query, DBConnection);
FbDataReader DBReader = null;
try
{
using (DBReader = FBC.ExecuteReader());
}
catch (Exception e)
{
Log.ErrorException("Database Execute Reader Exception", e);
throw;
}
foreach (DbDataRecord record in DBReader) <<- DBReader is closed at this stage
yield return record;
}
The code you've got looks fine to me (except I'd use braces round the yield return as well, and change the variable names to fit in with .NET naming conventions :)
The Dispose method will only be called on the reader if:
Accessing MoveNext() or Current in the reader throws an exception
The code using the iterator calls dispose on it
Note that a foreach statement calls Dispose on the iterator automatically, so if you wrote:
foreach (DbDataRecord record in ExecuteQuery())
{
if (someCondition)
{
break;
}
}
then that will call Dispose on the iterator at the end of the block, which will then call Dispose on the FbDataReader. In other words, it should all be working as intended.
If you need to add exception handling within the method, you would need to do something like:
using (FbDataReader DBReader = FBC.ExecuteReader())
{
using (var iterator = DBReader.GetEnumerator())
{
while (true)
{
DbDataRecord record = null;
try
{
if (!iterator.MoveNext())
{
break;
}
record = iterator.Current;
}
catch (FbException e)
{
// Handle however you want to handle it
}
yield return record;
}
}
}
Personally I'd handle the exception at the higher level though...
This line won't work, note the ; at the end, it is the entire scope of the using()
try
{
using (DBReader = FBC.ExecuteReader())
; // this empty statement is the scope of using()
}
The following would be the correct syntax except that you can't yield from a try/catch:
// not working
try
{
using (DBReader = FBC.ExecuteReader())
{
foreach (DbDataRecord record in DBReader)
yield return record;
}
}
catch (Exception e)
{
Log.ErrorException("Database Execute Reader Exception", e);
throw;
}
But you can stay a little closer to your original code:
// untested, ought to work
FbDataReader DBReader = null;
try
{
DBReader = FBC.ExecuteReader();
}
catch (Exception e)
{
Log.ErrorException("Database Execute Reader Exception", e);
throw;
}
using (DBReader)
{
foreach (DbDataRecord record in DBReader) // errors here won't be logged
yield return record;
}
To catch errors from the read loop as well see Jon Skeet's answer.

how to be sure in Linq to SQL that record has been added successfully or not?

I am just learning Linq to SQL and don't know much. Kindly guide and help me.
I am adding a new record by using a SubmitChanges(); How I can be confirmed that record has been added? For example, in using stored procedures we use to send a flag back to application but how is it done in LINQ to SQL? Please guide me.
Allow your code to flow out of your method. Only when an exception is throw has your statement not completed.
If you wanted a 'flag', you could return a bool.
public bool AddCustomer()
{
try{
....
db.SubmitChanges();
return true;
}
catch(Exception e)
{
...
return false;
}
}
You could do something like this;
public void Save()
{
Northwnd db = new Northwnd(#"c:\northwnd.mdf");
// Make changes here.
try
{
db.SubmitChanges();
}
catch (Exception err)
{
//Log error
}
}
If no exception is thrown you can assume the data is saved correct. Though another option is something like this.
public bool Save()
{
Northwnd db = new Northwnd(#"c:\northwnd.mdf");
// Make changes here.
try
{
db.SubmitChanges();
return true;
}
catch (Exception e)
{
//Log the error
return false;
}
}
True will be returned if save succeeds, otherwise false will be returned
See: MSDN, How to: Submit Changes to the Database
Note that there is an overload of SubmitChanges() that will let you specidy how to handle conflicts.
Check this http://msdn.microsoft.com/en-us/library/bb399378.aspx
At this point, any errors detected by the database cause the submission process to stop, and an exception is raised. All changes to the database are rolled back as if no submissions ever occurred. The DataContext still has a full recording of all changes. You can therefore try to correct the problem and call SubmitChanges again
Northwnd db = new Northwnd(#"c:\northwnd.mdf");
// Make changes here.
try
{
db.SubmitChanges();
}
catch (ChangeConflictException e)
{
Console.WriteLine(e.Message);
// Make some adjustments.
// ...
// Try again.
db.SubmitChanges();
}

Categories