Entity Framework: Atomic Transaction (Database context) - c#

i am dealing with Entity framework 4 as the application is already been built and i have to make some up gradation in it.
Scenario:
Implemented a DBTransaction(inserts data in database) in my code and once a transaction aborts in the mid way and roll back executes then on next time when same transaction executes with correct/validated data still the transaction abort by giving the previous exception. It is quite difficult to understand as i presume that the RollBack should remove the validation messages and data from the Database Context as it is SQL.
Note: I am using a static DatabaseContext all through.
public class TestClass
{
static SampleDataBaseEntities ctx = new SampleDataBaseEntities();
public void SqlTransaction()
{
ctx.Connection.Open();
using (DbTransaction transaction = ctx.Connection.BeginTransaction())
{
try
{
Student std = new Student();
std.first_name = "first";
//std.last_name = "last"; (This is responsible for generating the exception)
AddTeacher();
ctx.AcceptAllChanges();
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
}
finally
{
ctx.Connection.Close();
}
}
}
public void SqlTransaction2()
{
ctx.Connection.Open();
using (DbTransaction transaction = ctx.Connection.BeginTransaction())
{
try
{
Student std = new Student();
std.first_name = "first";
std.last_name = "last";
AddTeacher();
ctx.Students.AddObject(std);
ctx.SaveChanges(false);
transaction.Commit();
ctx.AcceptAllChanges();
}
catch (Exception e)
{
transaction.Rollback();
transaction.Dispose();
ctx.Connection.Close();
}
}
}
public void AddTeacher()
{
Teacher t = new Teacher();
t.first_name = "teacher_first";
t.last_name = "teacher_last";
t.school_name = "PUCIT";
ctx.Teachers.AddObject(t);
ctx.SaveChanges(false);
}
}
class Program
{
static void Main(string[] args)
{
TestClass test = new TestClass();
test.SqlTransaction();
test.SqlTransaction2();
}
}
Solutions(Which i have tried):
Using the SaveChanges(false).
Using SaveChanges(false) and ctx.AcceptAllChanges().
Workaround:
The workaround which i got is to re instantiate the DatabaseContext object.
As i have complexity issues while re instantiating the context that's why looking for a more appropriate solution.
Thanks in advance.

All problems come from not creating new instances of the context. Simplify your code to this and it should work.
using (var ctx = new SampleDataBaseEntities()) {
Student std = new Student();
std.first_name = "first";
std.last_name = "last";
ctx.Student.Add(std);
ctx.SaveChanges();
}

"The workaround which i got is to re instantiate the DatabaseContext object."
yes this is correct for doing the transaction again.
Since you are using the static data context, so for the next time you do transaction the same data context is used that cause you problem re entering-data and validation errors.
Solution: Try never to use static dataContext since you are doing transactions very sooner. So you need updated datacontext for each transaction. so always try to instantiate a new dataContext and destroy it as soon as your transaction completes.
Hope it will work!

Related

Does it make sense to start a transaction for one SaveChanges call?

Does it make sense to Begin transaction, if I want to call SaveChanges() only once?
for example:
using (var foo = ContextFactory.CreateFooContext())
{
using (var transaction = foo.Database.BeginTransaction())
{
try
{
Bar bar = new Bar()
{
};
bar = foo.Bars.Add(bar);
foo.SaveChanges();
transaction.Commit();
return bar.Id;
}
catch
{
transaction.Rollback();
throw;
}
}
}
I couldn't find if there are any advantages of starting new transaction, if I call SaveChanges only once. I mean... there is no chance of saving partial data, right?

Handle Concurrency with Transaction in Entity Framework while saving collection

