Entity Framework - force commit without transaction - c#

I have a loop that looks a bit like this:
CountsToReport = rep.Counts_Get().Where(x => x.Status == "Completed");
foreach (var count in CountsToReport.ToList())
{
//do some stuff
//send an email
count.Status = "Reported";
rep.SaveChanges();
}
Where "rep" is a repository wrapper around an EF context.
When this runs, the unfortunate email recipient gets deluged with spam because the SaveChanges call doesn't actually commit the changes - so the loop keep getting the same counts, emailing them, and marking them as "Reported" but doesn't actually save the change.
If you stop the loop, and re-start the code, the change saves successfully. You can confirm this scenario by stepping through the code: the EF object in C# changes its Status, but the underlying data in SQL doesn't change.
I'm presuming this is because SaveChanges doesn't actually commit the transaction - it just marks the data as having changed ready for the end of the transaction. But we're not using transactions anywhere else in the DB, and it'd be a bit of a pain to change the repository for this one use case.
Is there any other way I can force EF to commit this change and escape my endless loop of doom? Or am I mistaken about the cause?
EDIT: Putting this in the repository and calling it instead of SaveChanges his fixes it:
public void SaveWithTransaction()
{
using (var transaction = new System.Transactions.TransactionScope())
{
db.SaveChanges();
transaction.Complete();
}
}
But it seems ugly. Still interested to know if there's another way round.
EDIT: This is deceptive. It looks like it's just the old add/modified problem again. Marking the object as modified seems to help.

It is quite possible that entities in your CountsToReport are detached from context.
Thus not only ChangeTracker doesn't see any sort of change done over Count, it doesn't know anything about the entity at all.
To have the issue fixed:
iterate through collection of CountsToReport
attach each entity back to context, something like db.Counts.Attach(count)
modify the count.Status and call db.SaveChanges();
like this:
foreach (var count in CountsToReport.ToList())
{
//do some stuff
//send an email
rep.Counts.Attach(count);
count.Status = "Reported";
rep.SaveChanges();
}
Have a look at the following link for more information about entity states and how to deal with them:
http://msdn.microsoft.com/en-us/data/jj592676.aspx

Related

Entity Framework - Loop then call SaveChanges

Would SaveChanges save all changes of debt? Or only the last because it lost the reference because I change element debt in each loop?
List<DTO.ClientDebt> ClientDebtList = Business.Generic.GetAll<DTO.ClientDebt>();
foreach (var oClienteDeuda in oClienteDeudaSyncList) //oClienteDeudaSyncList is a list of debts
{
DTO.ClientDebt debt = ClientDebtList.Where(x => x.ClienteId == oClienteDeuda.ClienteId && x.NumeroComprobante == oClienteDeuda.NumeroComprobante).FirstOrDefault();
debt.Active = oClienteDeuda.Active ? 1 : 0;
}
Data.Generic.SaveChanges();
The way you have done is the correct way when we're dealing with the foreach loops.You can do the same thing inside the loop too.But it'll degrade the performance of the operation heavily.So always do the SaveChanges() after the foreach loop.SaveChanges() method persist modifications made to all entities attached to it.So you don't need to worry about the reference changes and etc.It works as unit of work.That means either save all or none.
Note : SaveChanges() operates within a transaction. SaveChanges() will roll back that transaction and throw an exception if any of the dirty ObjectStateEntry objects cannot be persisted.
the changes to entities are monitored internally, therefore none of your changes should be lost as long as you did not tell the entity framework to skip the change tracking (e.g. using AsNotracking() in your query)

NHibernate rollback does not seem to work

My question is simple. I have the below code and user is still inserted. When I check the database just after SaveOrUpdate (and before the rollback) I see user is already inserted. It's like flush mode and transaction not working. Where am I going wrong?
using (var session = sessionFactory.OpenSession())
{
session.FlushMode = FlushMode.Never;
using (var tran = session.BeginTransaction())
{
var user = CreateUser();
session.SaveOrUpdate(user);
tran.Rollback();
}
}
If we check "just after SaveOrUpdate" ... we are still inside of one transaction. Lot of stuff could happen before final decision (commit or rollback).
And one of the operation would be to decide if object should be Created or Updated. So in case, that the ID generator is set to native/identity (e.g. SQL server) - NHibernate must execute the INSERT to get the ID. Lot of operation could be postponed, but to receive the ID - there is no way to wait.
So most likely your ID need to be obtained from DB.. and that's why INSERT happens. BUT other stuff won't be written into DB, until Flush() is called... So, I wouldn't mark described behaviour as something special.

What persistence behavior should I expect when in this scenario

