DbContext élimine les modifications sans élimination
j'ai une application client de bureau qui utilise des fenêtres modales pour définir des propriétés pour les objets hiérarchiques. Comme il s'agit d'une application client et que L'accès au Dbcontexte n'est pas threadé, j'utilise un contexte de longue durée sur le formulaire principal qui est transmis aux enfants modaux.
ces fenêtres modales utilisent la directive PropertyGrid pour afficher les propriétés des entités et ont aussi des boutons cancel. Si des données sont modifiées et que le bouton d'annulation est appuyé, les changements sont reflétés dans le parent forme (où je ne peux pas disposer de la DbContext object
).
y a-t-il un moyen d'écarter tout changement effectué si le DbContext.SaveChanges()
la méthode n'a pas été appelée?
mise à jour: Entity Framework Version 4.4.
7 réponses
pourquoi ne pas l'emballer dans une transaction?
using(var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })){
// Do something
context.SaveChanges();
// Do something else
context.SaveChanges();
scope.Complete();
}
public void RejectChanges()
{
foreach (var entry in ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Modified:
case EntityState.Deleted:
entry.State = EntityState.Modified; //Revert changes made to deleted entity.
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
}
}
}
Dans le cas simple d'annuler les modifications apportées aux propriétés d'une entité unique, vous pouvez définir les valeurs actuelles pour les valeurs d'origine.
context.Entry(myEntity).CurrentValues.SetValues(context.Entry(myEntity).OriginalValues);
//you may also need to set back to unmodified -
//I'm unsure if EF will do this automatically
context.Entry(myEntity).State = EntityState.UnModified;
ou sinon recharger (mais les résultats en db hit)
context.Entry(myEntity).Reload();
Vous colud essayer de le faire manuellement, quelque chose comme ça.. pas sûr que cela fonctionne pour votre scénario, mais vous pouvez lui donner un essai:
public void UndoAll(DbContext context)
{
//detect all changes (probably not required if AutoDetectChanges is set to true)
context.ChangeTracker.DetectChanges();
//get all entries that are changed
var entries = context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged).ToList();
//somehow try to discard changes on every entry
foreach (var dbEntityEntry in entries)
{
var entity = dbEntityEntry.Entity;
if (entity == null) continue;
if (dbEntityEntry.State == EntityState.Added)
{
//if entity is in Added state, remove it. (there will be problems with Set methods if entity is of proxy type, in that case you need entity base type
var set = context.Set(entity.GeType());
set.Remove(entity);
}
else if (dbEntityEntry.State == EntityState.Modified)
{
//entity is modified... you can set it to Unchanged or Reload it form Db??
dbEntityEntry.Reload();
}
else if (dbEntityEntry.State == EntityState.Deleted)
//entity is deleted... not sure what would be the right thing to do with it... set it to Modifed or Unchanged
dbEntityEntry.State = EntityState.Modified;
}
}
Vous pouvez appliquer ceci:
context.Entry(TEntity).Reload();
je l'ai essayer et sa fonctionne bien pour moi.
Remarque: Cette méthode ( recharger) Recharge l'entité de la base de données en remplaçant toute valeur de propriété par des valeurs de la base de données. L'entité sera dans l'état Inchangé après l'appel de cette méthode.
ceci est basé sur la réponse de Surgey Shuvalov. Il ajoute la prise en charge des changements de propriétés de navigation.
public void RejectChanges()
{
RejectScalarChanges();
RejectNavigationChanges();
}
private void RejectScalarChanges()
{
foreach (var entry in ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Modified:
case EntityState.Deleted:
entry.State = EntityState.Modified; //Revert changes made to deleted entity.
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
}
}
}
private void RejectNavigationChanges()
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var deletedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).Where(e => e.IsRelationship && !this.RelationshipContainsKeyEntry(e));
var addedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Where(e => e.IsRelationship);
foreach (var relationship in addedRelationships)
relationship.Delete();
foreach (var relationship in deletedRelationships)
relationship.ChangeState(EntityState.Unchanged);
}
private bool RelationshipContainsKeyEntry(System.Data.Entity.Core.Objects.ObjectStateEntry stateEntry)
{
//prevent exception: "Cannot change state of a relationship if one of the ends of the relationship is a KeyEntry"
//I haven't been able to find the conditions under which this happens, but it sometimes does.
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var keys = new[] { stateEntry.OriginalValues[0], stateEntry.OriginalValues[1] };
return keys.Any(key => objectContext.ObjectStateManager.GetObjectStateEntry(key).Entity == null);
}
je suis tombé sur une mauvaise surprise - appel à ChangeTracker.Entrées() se bloque si vous avez besoin de faire reculer des modifications dues à une exception dans DbContext par exemple
System.InvalidOperationException:
'The property 'Id' on entity type 'TestEntity' is part of a key and so cannot be modified or marked as modified.
To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.'
j'ai donc créé une version hackée du rollback manuel
public async Task RollbackChanges()
{
var oldBehavoir = ChangeTracker.QueryTrackingBehavior;
var oldAutoDetect = ChangeTracker.AutoDetectChangesEnabled;
// this is the key - disable change tracking logic so EF does not check that there were exception in on of tracked entities
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
ChangeTracker.AutoDetectChangesEnabled = false;
var entries = ChangeTracker.Entries().ToList();
foreach (var entry in entries)
{
switch (entry.State)
{
case EntityState.Modified:
await entry.ReloadAsync();
break;
case EntityState.Deleted:
entry.State = EntityState.Modified; //Revert changes made to deleted entity.
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
}
}
ChangeTracker.QueryTrackingBehavior = oldBehavoir;
ChangeTracker.AutoDetectChangesEnabled = oldAutoDetect;
}