I get some data from a source and I want to add them in the database, so I open a context for each batch and use a transaction to commit the changes after all the data was added.
My code:
using (var conn = new SqlConnection(dataAccessSettings.DatabaseConnectString))
{
conn.Open();
using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
foreach (var dataBatch in GetData())
{
using (var context = new DataContext(conn, false))
{
context.Database.UseTransaction(sqlTxn);
context.DataTable.AddRange(dataBatch);
context.SaveChanges();
}
}
sqlTxn.Commit();
}
}
However, when the second context calls the context.Database.UseTransaction(sqlTxn) I get the following error:
The transaction passed in must have a non-null connection. A null connection indicates the transaction has already been completed.
I set the contextOwnsConnection constructor parameter of the context to false so what exactly is closing my connection?
You must remove SaveChanged in loop statement.
If you call it inside the loop, EF will write back the changes to the database for every single entity (and every entity will be in its own, separate transaction), One call with 50 changes is typically much better / faster / more efficient than 50 calls for 1 change each.
Change Code for it
using (var conn = new SqlConnection(dataAccessSettings.DatabaseConnectString))
{
conn.Open();
using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
foreach (var dataBatch in GetData())
{
using (var context = new DataContext(conn, false))
{
context.Database.UseTransaction(sqlTxn);
context.DataTable.AddRange(dataBatch);
// remove SaveChanges here
}
}
sqlTxn.Commit();
context.SaveChanges();
}
}
based off : Using Transactions or SaveChanges(false) and AcceptAllChanges()?
using (var conn = new SqlConnection(dataAccessSettings.DatabaseConnectString))
using (var context = new BloggingContext(conn, false))
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
foreach (var dataBatch in GetData())
{
context.Database.UseTransaction(sqlTxn);
context.DataTable.AddRange(dataBatch);
}
context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback(); //Required according to MSDN article
throw; //Not in MSDN article, but recommended so the exception still bubbles up
}
}
}
}
Related
In one of my previous questions someone suggested that instead of using trycatch and closing the connection at finally I should just use "using". Can someone give me an example?
I have this code, how do I use just "using" with it?
try
{
conn3.Open();
string sql_check = "SELECT (time_in) FROM timeinout WHERE employee_id = #employee_id AND date = #date";
using var cmd_check = new NpgsqlCommand(sql_check, conn3);
cmd_check.Parameters.AddWithValue("employee_id", id);
cmd_check.Parameters.AddWithValue("date", date);
cmd_check.Prepare();
var reader = cmd_check.ExecuteReader();
if (reader.Read())
{
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
finally
{
conn3.Close();
}
using is not a replacement for try...catch...finally. It enables you to do away with the finally block but, if an exception can be thrown, you still need to catch it. The using block guarantees that the object created will be disposed, whether an exception is thrown or not. This:
var obj = new SomeType();
try
{
// Use obj here.
}
catch
{
// Handle exception here.
}
finally
{
obj.Dispose();
}
can AND SHOULD be replaced with this:
using (obj = new SomeType())
{
try
{
// Use obj here.
}
catch
{
// Handle exception here.
}
}
Basically, if already have exception handling then you still need exception handling but you don't need to close/dispose locally-created objects in a finally block. If you're doing anything else in your finally block then you still need it too.
In your case, you should be creating your connection, command and data reader with using statements:
using (var connection = new NpgsqlConnection(connectionString))
using (var command = new NpgsqlCommand(query, connection))
{
try
{
connection.Open();
using (var reader = command.ExecuteReader())
{
return reader.HasRows;
}
}
catch
{
return false;
}
}
The data reader will be closed at the end of the inner using block and the connection will be closed at the end of the outer using block.
I have a function that is being called recursively. In this function, a TransactionScope object with Isolation level ReadUncommitted is being used. The strange behaviour is that the first time, it hits Database, locks the table and after the completion of first iteration, it releases the table and the behaviour is fine. Can anyone explain why? Please note that I'm using Core ADO.Net. No ORM. My functon looks like the following:
public void ExportTransactionsData()
{
var now = DateTime.Now;
var slash = ConfigurationReader.PathCharacterToUse;
var startId = 0L;
var endId = 0L;
var hasMoreRecords = false;
using (var transScope = UtilityMethods.TransactionScope(IsolationLevel.ReadUncommitted, TransactionScopeOption.Required))
{
using (var stream = _provider.GetTransactionsXml(out startId, out endId, out hasMoreRecords))
{
if (stream.Length > 0)
{
var filePath = $"{ConfigurationReader.TransactionFilePath}{slash}EZFareTransactions_{startId}_{endId}_{now.ToString("yyyyMMddTHHmmss")}.xml";
using (IUploadClient client = UploadClientFactory.Create(ConfigurationReader.UploadType))
{
client.Upload(filePath, stream);
}
}
}
transScope.Complete();
}
if (hasMoreRecords)
{
ExportTransactionsData();
}
}
I have question regarding Entity Framework. In my program I first fill-up my DbContext with data. Then I need to delete all the data from the tables in DB, but only if new data will be saved. If db.Savechanges() throws an exception I need my old data to still be in the tables.
My code is:
static void Main(string[] args)
{
PdmContext db = new PdmContext();
FillDbContext();
try
{
if (db.SaveChanges() > 0)
{
using (var del = new PdmContext())
{
DeleteModel.deleteFromAllTables();
}
db.SaveChanges();
}
}
catch (Exception exp)
{
Logger.Log("Exception (global catch));
}
}
I can't seem to figure this out. Anyone can help with this? :)
You can use Transaction which will make sure to revert the operation done within the scope of it if the operation fails at some stage :
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
using (var del = new PdmContext())
{
DeleteModel.deleteFromAllTables();
}
db.SaveChanges();
scope.Complete(); // commits the transaction
}
Now the changes to the database will be atomic so that it will only keep all changes or not at all. I have not included exception handling code for simplicity but due to any reason if the scope.Complete() was not being executed and control exists the transaction block without executing that the transaction will get rolled back.
You need to use a Transaction.
see how to use that:
using (var dbContextTransaction = PdmContext.Database.BeginTransaction())
{
try
{
// HERE your operation insert etc.
PdmContext.SaveChanges();
dbContextTransaction.Commit(); // here, apply your operation
}
catch (Exception)
{
dbContextTransaction.Rollback(); // here, undo your operations
}
}
You can handle such scenario with transaction management.
There is two way to handle it.
1) You can use single dbcontext for all operation instead of create multiple for single operation.
using (var context = new SchoolContext())
{
try
{
context.Students.Add(new Student()
{
FirstName = "Rama2",
StandardId = standard.StandardId
});
context.Courses.Add(new Course() { CourseName = "Computer Science" });
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine("Error occurred.");
}
}
2) Using single DbContextTransaction object:
using (var context = new SchoolContext())
{
context.Database.Log = Console.Write;
using (DbContextTransaction transaction = context.Database.BeginTransaction())
{
try
{
context.Students.Add(new Student()
{
FirstName = "Rama2",
StandardId = standard.StandardId
});
context.SaveChanges();
context.Courses.Add(new Course() { CourseName = "Computer Science" });
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine("Error occurred.");
}
}
}
I hope it work for you.
Here's my code:
TransactionScope trans = new TransactionScope();
dbDataContext db = new dbDataContext()
// did some insert
var user = new User();
user.ID = 1;
user.Name = "Nick";
db.Users.Add(user);
db.SubmitChanges();
trans.Complete();
Now how do I rollback transaction if some error occurs while saving changes?
There are two ways in which you can use transactions in entity framework. One using TransactionScope and another using database transaction.
For using database transaction, use the following code example:
using (dbDataContext db = new dbDataContext())
using (var dbContextTransaction = db.Database.BeginTransaction())
{
try
{
var user = new User(){ID = 1, Name = "Nick"};
db.Users.Add(user);
db.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
And here's an example of EF's TransactionScope:
using (TransactionScope tranScope = new TransactionScope())
using (dbDataContext db = new dbDataContext())
{
try
{
var user = new User(){ID = 1, Name = "Nick"};
db.Users.Add(user);
db.SaveChanges();
tranScope.Complete();
}
catch(Exception ex){}
}
As you can see, you don't need to call any Roll-Back method in the case of TransactionScope. If you don't call the Complete() method, the transaction does not get committed and gets rolled back automatically before being disposed.
I need to access data from two different DbContexts at the same time, making sure each uses READ UNCOMMITTED for their queries (really, the important thing is that it doesn't lock the rows it iterates over - adding WITH NO LOCK to the query would work too).
How can you do this using Entity Framework? If I wrap each of the two queries in a TransactionScope, it tries to promote the transaction to MSDTC which isn't an option for us.
private static IEnumerable<Image> EnumerateSourceImages()
{
using (var dbContext = new SourceDbContext())
{
using (var transScope = new TransactionScope(
TransactionScopeOption.RequiresNew,
new TransactionOptions() {
IsolationLevel = IsolationLevel.ReadUncommitted
}
)
)
{
var imagesSourceQuery = dbContext.ImageDatas
.AsNoTracking()
.OrderBy(imageData => imageData.ImageID)
foreach (var image in imagesSourceQuery)
{
yield return image;
}
transScope.Complete();
}
}
}
private static IEnumerable<Image> EnumerateDestinationImages()
{
using (var dbContext = new DestinationDbContext())
{
using (var transScope = new TransactionScope(
TransactionScopeOption.RequiresNew,
new TransactionOptions() {
IsolationLevel = IsolationLevel.ReadUncommitted
}
)
)
{
var imagesSourceQuery = dbContext.ImageDatas
.AsNoTracking()
.OrderBy(imageData => imageData.ImageID)
foreach (var image in imagesSourceQuery)
{
yield return image;
}
transScope.Complete();
}
}
}
private static void main(string[] args){
{
IEnumerator<ItemImage> sourceImagesEnumerator = null;
IEnumerator<ItemImage> destImagesEnumerator = null;
try{
sourceImagesEnumerator = EnumerateSourceImages().GetEnumerator();
destImagesEnumerator = EnumerateDestinationImages().GetEnumerator();
bool sourceHasMore = sourceImagesEnumerator.MoveNext();
//Exception on next line about MSDTC Promotion
bool destHasMore = destImagesEnumerator.MoveNext();
} finally{
if(sourceImagesEnumerator != null) sourceImagesEnumerator.Dispose();
if(destImagesEnumerator != null) destImagesEnumerator.Dispose():
}
}
Did you try setting enlist=false in your connection string?
http://forums.asp.net/t/1401606.aspx/1
Brgrds,
Lari