What I know is EF creates transaction for DbContext.SaveChanges.
But I need a block of operations including inserts and I need identity results from them to complete my sequence.
So what I do looks like this:
using var dbTransaction = context.DataBase.BeginTransaction();
try {
context.Add(myNewEntity);
context.SaveChanges();
otherEntity.RefId = myNewEntity.Id;
context.Update(otherEntity);
// some other inserts and updates
context.SaveChanges();
dbTransaction.Commit();
}
catch {
dbTransaction.Rollback();
throw;
}
So I call SaveChanges on inserts to get identities and not to break relations.
It looks like transactions in transactions. Is it correct? Is it how it should be done? I mean - Commit doesn't require SaveChanges? I assume it just saves the changes, but I want to be sure.
Your code will be working properly, but I prefer to do it this way:
try {
context.Add(myNewEntity);
var result= context.SaveChanges();
if(result==0){
dbTransaction.Rollback();
... return error
}
otherEntity.RefId = myNewEntity.Id;
context.Update(otherEntity);
// some other inserts and updates
result=context.SaveChanges();
if(result==0){
dbTransaction.Rollback();
... return error
}
dbTransaction.Commit();
}
catch {
dbTransaction.Rollback();
throw;
}
It is very usefull if for example you update or add or delete several records.
In this case the result will return the number of effected records and instead of result==0 I usually use if result < ...effected records I expect.
Related
I have entity framework update operations, between these operations I also have a Stored Procedure (Performing some delete operations). But ef update operations may be failed, in this case, I want to prevent Stored procedure execution. I want to execute a stored procedure only if all ef Update operations successfully completed without error.
Here is my code:
assignment.ActualStatus = model.InquiryAction;
officeAssignment.UpdateAt = currentDateTime;
assignment.UpdateBy = userId;
db.Entry(officeAssignment).State = EntityState.Modified;
//delete data for table..
var succes = db.Database.ExecuteSqlCommand("[dbo].[spUpdateStatus] #inquiryId, #status",
new SqlParameter("inquiryId", model.InquiryId),
new SqlParameter("status", model.Action));
I know that I can call a stored procedure after the database context.SaveChanges() successfully return 1 but I don't want to use that approach because I have already lots of dependent code. In the above example, I have shown a small part of code.
I recommend to roll it all into one SP. Within the SP, you start your transaction, and every step of the way make sure it updates/deletes successfully before moving onto the next query in the SP. If something fails, rollback. Once you get everything done, commit.
Edit to Muhammad's comment: There is an alternative. Wrap the EF code in a using BeginTransaction block, in the transaction block, do a foreach with a try/catch block. In the try, put your db code. On catch, rollback. At the end of the using/end of method, commit. Sample code below (code is NET Core 3.1 btw):
using(IDbContextTransaction transaction = db.Database.BeginTransaction())
{
foreach (int currentId in IdsEnumerable ?? Enumerable.Empty<int>())
{
try
{
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter("#Id", id),
// other parameters here
};
int rowsAffected = db.Database.ExecuteSqlRaw("EXEC Schema.SPName #Id, etc...", parameters);
if (rowsAffected > 0)
{
totalRowsAffected += rowsAffected;
}
else
{
transaction.Rollback();
return false;
}
}
catch(Exception ex)
{
transaction.Rollback();
}
}
db.SaveChanges();
transaction.Commit();
return totalRowsAffected > 0;
}
Can someone please tell me which is the better way to update a table with a list of values? And which one to use when and what's the reason behind that?
Method 1:
public void SavDetails(List<MyTable> list)
{
_Entity.MyTable.AddOrUpdate(list.ToArray());
try
{
_Entity.SaveChanges();
}
catch (DbUpdateException ex)
{
Console.WriteLine(ex);
}
}
Method 2:
public void SaveDetails(List<MyTable> list)
{
foreach (var file in list)
{
_Entity.MyTable.Add(file);
_Entity.Entry(file).State = EntityState.Modified;
try
{
_Entity.SaveChanges();
}
catch (DbUpdateException ex)
{
Console.WriteLine(ex);
}
}
}
Method 1
The AddOrUpdate method is in the System.Data.Entity.Migrations namespace, because it is intended for use with migrations. If it works for you then it may be a good option, but be aware that this is not how it was designed to be used.
Method 2
This seems to assume that all entities already exist in the database, and it will fail if they do not. If you know this, then it will be more efficient because it doesn't need to check.
Do not call SaveChanges() inside the loop, as this causes multiple calls to the database. Instead, call it once at the end and all the entities will be updated at once. Also, you should replace Add (which implies adding a new object) with Attach (which is for telling the context to track existing entities). This will make your intent easier to follow.
If you can't rely on all entities already existing in the database, then you'll need to manually check:
var existing = _Entity.MyTable.Select(o => o.Id).ToList();
foreach (var file in list)
{
if (existing.Contains(file.Id))
{
_Entity.MyTable.Attach(file);
_Entity.Entry(file).State = EntityState.Modified;
}
else
{
_Entity.MyTable.AddObject(file);
}
}
_Entity.SaveChanges();
If you want to delete rows from the database table which are not in your list, that will require additional logic.
Conclusion
There is no way of knowing for sure which approach is best for your scenario. If both methods work, and they're both fast enough, then choose the one you prefer. If speed is an issue, then benchmark them and choose the faster option.
Will the below code rollback the changes if there are any exception while saving?
using (SampleEntities context = new SampleEntities())
{
//Code Omitted
context.EmpPercAdjustments.AddRange(pp);
context.SampleJobs.AddRange(sampleJobs);
context.SaveChanges();
}
Or
Do I need to use transaction?
using (SampleEntities context = new SampleEntities())
{
//Code Omitted
using (System.Data.Entity.DbContextTransaction tranc = context.Database.BeginTransaction( ))
{
try
{
context.EmpPercAdjustments.AddRange(pp);
context.SampleJobs.AddRange(sampleJobs);
context.SaveChanges();
tranc.Commit();
}
catch (Exception ee)
{
tranc.Rollback();
}
}
}
Are there any advantages of using one over the others?
Yes it will rollback correctly.
In this case, you do not need to run an explicit transaction, because there is one already created by Entity Framework.
Creating a transaction by calling context.Database.BeginTransaction() is good, if you want f.e. to get the Id of just inserted record, something like this:
using (SampleEntities context = new SampleEntities())
{
using (System.Data.Entity.DbContextTransaction trans = context.Database.BeginTransaction( ))
{
context.SampleJobs.Add(newJob);
context.SaveChanges();
var jobId = newJob.Id;
//do other things, then commit or rollback
trans.Commit();
}
}
In this case, after calling SaveChanges(), the changes made on context objects are applied (so you can read database generated Id of added object in your scope), but they still have to be commited or rolled back, because changes are only dirty written.
Defining an explicit transaction can also be useful, if you have multiple methods that can modify context objects, but you want to have a final say, if changes they made will be all commited or not.
I have a code like this:
try
{
Member member = database.Members.Where(m=>m.ID=1).FirstOrDefault();
member.Name = "NewMemberName";
database.Entry(member).State = EntityState.Modified;
database.SaveChanges();
}
catch (Exception ex)
{
database.Logs.Add(new Log() { Value=ex.ToString() });
database.SaveChanges();
}
And Entity:
[StringLength(5)]
public string Name { get; set; }
If the Name String more than 5 it would be error and catch the exception ,but when I add a log then save ,the exception from SaveChange(); still remains,how should I do?(Can't change the schema)
the exception from SaveChange(); still remains
Well, if this throws an exception:
database.SaveChanges();
Then there's a pretty good chance that this will also throw an exception:
database.SaveChanges();
Basically, in your catch block you shouldn't be immediately re-trying the operation that just failed a millisecond ago. Instead, log the failure and handle the exception:
catch (Exception ex)
{
// DO NOT call SaveChanges() here.
}
Of course, if writing to the database is failing, then logging to the database is also likely to fail. Suppose for example that the connection string is wrong or the database is down or timing out. You can't log that.
I recommend using a logging framework (log4net, NLog, etc.) as a separate dependency from your Entity Framework data access layer. It's a small learning curve, but you end up with a pretty robust logging system that can much more effectively handle problems. And can be easily configured to log to multiple places, so if writing to one error log (the database) fails then you still have another one (a file, for example).
At the very least, if persisting your data context fails, you'll need to log to a new data context. Otherwise the part that failed is still there.
Something structurally more like this:
try
{
using (var database = new DbContext())
{
Member member = database.Members.Where(m=>m.ID=1).FirstOrDefault();
member.Name = "NewMemberName";
database.Entry(member).State = EntityState.Modified;
database.SaveChanges();
}
}
catch (Exception ex)
{
using (var database = new DbContext())
{
database.Logs.Add(new Log() { Value=ex.ToString() });
database.SaveChanges();
}
}
My problem is that the transaction is not working properly it should not save the data for one table if an exception occurs during the trascation
When all the table is correct then only save data.
Consider the following:
databaseEntites objEntites = null;
using (objEntites = new databaseEntites())
{
objEntites.Connection.Open();
using (System.Data.Common.DbTransaction transaction =
objEntites.Connection.BeginTransaction())
{
try
{
customer objcust=new customer();
objcust.id=id;
objcust.name="test1";
objcust.email="test#gmail.com";
objEntites.customer.AddObject(objcust);
order objorder=new order();
objorder.custid=objcust.id;
objorder.amount=500;
objEntites.order.AddObject(objorder);
objEntites.SaveChanges();
transaction.Commit();
}
catch()
{
transaction.Rollback();
}
}
}
In this my second table column name is not correct and on SaveChanges() giving the exception.
When i see the database and found that it saving the data for customer table which is wrong i want data will go in the customer table when all table is correct and this savechanges either save for all table or not save for any.
For this i have also try the TransactionScope
using (TransactionScope tscope =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
......all the code here....
objEntites.SaveChanges(false);
tscope.Complete();
objEntites.AcceptAllChanges();
}
But its giving the same issue as described above.
Thanks in advance.
You can use database transaction or EF TransactionScope. For using database transaction it is enough to do as below:
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
//Some stuff
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
And for using second way that EF TransactionScope just use easily as below:
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
try
{
//Some stuff
scope.Complete();
}
catch (Exception)
{
//Don't need call any rollback like method
}
}
The point is no Rollback() method exist in the TransactionScope (against the normal ADO.NET Transaction) and Unless you call the Complete() method, the transaction do not complete and all the changes are rolled back automatically. You can see MSDN for better understand: http://msdn.microsoft.com/en-us/data/dn456843.aspx
Hope this help
If you have already a try ... catch block, you don't need using - just add finally. The following example should have everything you need:
databaseEntites objEntites = null;
var System.Data.Common.DbTransaction transaction = null;
try
{
objEntites = new databaseEntites();
objEntites.Connection.Open();
transaction = objEntites.Connection.BeginTransaction();
customer objcust=new customer();
objcust.id=id;
objcust.name="test1";
objcust.email="test#gmail.com";
objEntites.customer.AddObject(objcust);
order objorder=new order();
objorder.custid=objcust.id;
objorder.amount=500;
objEntites.order.AddObject(objorder);
objEntites.SaveChanges();
transaction.Commit();
}
catch()
{
if (transaction != null) transaction.Rollback();
}
finally
{
if (objEntites != null && objEntites.Connection.State != System.Data.ConnectionState.Closed
&& objEntites.Connection.State != System.Data.ConnectionState.Broken)
objEntites.Connection.Close();
}
Hints:
The finally block is executed even after an exception has occured, hence in case of an exception the exception handling code is executed first, then the code in the finally block. Only if severe exceptions (system errors) - such as a StackOverflowException - occur, it is not executed but you can't handle such kinds of exceptions anyway easily. For more information about this topic please look here.
For SaveChanges you can also use the option System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave, which I prefer because it guarantees that every entity object has its changes accepted after successful save.
BeginTransaction allows also to specify the kind of transaction, but I would not use the parameter options because if you omit it then it creates a transaction as specified by the database's default - so the administrator is still able to change this easily if required. But if you need to specify it, ensure that you don't hardcode it, but allow to configure it in the App.Config or Web.Config file.
Please let me know if that works for you.