How to add a delete callback in entity framework? - entity-framework

Inspired by ruby on rails I want to add a delete callback to entity framework. I've started by overriding SaveChanges() to loop over the tracked entities and create an interface which is called whenever an entity gets deleted.
var changedEntities = ChangeTracker.Entries();
foreach (var changedEntity in changedEntities)
{
if (changedEntity.Entity is IBeforeDelete && changedEntity.State == EntityState.Deleted)
{
IBeforeDelete saveChange = (IBeforeDelete)changedEntity.Entity;
saveChange.BeforeDelete(this, changedEntity);
}
}
This works quite well, but I figured one problem and I don't know how to solve that. If an entity gets deleted within the callback, the Changetracker needs to be updated to resprect the newly deleted items. How can I solve that? Or is there another solution? Or do I do it wrong?

Good question. If I understand you correctly, your BeforeDelete implementations might delete a different entry that also needs to have BeforeDelete called on it. This could recursively go forever, so the only thing I could think of would be to recursively check the change tracker entries to see if new ones were added after the last batch was processed.
Untested:
public override int SaveChanges()
{
var processed = new List<DbEntityEntry>();
var entries = ChangeTracker.Entries();
do
{
foreach (var entry in entries)
{
processed.Add(entry);
if (entry.Entity is IBeforeDelete && entry.State == EntityState.Deleted)
{
IBeforeDelete saveChange = (IBeforeDelete)entry.Entity;
saveChange.BeforeDelete(this, entry);
}
}
} while ((entries = ChangeTracker.Entries().Except(processed)).Any());
return base.SaveChanges();
}

you can filter out uncommited entity changes...
var changedEntities = Context.ChangeTracker
.Entries<TEntity>()
.Where(e => e.State != EntityState.Detached)
.Select(e => e.Entity);
and lock on the rest in your "if" block
lock(changedEntity.Entity){ ... interface code }

Related

c# entity framework savechangesasync saves new record but returns 0

Entity Framework: 6.1.3.
I have a function that reads a simple table for a record and either updates it or first creates a new entity. Either way it then calls AddOrUpdate and SaveChangesAsync. This function has worked for quite some time without any apparent problem.
In my current situation, however, I'm getting a return value of 0 from SaveChangesAsync. I have a break point just before the save and verified that the record doesn't exist. I step through the code and, as expected, a new entity was created. The curious part is that the record is now in the table as desired. If I understand the documentation, 0 should indicate that nothing was written out.
I'm not using transactions for this operation. Other database operations including writes would have already occurred on the context prior to this function being called, however, they should all have been committed.
So how can I get a return of 0 and still have something written out?
Here is a slightly reduced code fragment:
var settings = OrganizationDb.Settings;
var setting = await settings.FirstOrDefaultAsync(x => x.KeyName == key).ConfigureAwait(false);
if (setting == null)
{
setting = new Setting()
{
KeyName = key,
};
}
setting.Value = value;
settings.AddOrUpdate(setting);
if (await OrganizationDb.SaveChangesAsync().ConfigureAwait(false) == 0)
{
//// error handling - record not written out.
}

Entity framework core - DbUpdateException

UPDATED: I have a piece of code that creates records when they don't exist, or updates them when they do exist. However, while trying to update the records I get this exception:
Microsoft.EntityFrameworkCore.DbUpdateException The DELETE statement
conflicted with the REFERENCE constraint
public static string AddCurrencies(ApplicationDbContext db)
{
// ...
foreach (Currency c in db.Currency.ToList())
{
try
{
db.Remove(c); // the troublemaker!
db.SaveChanges();
}
catch
{
// probably in use (foreign key)
}
}
// ...
foreach (Currency c in CurrencyList)
{
var c_db = db.Currency.FirstOrDefault(x => x.Code == c.Code);
if (c_db == null)
{
// adding
db.Currency.Add(c);
}
else
{
// updating
c_db.Name = c.Name;
c_db.LocalDisplay = c.LocalDisplay;
}
db.SaveChanges(); // exception fired if updating!
}
// ...
}
After some investigation, and having being able to turn SQL debugging on, I found out that the Remove() "persists" and that it will be retried with the second call to SaveChanges(), hence the exception. Now the question is reformulated: how do I "undo" (in the lack of a better expression) the Remove() commands that failed?
I managed to solve this issue this way:
var entry = context.Entry(entity);
entry.Reload();
for each entry where delete failed.

TransactionScope with two datacontexts doesn't roll back

I am trying to solve situation with rolling back our datacontexts.
We are using one TransactionScope and inside two data contexts of two different databases.
At the end we want to save changes on both databases so we call .SaveChanges but the problem is that when an error occurs on the other database the changes on the first database are still saved.
What am I doing wrong in there that the first database doesn't roll back?
Thank you,
Jakub
public void DoWork()
{
using (var scope = new TransactionScope())
{
using (var rawData = new IntranetRawDataDevEntities())
{
rawData.Configuration.AutoDetectChangesEnabled = true;
using (var dataWareHouse = new IntranetDataWareHouseDevEntities())
{
dataWareHouse.Configuration.AutoDetectChangesEnabled = true;
... some operations with the data - no savechanges() is being called.
// Save changes for all items.
if (!errors)
{
// First database save.
rawData.SaveChanges();
// Fake data to fail the second database save.
dataWareHouse.Tasks.Add(new PLKPIDashboards.DataWareHouse.Task()
{
Description = string.Empty,
Id = 0,
OperationsQueue = new OperationsQueue(),
Queue_key = 79,
TaskTypeSLAs = new Collection<TaskTypeSLA>(),
Tasktype = null
});
// Second database save.
dataWareHouse.SaveChanges();
scope.Complete();
}
else
{
scope.Dispose();
}
}
}
}
From this article http://blogs.msdn.com/b/alexj/archive/2009/01/11/savechanges-false.aspx
try to use
rawData.SaveChanges(false);
dataWareHouse.SaveChanges(false);
//if everything is ok
scope.Complete();
rawData.AcceptAllChanges();
dataWareHouse.AcceptAllChanges();

Understanding Entity Framework optimistic concurrency (database wins) pattern

See Resolving optimistic concurrency exceptions with Reload (database wins) :
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update the values of the entity that failed to save from the store
ex.Entries.Single().Reload();
}
} while (saveFailed);
}
Why the method SaveChanges() is called after Reload()?
This call will never change the data in the database.
I agree it's not too clear. The intention of this piece of code is in the sentence
The entity is then typically given back to the user in some form and they must try to make their changes again and re-save.
So it would have been better if they had added a comment:
...
// User evaluates current values and may make new changes.
try
{
context.SaveChanges();
}
...

delete and then insert object Entity framework

I have this method that delete object if exist and insert the new instance any way :
internal void SaveCarAccident(WcfContracts.BLObjects.Contract.Dtos.CarAccident DTOCarAccident)
{
using(var context = BLObjectsFactory.Create())
{
context.ContextOptions.ProxyCreationEnabled = false;
CarAccident NewCarAccident = ConvertToCarAccident(DTOCarAccident);
CarAccident carFromDB = context.CarAccident.FirstOrDefault(current => current.CarAccidentKey.Equals(NewCarAccident.CarAccidentKey));
if(carFromDB != null)
context.CarAccident.DeleteObject(carFromDB);
context.CarAccident.AddObject(NewCarAccident);
context.SaveChanges();
}
}
I sometimes get exception that the key already exist in table.
I wnted to know if the way I save the changes is a problem (saving after delete and insert and not after each one)
At the time I got the exception there were few clients that activate the method at the same time I blocked other clients from writing already, but is this may be the problem ?
Thanks
Eran