| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 | 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.Provider.Query<Deletion>(            new Filter<Deletion>(x => x.Data).IsEqualTo(""));        Logger.Send(LogType.Information, "", "Updating Deletions");        foreach (var deletion in deletions.ToObjects<Deletion>())        {            Purge(deletion);        }        Logger.Send(LogType.Information, "", "Finished updating Deletions");        return true;    }        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).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).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>()).ToObjects<T>().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.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).GetMethod(nameof(PurgeEntityType), BindingFlags.NonPublic | BindingFlags.Static)!;        purgeMethod.MakeGenericMethod(entityType).Invoke(null, new object[] { deletion });        DbFactory.Provider.Purge<Deletion>(deletion);    }}
 |