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.
Related
In my .NET Core project (.NET 5) I'm using EF Core 6 and for SQL auth, it's using Azure AD Identity authentication.
Here is my startup where DB context is initialized:
services.AddDbContext<EPMO_DevContext>((provider, options) => //managed
{
SqlConnection azureConnection = new SqlConnection(Configuration["DefaultConnection"]);
var dbAuthentication = provider.GetService<Func<string, AzAuthentication>>()("DbScope");
azureConnection.AccessToken = dbAuthentication?.AccessToken;
SqlConnection localDb = new SqlConnection(Configuration.GetConnectionString("EpmoDb"));
var tokenResult = app.GetAuthenticationResultAsync( Configuration["DbScope"] , "mydomain.com").Result;
azureConnection.AccessToken = tokenResult.AccessToken;*/
string env = Configuration["Environment"];
options.UseSqlServer(env == "local" ? localDb:azureConnection, sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
});
And using DI in my constructor
public AdoExtract(IConfiguration configuration = null, EPMO_DevContext context = null)
{
_configuration = configuration;
_context = context;
}
But since EF taking too much time for bulk insert I am advised to use SQL bulk copy
private int WorkitemsToDb(List<AdoWorkitem> list )
{
try
{
using (var connection = _context.Database.GetDbConnection())
{
connection.Open();
SqlTransaction transaction = connection.BeginTransaction();
using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction))
{
bulkCopy.BatchSize = 100;
bulkCopy.DestinationTableName = "dbo.Person";
try
{
bulkCopy.WriteToServer(list.AsDataTable());
}
catch (Exception)
{
transaction.Rollback();
connection.Close();
}
}
transaction.Commit();
}
}
catch (Exception ex)
{
throw;
}
}
But at
SqlTransaction transaction = connection.BeginTransaction();
I get this error:
Cannot implicitly convert type 'System.Data.Common.DbTransaction' to 'Microsoft.Data.SqlClient.SqlTransaction'. An explicit conversion exists (are you missing a cast?)
So how can I get the SQL connection in class for bulk copy when using EF and Azure AD authentication for SQL Server?
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
}
}
}
}
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.
lib.ParamsInsert is called via another dll.
The transaction is rolled back when there is a problem with the throw statement. However, the operations in paramsInsert can not be undone.
In lib.ParamsInsert, there is a transaction in its own error.
spcarikart.Repository lib = new spcarikart.Repository();
using (var transaction = db.Database.BeginTransaction())
{
try
{
var result = db.Acenta.Add(obj).Entity;
var a = lib.ParamsInsert(db, new Params
{
Baslik = "Bahdir",
Deger = "1"
});
// ....Maybe Error other process
db.SaveChanges();
return result;
}
catch (Exception ex)
{
transaction.Rollback();
}
}
ParamsInsert
using (var transaction = db.Database.BeginTransaction())
{
try
{
var resul = db.Params.Add(obj).Entity;
db.SaveChanges();
transaction.Commit();
return resul;
}
catch (Exception ex)
{
transaction.Rollback();
throw new Exception();
}
}
They use different transactions. Don't start new transaction, as I see, you work with the same db context, so needn't start transaction in paramsInsert, remove using (var transaction = db.Database.BeginTransaction()) from it.
is it possible on the following try-catch to execute a set of statements as a transaction using ADO NET Entity Data Model?
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Customer c)
{
try
{
c.Created = DateTime.Now;
c.Active = true;
c.FullName = Request.Form["FirstName"];
db.AddToCustomer(c);
db.SaveChanges();
Log log = new Log();//another entity model object
log.Created = DateTime.Now;
log.Message =
string.Format(#"A new customer was created
with customerID {0}", c.CustomerID);
db.AddToLog(log);
db.SaveChanges();
return RedirectToAction("CreateSuccess", "Customer");
}
catch
{
return View();
}
}
Any thoughts would be very appreciated.
using (var transaction = db.Connection.BeginTransaction())
{
// your code here
...
transaction.Commit();
}