I have a large block of code with several nested loops which needs to ultimately update many records in the database. I am trying to minimize the number of SaveChanges() calls to Entity Framework. Also note that we we are using the repository pattern.
Essentially I'm iterating a collection and need to update both the items in the collection and upon each iteration, another object retrieved from the db and contextualized by the item from the collection.
Sample code:
foreach (var outer in outerList)
{
obj = unit.GetRepository<MyObj>().Get(s =>
s.id = myId
).SingleOrDefault();
obj.value += outer.value;
outer.objId = obj.objId;
unit.GetRepository<MyOuterObj>().Update(outerObj);
unit.GetRepository<MyObj>().Update(obj);
}
unit.Save();
The call to Update() performs the following:
public virtual void Update(T entityToUpdate)
{
if(entityToUpdate is AuditModelBase)
{
var model = entityToUpdate as AuditModelBase;
model.UpdatedAt = DateTime.UtcNow;
}
DbSet.Attach(entityToUpdate);
Context.Entry(entityToUpdate).State = EntityState.Modified;
}
And the call to Save() of course performs the following:
_context.SaveChanges();
So my question is, as I'm reassigning obj to a different value each time through the loop, do I need Save() inside the foreach loop in order for all instances of "obj" to persist. Or does the DbSet.Attach(obj) ensure that each individual instance is updated regardless on what I do with the object in my loop.
Or perhaps a better way to ask this is:
Given that it looks like Attach() is pass-by-reference so therefore only my last obj will be updated, what are best practices with EF to accomplish this sort of thing (excluding the option of straight calls to SQL) ?
I don't think you have to worry. Once you call Attach on your object, it doesn't matter whether you keep a reference to it yourself or not; it should be fine.
The thing to keep in mind is that your object lives as long that it's referenced by someone. So, it stands to reason that calling Attach would cause your DbSet to reference your object, thus keeping it alive even after you don't reference it anymore yourself.
However, the best thing to do, in my opinion, is to just give it a try and see what happens!

Entity Framework Transaction: What is better performance-wise?

Which has better performance:
using (GADEEntities context = new GADEEntities(_connectionString))
{
using (TransactionScope transaction = new TransactionScope())
{
AddToContext1(context);
AddToContext2(context);
AddToContext3(context);
...
context.SaveChanges();
transaction.Complete();
}
}
or
using (GADEEntities context = new GADEEntities(_connectionString))
{
using (TransactionScope transaction = new TransactionScope())
{
AddToContext1(context);
context.SaveChanges();
AddToContext2(context);
context.SaveChanges();
AddToContext3(context);
context.SaveChanges();
...
transaction.Complete();
}
}
At any time, this could translate into 5000+ inserts into a DB on a clients machine. Is either way any different?
It's very likely that your first version will be always faster, depending on what AddToContext exactly does. If your AddToContext method adds a single or only a few new objects to the context it will be definitely much faster. Calling SaveChanges after each insert (and probably also update and delete) slows the performance extremely down.
Here are a few measurements in a similar question:
Fastest Way of Inserting in Entity Framework
The way you have it set up, I do not think there is any significant difference. The data will be transmitted either way, and that's the real bottleneck.
There is very big difference because second version is terribly wrong.
What are you doing by this code:
AddToContext1(context);
context.SaveChanges(false);
You add record to context in Added state and let the context insert the record to the database but in the same time you are saying: "Let the data in Added state".
What happesn if you call this:
AddToContext2(context);
context.SaveChanges(false);
You add another recored to context in Added state and let the context insert all records in Added state to the database = the first record will be added again
It doesn't matter if AddToContext actually performs update because it will simply do the DB command again. So if you have 5.000 records you will insert or update the first one 5.000 times!
If you want to use second version you still have to accept changes during each saving.
Btw. SaveChanges overload accepting bool is obsolete in EFv4.

How to clear the DataContext cache on Linq to Sql

I'm using Linq to Sql to query some database, i only use Linq to read data from the DB, and i make changes to it by other means. (This cannot be changed, this is a restriction from the App that we are extending, all updates must go trough its sdk).
This is fine, but I'm hitting some cache problems, basically, i query a row using Linq, then i delete it trough external means, and then i create a new row externally if i query that row again using linq i got the old (cached) data.
I cannot turn off Object Tracking because that seems to prevent the data context from auto loading associated propertys (Foreign Keys).
Is there any way to clear the DataContex cache?
I found a method sufring the net but it doesn't seem safe: http://blog.robustsoftware.co.uk/2008/11/clearing-cache-of-linq-to-sql.html
What do you think? what are my options?.
If you want to refresh a specific object, then the Refresh() method may be your best bet.
Like this:
Context.Refresh(RefreshMode.OverwriteCurrentValues, objectToRefresh);
You can also pass an array of objects or an IEnumerable as the 2nd argument if you need to refresh more than one object at a time.
Update
I see what you're talking about in comments, in reflector you see this happening inside .Refresh():
object objectByKey = context.Services.GetObjectByKey(trackedObject.Type, keyValues);
if (objectByKey == null)
{
throw Error.RefreshOfDeletedObject();
}
The method you linked seems to be your best option, the DataContext class doesn't provide any other way to clear a deleted row. The disposal checks and such are inside the ClearCache() method...it's really just checking for disposal and calling ResetServices() on the CommonDataServices underneath..the only ill-effect would be clearing any pending inserts, updates or deletes that you have queued.
There is one more option, can you fire up another DataContext for whatever operation you're doing? It wouldn't have any cache to it...but that does involve some computational cost, so if the pending insert, update and deletes aren't an issue, I'd stick with the ClearCache() approach.
I made this code to really CLEAR the "cached" entities, detaching it.
var entidades = Ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged);
foreach (var objectStateEntry in entidades)
Ctx.Detach(objectStateEntry.Entity);
Where Ctx are my Context.
You should be able to just requery the result sets that are using this objects. This would not pull a cached set, but would actually return the final results. I know that this may not be as easy or feasible depending on how you setup your app...
HTH.

Categories