using System.Diagnostics.CodeAnalysis; using System.Reflection; using InABox.Core; using InABox.Database; namespace PRS.Shared; public class Update_7_06 : DatabaseUpdateScript { public override VersionNumber Version => new (7,06); public override bool Update() { var deletions = DbFactory.NewProvider(Logger.Main).Query( new Filter(x => x.Data).IsEqualTo("")); Logger.Send(LogType.Information, "", "Updating Deletions"); foreach (var deletion in deletions.ToObjects()) { Purge(deletion); } Logger.Send(LogType.Information, "", "Finished updating Deletions"); return true; } private static Dictionary>> _cascades = new(); private static Dictionary>>> _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>(); var setNulls = new List>>(); var childtypes = DbFactory.ProviderFactory.Types.Where(x => x.IsSubclassOf(typeof(Entity)) && x.GetCustomAttribute() == null); foreach (var childtype in childtypes) { // Get all registered types for this entitylink var fields = new List(); 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>? cascades) { LoadDeletions(type); return _cascades.TryGetValue(type, out cascades); } private static bool GetSetNulls(Type type, [NotNullWhen(true)] out List>>? setNulls) { LoadDeletions(type); return _setNulls.TryGetValue(type, out setNulls); } private static MethodInfo _deleteEntitiesMethod = typeof(Update_7_06).GetMethods(BindingFlags.NonPublic | BindingFlags.Static) .Single(x => x.Name == nameof(DeleteEntity) && x.IsGenericMethod); private static void DeleteEntity(Deletion deletion, Guid parentID, string parentField, DeletionData deletionData) where T : Entity, new() { var columns = DeletionData.DeletionColumns(); var delEntities = DbFactory.NewProvider(Logger.Main).QueryDeleted(deletion, new Filter(parentField).IsEqualTo(parentID), columns); var nDelntities = DbFactory.NewProvider(Logger.Main).Query(new Filter(parentField).IsEqualTo(parentID), columns); foreach (var row in delEntities.Rows.Concat(nDelntities.Rows)) { deletionData.DeleteEntity(row); CascadeDelete(typeof(T), deletion, row.Get(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).GetMethods(BindingFlags.NonPublic | BindingFlags.Static) .Single(x => x.Name == nameof(SetNullEntity) && x.IsGenericMethod); private static void SetNullEntity(List properties, Guid parentID, DeletionData deletionData) where T : Entity, new() { foreach (var property in properties) { var entities = DbFactory.NewProvider(Logger.Main).Query(new Filter(property).IsEqualTo(parentID), Columns.None().Add(x => x.ID)); foreach (var row in entities.Rows) { deletionData.SetNullEntity(row.Get(x => x.ID), property, parentID); } } } private static void SetNullEntity(Type T, List 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(Deletion deletion) where T : Entity, new() { var entities = DbFactory.NewProvider(Logger.Main).QueryDeleted(deletion, null, DeletionData.DeletionColumns()).ToObjects().ToList(); 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.NewProvider(Logger.Main).Save(newDeletion); } DbFactory.NewProvider(Logger.Main).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).GetMethod(nameof(PurgeEntityType), BindingFlags.NonPublic | BindingFlags.Static)!; purgeMethod.MakeGenericMethod(entityType).Invoke(null, new object[] { deletion }); DbFactory.NewProvider(Logger.Main).Purge(deletion); } }