I would like to use the same dbContext to save a collection of Program type objects, but if there is any exception or concurrency exception in any of the program object, I would like to rollback the whole saved collection, and need to notify user about all program objects where concurrency issue occurred. I am using Entity Framework 6.1.
Find the code snippet. I am facing an issue that, if any of program object is having concurrency exception then programContext object is throwing the same exception again even if next record is not having any concurrency issue. Please guide on this if it is wrong then how can we achieve it in EF6.1
//Code
public List<ProgramViewModel> SavePrograms(List<ProgramViewModel> newAndUpdatedPrograms)
{
List<ProgramViewModel> failedPrograms = new List<ProgramViewModel>();
using (ProgramContext programContext = new ProgramContext())
{
using (DbContextTransaction dbProgramTransaction = programContext.Database.BeginTransaction())
{
bool isErrorOccured = false;
foreach (var item in newAndUpdatedPrograms)
{
try
{
Program program = new Program();
program.ProgramID = item.ProgramId;
program.Title = item.Title;
program.ProgramCode = item.ProgramCode;
program.Description = item.Description;
//This is to check whether user is having the latest record or dirty record (Concurency check)
program.RowVersion = System.Convert.FromBase64String(item.RowVersion);
if (program.ProgramID == 0)
programContext.Entry(program).State = System.Data.Entity.EntityState.Added;
else
programContext.Entry(program).State = System.Data.Entity.EntityState.Modified;
programContext.SaveChanges(); //Throws the previous concurrency exception here
}
catch (DbUpdateConcurrencyException ex)
{
isErrorOccured = true;
failedPrograms.Add(item);
}
}
if (isErrorOccured)
{
dbProgramTransaction.Rollback();
}
else
{
dbProgramTransaction.Commit();
}
}
}
return failedPrograms;
}

Proper way of using BeginTransaction with Dapper.IDbConnection

Which is the proper way of using BeginTransaction() with IDbConnection in Dapper ?
I have created a method in which i have to use BeginTransaction(). Here is the code.
using (IDbConnection cn = DBConnection)
{
var oTransaction = cn.BeginTransaction();
try
{
// SAVE BASIC CONSULT DETAIL
var oPara = new DynamicParameters();
oPara.Add("#PatientID", iPatientID, dbType: DbType.Int32);
..........blah......blah............
}
catch (Exception ex)
{
oTransaction.Rollback();
return new SaveResponse { Success = false, ResponseString = ex.Message };
}
}
When i executed above method - i got an exception -
Invalid operation. The connection is closed.
This is because you can't begin a transaction before the connection is opened. So when i add this line: cn.Open();, the error gets resolved. But i have read somewhere that manually opening the connection is bad practice!! Dapper opens a connection only when it needs to.
In Entity framework you can handle a transaction using a TransactionScope.
So my question is what is a good practice to handle transaction without adding the line cn.Open()... in Dapper ? I guess there should be some proper way for this.
Manually opening a connection is not "bad practice"; dapper works with open or closed connections as a convenience, nothing more. A common gotcha is people having connections that are left open, unused, for too long without ever releasing them to the pool - however, this isn't a problem in most cases, and you can certainly do:
using(var cn = CreateConnection()) {
cn.Open();
using(var tran = cn.BeginTransaction()) {
try {
// multiple operations involving cn and tran here
tran.Commit();
} catch {
tran.Rollback();
throw;
}
}
}
Note that dapper has an optional parameter to pass in the transaction, for example:
cn.Execute(sql, args, transaction: tran);
I am actually tempted to make extension methods on IDbTransaction that work similarly, since a transaction always exposes .Connection; this would allow:
tran.Execute(sql, args);
But this does not exist today.
TransactionScope is another option, but has different semantics: this could involve the LTM or DTC, depending on ... well, luck, mainly. It is also tempting to create a wrapper around IDbTransaction that doesn't need the try/catch - more like how TransactionScope works; something like (this also does not exist):
using(var cn = CreateConnection())
using(var tran = cn.SimpleTransaction())
{
tran.Execute(...);
tran.Execute(...);
tran.Complete();
}
You should not call
cn.Close();
because the using block will try to close too.
For the transaction part, yes you can use TransactionScope as well, since it is not an Entity Framework related technique.
Have a look at this SO answer: https://stackoverflow.com/a/6874617/566608
It explain how to enlist your connection in the transaction scope.
The important aspect is: connection are automatically enlisted in the transaction IIF you open the connection inside the scope.
Take a look at Tim Schreiber solution which is simple yet powerful and implemented using repository pattern and has Dapper Transactions in mind.
The Commit() in the code below shows it.
public class UnitOfWork : IUnitOfWork
{
private IDbConnection _connection;
private IDbTransaction _transaction;
private IBreedRepository _breedRepository;
private ICatRepository _catRepository;
private bool _disposed;
public UnitOfWork(string connectionString)
{
_connection = new SqlConnection(connectionString);
_connection.Open();
_transaction = _connection.BeginTransaction();
}
public IBreedRepository BreedRepository
{
get { return _breedRepository ?? (_breedRepository = new BreedRepository(_transaction)); }
}
public ICatRepository CatRepository
{
get { return _catRepository ?? (_catRepository = new CatRepository(_transaction)); }
}
public void Commit()
{
try
{
_transaction.Commit();
}
catch
{
_transaction.Rollback();
throw;
}
finally
{
_transaction.Dispose();
_transaction = _connection.BeginTransaction();
resetRepositories();
}
}
private void resetRepositories()
{
_breedRepository = null;
_catRepository = null;
}
public void Dispose()
{
dispose(true);
GC.SuppressFinalize(this);
}
private void dispose(bool disposing)
{
if (!_disposed)
{
if(disposing)
{
if (_transaction != null)
{
_transaction.Dispose();
_transaction = null;
}
if(_connection != null)
{
_connection.Dispose();
_connection = null;
}
}
_disposed = true;
}
}
~UnitOfWork()
{
dispose(false);
}
}
There are two intended ways to use transactions with Dapper.
Pass your IDbTranasction to your normal Dapper call.
Before:
var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"});
After:
var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"}, transaction=tx);
Use the new .Execute extension method that Dapper adds to IDbTransaction itself:
tx.Execute(sql, new {CustomerName = "Mark"});
Note: the variable tx comes from IDbTransaction tx = connection.BeginTransaction();
This is how you're supposed to use transactions with Dapper; neither of them are TranasctionScope.
Bonus Reading
https://stackoverflow.com/a/67474832/12597

