EffortConnection.RollbackToRestorePoint incorrectly works with identity fields of entities
rmaenk opened this issue · 6 comments
Description
During rollback operation from restore point identity field value may change for an entity that was in unchanged state.
The reason of this behavoir is using Insert
method of Effort.Internal.DbManagement.Engine.ExtendedTable
, that by default tries to generate new identity value for entity that already has it. In result restored entity has another Id
value and relations to other entities become broken.
Exception
Exception message:
System.Exception: System.Exception: Oops! There is an error when trying to generate the insert order. ---> System.Reflection.TargetInvocationException: Адресат вызова создал исключение.(The target of an invocation has generated an exception.) ---> NMemory.Exceptions.ForeignKeyViolationException: Foreign key violation [dbo_DocumentType :: ProjectId]. The key value [1] does not exists in the referenced table [dbo_Projects :: Id].. Error code: RelationError.
Stack trace:
in Effort.Shared.Provider.EffortRestorePoint.CreateOrderedEntities()
in Effort.Shared.Provider.EffortRestorePoint.Restore(DbContext context)
in Effort.Provider.EffortConnection.RollbackToRestorePoint(DbContext context)
Further technical details
- EF version: Entity Framework Classic 7.1.3
- EF Effort version: Z.EntityFramework.Classic.Effort 2.0.0
- Database Provider: System.Data.SqlClient
Suggested fix
Disable identity field per each table before rollback. Enable identity fields after rollback is completed.
public class EffortConnection : DbConnection {
...
public void RollbackToRestorePoint(DbContext context) {
if (this.RestorePoint == null) {
throw new Exception("You must create a restore point first");
}
if (this.DbContainer != null) {
ClearTables(context);
this.DbContainer.SetIdentityFields(false);
this.RestorePoint.Restore(context);
this.DbContainer.SetIdentityFields(true);
}
}
...
}
Workaround
In DB context for tests, add the wrapper method and use it instead of EffortConnection.RollbackToRestorePoint
:
public void EffortRollbackToRestorePoint() {
void SetIsIdentityFieldEnabled(object table, bool value) {
var propertyInfo = table.GetType()
.GetProperty("IsIdentityFieldEnabled",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
if (propertyInfo != null) {
propertyInfo.SetValue(table, value);
}
}
// this is optional, recommended if Query Cache extension is used
QueryCacheManager.ExpireAll();
var tables = (this.Database.Connection as EffortConnection)?.GetAllTables() ?? new List<ITable>();
foreach (var table in tables) {
SetIsIdentityFieldEnabled(table, false);
}
(this.Database.Connection as EffortConnection)?.RollbackToRestorePoint(this);
foreach (var table in tables) {
SetIsIdentityFieldEnabled(table, true);
}
}
Regards,
Roman
Hello @rmaenk ,
Thank you for reporting,
My developer will look at it.
Best Regards,
Jon
Performance Libraries
context.BulkInsert(list, options => options.BatchSize = 1000);
Entity Framework Extensions • Entity Framework Classic • Bulk Operations • Dapper Plus
Runtime Evaluation
Eval.Execute("x + y", new {x = 1, y = 2}); // return 3
C# Eval Function • SQL Eval Function
Hello @rmaenk ,
A new version has been released.
Could you look at it and let me know if everything works as expected.
Hello @JonathanMagnan,
Where I can find new version of Z.EntityFramework.Classic.Effort nuget package?
Here it is still version 2.0.0: here
EF Classic should be released during the day, I will let you know when the package will be available
Hello @rmaenk ,
The package is now available with the version 2.2.9
Hello @JonathanMagnan
I have tested this version. Looks good.
Thanks!
Roman