/// <summary> /// Ensures the automatic history rollback. /// The entity goes back to a defined previous state or its last previous state. /// </summary> /// <param name="context">The context.</param> /// <param name="entity">The entity being restaured to its previous state.</param> /// <param name="historyNumber">[Optional] The history number from where rollback is done.</param> public static void AutoHistoryRollback <T>(this DbContext context, ref T entity, int?historyNumber = null) where T : class { if (String.IsNullOrEmpty(context.Entry(entity).PrimaryKey())) { throw new ArgumentNullException(String.Format("The entity {0} doesn't have a valid primary key, so can not be rollbacked", entity.GetType().Name)); } if (context.Entry(entity).State == EntityState.Added) { throw new Exception(String.Format("The entity {0} is added, it can not be rollbacked", entity.GetType().Name)); } context.Entry(entity).State = EntityState.Detached; string keyString = context.Entry(entity).PrimaryKey(); var historicChanges = context.Set <AutoHistory>().Where(h => h.RowId == keyString); AutoHistory history = null; if (historicChanges.Count() == 1) { return; } if (historyNumber != null) { history = historicChanges.Where(h => h.Id == historyNumber).FirstOrDefault(); } else { history = historicChanges.OrderByDescending(h => h.Id).FirstOrDefault(); } if (history != null) { try { // If this entity type does not exist in its assembly, GetType throws a TypeLoadException. //Type elementType = Type.GetType(history.EntityName, true); JObject jsonObj = JObject.Parse(history.Changed); entity = Newtonsoft.Json.JsonConvert.DeserializeObject <T>(jsonObj.GetValue("before").ToString()); context.Remove(history); context.Entry(entity).State = EntityState.Modified; } catch (Newtonsoft.Json.JsonReaderException ex) { Console.WriteLine("{0}: Unable to load json string", ex.Message); } catch (TypeLoadException e) { Console.WriteLine("{0}: Unable to load type", e.GetType().Name); } } }
private static void WriteHistoryDeletedState(AutoHistory history, EntityEntry entry, IEnumerable <PropertyEntry> properties) { dynamic json = new System.Dynamic.ExpandoObject(); foreach (var prop in properties) { ((IDictionary <String, Object>)json)[prop.Metadata.Name] = prop.OriginalValue; } history.RowId = entry.PrimaryKey(); history.Kind = EntityState.Deleted; history.Changed = JsonSerializer.Serialize(json, AutoHistoryOptions.Instance.JsonSerializerOptions); }
/// <summary> /// Ensures the automatic history. /// </summary> /// <param name="context">The context.</param> public static void EnsureAutoHistory(this DbContext context) { // TODO: only record the changed properties. var jsonSetting = new JsonSerializerSettings { ContractResolver = new EntityContractResolver(context), }; var entries = context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged && e.State != EntityState.Detached && IncludeEntity(e)).ToList(); if (entries.Count == 0) { return; } foreach (var entry in entries) { var history = new AutoHistory { TypeName = entry.Entity.GetType().FullName, }; switch (entry.State) { case EntityState.Added: // REVIEW: what's the best way to do this? history.SourceId = "0"; history.Kind = EntityState.Added; history.AfterJson = JsonConvert.SerializeObject(entry.Entity, Formatting.Indented, jsonSetting); break; case EntityState.Deleted: history.SourceId = entry.PrimaryKey(); history.Kind = EntityState.Deleted; history.BeforeJson = JsonConvert.SerializeObject(entry.Entity, Formatting.Indented, jsonSetting); break; case EntityState.Modified: history.SourceId = entry.PrimaryKey(); history.Kind = EntityState.Modified; history.BeforeJson = JsonConvert.SerializeObject(entry.Original(), Formatting.Indented, jsonSetting); history.AfterJson = JsonConvert.SerializeObject(entry.Entity, Formatting.Indented, jsonSetting); break; default: continue; } context.Add(history); } }
private static void WriteHistoryAddedState(AutoHistory history, IEnumerable <PropertyEntry> properties) { dynamic json = new System.Dynamic.ExpandoObject(); foreach (var prop in properties) { if (prop.Metadata.IsKey() || prop.Metadata.IsForeignKey()) { continue; } ((IDictionary <String, Object>)json)[prop.Metadata.Name] = prop.CurrentValue; } // REVIEW: what's the best way to set the RowId? history.RowId = "0"; history.Kind = EntityState.Added; history.Changed = JsonSerializer.Serialize(json); }
private static void WriteHistoryModifiedState(AutoHistory history, EntityEntry entry, IEnumerable <PropertyEntry> properties) { dynamic json = new System.Dynamic.ExpandoObject(); dynamic bef = new System.Dynamic.ExpandoObject(); dynamic aft = new System.Dynamic.ExpandoObject(); PropertyValues databaseValues = null; foreach (var prop in properties) { if (prop.IsModified) { if (prop.OriginalValue != null) { if (!prop.OriginalValue.Equals(prop.CurrentValue)) { ((IDictionary <String, Object>)bef)[prop.Metadata.Name] = prop.OriginalValue; } else { databaseValues ??= entry.GetDatabaseValues(); var originalValue = databaseValues.GetValue <object>(prop.Metadata.Name); ((IDictionary <String, Object>)bef)[prop.Metadata.Name] = originalValue; } } else { ((IDictionary <String, Object>)bef)[prop.Metadata.Name] = null; } ((IDictionary <String, Object>)aft)[prop.Metadata.Name] = prop.CurrentValue; } } ((IDictionary <String, Object>)json)["before"] = bef; ((IDictionary <String, Object>)json)["after"] = aft; history.RowId = entry.PrimaryKey(); history.Kind = EntityState.Modified; history.Changed = JsonSerializer.Serialize(json, AutoHistoryOptions.Instance.JsonSerializerOptions); }
internal static AutoHistory AutoHistory(this EntityEntry entry) { var history = new AutoHistory { TableName = entry.Metadata.Relational().TableName, }; // Get the mapped properties for the entity type. // (include shadow properties, not include navigations & references) var properties = entry.Properties; var json = new JObject(); switch (entry.State) { case EntityState.Added: foreach (var prop in properties) { if (prop.Metadata.IsKey() || prop.Metadata.IsForeignKey()) { continue; } //json[prop.Metadata.Name] = new JObject(prop.CurrentValue); json[prop.Metadata.Name] = JToken.FromObject(prop.CurrentValue); } // REVIEW: what's the best way to set the RowId? history.RowId = "0"; history.Kind = EntityState.Added; history.Changed = json.ToString(); break; case EntityState.Modified: var bef = new JObject(); var aft = new JObject(); foreach (var prop in properties) { if (prop.IsModified) { bef[prop.Metadata.Name] = JToken.FromObject(prop.OriginalValue); aft[prop.Metadata.Name] = JToken.FromObject(prop.CurrentValue); } } json["Before"] = bef; json["After"] = aft; history.RowId = entry.PrimaryKey(); history.Kind = EntityState.Modified; history.Changed = json.ToString(); break; case EntityState.Deleted: foreach (var prop in properties) { json[prop.Metadata.Name] = JToken.FromObject(prop.OriginalValue); } history.RowId = entry.PrimaryKey(); history.Kind = EntityState.Deleted; history.Changed = json.ToString(); break; case EntityState.Detached: case EntityState.Unchanged: default: throw new NotSupportedException("AutoHistory only support Deleted and Modified entity."); } return(history); }