How to set transaction level in entity framework?

I have below method
public void UpdateQuantity()
{
Sql ss = new Sql();
M3 m3 = new M3();
TransactionOptions ff = new TransactionOptions();
ff.IsolationLevel = IsolationLevel.ReadUncommitted;
using (TransactionScope dd = new TransactionScope(TransactionScopeOption.Required, ff))
{
try
{
ss.AddRegion("ALFKI", "SES1"); //step 1
m3.UpdateAnotherSystem(); //step2
dd.Complete();
}
catch (Exception)
{
}
}
}
public void AddRegion(string customerName, string Deception)
{
using (NorthWind context = new NorthWind())
{
Region rr = new Region();
rr.RegionID = 5;
rr.RegionDescription = "Ssaman";
context.Regions.Add(rr);
try
{
context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
}
In that first im going to update Sql server data base .After that im going to perform another update on other system.If step2 fails(may be network failure) then i need to reverse step 1.There for i put two method calls inside the transactionscope. I'm use entity framework to work with sql.Entity framework always set the transaction isolation level as read committed(according to the sql profiler).
but my problem is after context.SaveChanges() called my target table is locked till transaction completes(dd.Complete()).
Are there are any way to change entity framework transaction isolation level?(My entity framework version is 5).
SQL Server does not release locks that were taken due to writes until the end of the transaction. This is so that writes can be rolled back. You cannot do anything about this.
End your transaction or live with the fact that the rows written are still in use. Normally, this is not a problem. You should probably have a single context, connection and transaction for most work that happens in an HTTP request or WCF request. Transactions do not block on themselves.
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
context.Database.ExecuteSqlCommand(
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'"
);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
}

How to rollback a transaction in Entity Framework

string[] usersToAdd = new string[] { "asd", "asdert", "gasdff6" };
using (Entities context = new Entities())
{
foreach (string user in usersToAdd)
{
context.AddToUsers(new User { Name = user });
}
try
{
context.SaveChanges(); //Exception thrown: user 'gasdff6' already exist.
}
catch (Exception e)
{
//Roll back all changes including the two previous users.
}
Or maybe this is done automatically, meaning that if error occurs, committing changes are canceled for all the changes.
is it?
OK
I created a sample a application like the example from the the question and afterwords I checked in the DB and no users were added.
Conclusion: ObjectContext.SaveChange it's automatically a transaction.
Note: I believe transactions will be needed if executing sprocs etc.
I believe (but I am no long time expert in EF) that until the call to context.SaveChanges goes through, the transaction is not started. I'd expect an Exception from that call would automatically rollback any transaction it started.
Alternatives (in case you want to be in control of the transaction) [from J.Lerman's "Programming Entity Framework" O'Reilly, pg. 618]
using (var transaction = new System.Transactions.TransactionScope())
{
try
{
context.SaveChanges();
transaction.Complete();
context.AcceptAllChanges();
}
catch(OptimisticConcurrencyException e)
{
//Handle the exception
context.SaveChanges();
}
}
or
bool saved = false;
using (var transaction = new System.Transactions.TransactionScope())
{
try
{
context.SaveChanges();
saved = true;
}
catch(OptimisticConcurrencyException e)
{
//Handle the exception
context.SaveChanges();
}
finally
{
if(saved)
{
transaction.Complete();
context.AcceptAllChanges();
}
}
}

Categories

Resources