Browse Source

Database update script to transition old deletion data.

Kenric Nugteren 2 years ago
parent
commit
720534e963
1 changed files with 188 additions and 0 deletions
  1. 188 0
      prs.shared/DatabaseUpdateScripts.cs

+ 188 - 0
prs.shared/DatabaseUpdateScripts.cs

@@ -8,6 +8,8 @@ using System.Linq.Expressions;
 using System.Text;
 using System.Threading.Tasks;
 using Syncfusion.Windows.Tools.Controls;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
 
 namespace PRS.Shared
 {
@@ -21,6 +23,7 @@ namespace PRS.Shared
             DataUpdater.RegisterUpdateScript("6.39", Update_6_39);
             DataUpdater.RegisterUpdateScript("6.43", Update_6_43);
             DataUpdater.RegisterUpdateScript("7.00", Update_7_00);
+            DataUpdater.RegisterUpdateScript("7.06", Update_7_06);
         }
 
         private static Dictionary<string, Tuple<string, string>> _6_31_module_map = new()
@@ -343,5 +346,190 @@ namespace PRS.Shared
             return true;
         }
         
+        private class Update_7_06_Class
+        {
+            private static Dictionary<Type, List<Tuple<Type, string>>> _cascades = new();
+            private static Dictionary<Type, List<Tuple<Type, List<string>>>> _setNulls = new();
+
+            private static void LoadDeletions(Type type)
+            {
+                if (_cascades.ContainsKey(type)) return;
+
+                // Get the EntityLink that is associated with this class
+                var linkclass = CoreUtils.TypeList(
+                    new[] { type.Assembly },
+                    x => typeof(IEntityLink).GetTypeInfo().IsAssignableFrom(x) && x.GetInheritedGenericTypeArguments().FirstOrDefault() == type
+                ).FirstOrDefault();
+
+                // if The entitylink does not exist, we don't need to do anything
+                if (linkclass == null)
+                    return;
+
+                var cascades = new List<Tuple<Type, string>>();
+                var setNulls = new List<Tuple<Type, List<string>>>();
+
+                var childtypes = DbFactory.Provider.Types.Where(x => x.IsSubclassOf(typeof(Entity)) && x.GetCustomAttribute<AutoEntity>() == null);
+                foreach (var childtype in childtypes)
+                {
+                    // Get all registered types for this entitylink
+                    var fields = new List<string>();
+                    var bDelete = false;
+
+                    // Find any IEntityLink<> properties that refer back to this class
+                    var childprops = CoreUtils.PropertyList(childtype, x => x.PropertyType == linkclass);
+
+                    foreach (var childprop in childprops)
+                    {
+                        var fieldname = string.Format("{0}.ID", childprop.Name);
+                        var attr = childprop.GetCustomAttributes(typeof(EntityRelationshipAttribute), true).FirstOrDefault();
+                        if (attr != null && ((EntityRelationshipAttribute)attr).Action.Equals(DeleteAction.Cascade))
+                        {
+                            cascades.Add(new(childtype, fieldname));
+                            bDelete = true;
+                            break;
+                        }
+
+                        fields.Add(fieldname);
+                    }
+                    if (!bDelete && fields.Any())
+                    {
+                        setNulls.Add(new(childtype, fields));
+                    }
+                }
+
+                _cascades[type] = cascades;
+                _setNulls[type] = setNulls;
+            }
+            private static bool GetCascades(Type type, [NotNullWhen(true)] out List<Tuple<Type, string>>? cascades)
+            {
+                LoadDeletions(type);
+                return _cascades.TryGetValue(type, out cascades);
+            }
+            private static bool GetSetNulls(Type type, [NotNullWhen(true)] out List<Tuple<Type, List<string>>>? setNulls)
+            {
+                LoadDeletions(type);
+                return _setNulls.TryGetValue(type, out setNulls);
+            }
+
+
+            private static MethodInfo _deleteEntitiesMethod = typeof(Update_7_06_Class).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
+                .Single(x => x.Name == nameof(DeleteEntity) && x.IsGenericMethod);
+            private static void DeleteEntity<T>(Deletion deletion, Guid parentID, string parentField, DeletionData deletionData) where T : Entity, new()
+            {
+                var columns = DeletionData.DeletionColumns<T>();
+                var delEntities = DbFactory.Provider.QueryDeleted(deletion, new Filter<T>(parentField).IsEqualTo(parentID), columns);
+                var nDelntities = DbFactory.Provider.Query(new Filter<T>(parentField).IsEqualTo(parentID), columns);
+                foreach (var row in delEntities.Rows.Concat(nDelntities.Rows))
+                {
+                    deletionData.DeleteEntity<T>(row);
+                    CascadeDelete(typeof(T), deletion, row.Get<T, Guid>(x => x.ID), deletionData);
+                }
+            }
+
+            private static void DeleteEntity(Type T, Deletion deletion, Guid parentID, string parentField, DeletionData deletionData)
+            {
+                _deleteEntitiesMethod.MakeGenericMethod(T).Invoke(null, new object?[] { deletion, parentID, parentField, deletionData });
+            }
+
+
+            private static MethodInfo _setNullEntityMethod = typeof(Update_7_06_Class).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
+                .Single(x => x.Name == nameof(SetNullEntity) && x.IsGenericMethod);
+            private static void SetNullEntity<T>(List<string> properties, Guid parentID, DeletionData deletionData) where T : Entity, new()
+            {
+                foreach (var property in properties)
+                {
+                    var entities = DbFactory.Provider.Query(new Filter<T>(property).IsEqualTo(parentID), new Columns<T>(x => x.ID));
+                    foreach (var row in entities.Rows)
+                    {
+                        deletionData.SetNullEntity<T>(row.Get<T, Guid>(x => x.ID), property, parentID);
+                    }
+                }
+            }
+
+            private static void SetNullEntity(Type T, List<string> properties, Guid parentID, DeletionData deletionData)
+            {
+                _setNullEntityMethod.MakeGenericMethod(T).Invoke(null, new object?[] { properties, parentID, deletionData });
+            }
+
+
+            private static void CascadeDelete(Type type, Deletion deletion, Guid parentID, DeletionData deletionData)
+            {
+                if (GetCascades(type, out var cascades))
+                {
+                    foreach (var cascade in cascades)
+                    {
+                        DeleteEntity(cascade.Item1, deletion, parentID, cascade.Item2, deletionData);
+                    }
+                }
+                if (GetSetNulls(type, out var setNulls))
+                {
+                    foreach (var setNull in setNulls)
+                    {
+                        SetNullEntity(setNull.Item1, setNull.Item2, parentID, deletionData);
+                    }
+                }
+            }
+
+            // Referenced via reflection.
+            private static void PurgeEntityType<T>(Deletion deletion) where T : Entity, new()
+            {
+                var entities = DbFactory.Provider.QueryDeleted(deletion, null, DeletionData.DeletionColumns<T>()).ToList<T>();
+
+                var deletionData = new DeletionData();
+                foreach (var entity in entities)
+                {
+                    deletionData.DeleteEntity(entity);
+                    CascadeDelete(typeof(T), deletion, entity.ID, deletionData);
+                }
+
+                if (deletionData.Cascades.Count > 0 || deletionData.SetNulls.Count > 0)
+                {
+                    var tableName = typeof(T).Name;
+                    var newDeletion = new Deletion()
+                    {
+                        DeletionDate = DateTime.Now,
+                        HeadTable = tableName,
+                        Description = $"Deleted {entities.Count} entries",
+                        DeletedBy = deletion.DeletedBy,
+                        Data = Serialization.Serialize(deletionData)
+                    };
+                    DbFactory.Provider.Save(newDeletion);
+                }
+                DbFactory.Provider.Purge(entities);
+            }
+
+            public static void Purge(Deletion deletion)
+            {
+                if (deletion.ID == Guid.Empty)
+                {
+                    Logger.Send(LogType.Error, "", "Empty Deletion ID");
+                    return;
+                }
+
+                var entityType = CoreUtils.Entities.FirstOrDefault(x => x.Name == deletion.HeadTable);
+                if (entityType is null)
+                {
+                    Logger.Send(LogType.Error, "", $"Entity {deletion.HeadTable} does not exist");
+                    return;
+                }
+                var purgeMethod = typeof(Update_7_06_Class).GetMethod(nameof(PurgeEntityType), BindingFlags.NonPublic | BindingFlags.Static)!;
+                purgeMethod.MakeGenericMethod(entityType).Invoke(null, new object[] { deletion });
+
+                DbFactory.Provider.Purge<Deletion>(deletion);
+            }
+        }
+
+        private static bool Update_7_06()
+        {
+            var deletions = DbFactory.Provider.Query<Deletion>(
+                new Filter<Deletion>(x => x.Data).IsEqualTo(""));
+            Logger.Send(LogType.Information, "", "Updating Deletions");
+            foreach (var deletion in deletions.ToObjects<Deletion>())
+            {
+                Update_7_06_Class.Purge(deletion);
+            }
+            Logger.Send(LogType.Information, "", "Finished updating Deletions");
+            return true;
+        }
     }
 }