Update_7_06.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Reflection;
  3. using InABox.Core;
  4. using InABox.Database;
  5. namespace PRS.Shared;
  6. public class Update_7_06 : DatabaseUpdateScript
  7. {
  8. public override VersionNumber Version => new (7,06);
  9. public override bool Update()
  10. {
  11. var deletions = DbFactory.NewProvider(Logger.Main).Query<Deletion>(
  12. new Filter<Deletion>(x => x.Data).IsEqualTo(""));
  13. Logger.Send(LogType.Information, "", "Updating Deletions");
  14. foreach (var deletion in deletions.ToObjects<Deletion>())
  15. {
  16. Purge(deletion);
  17. }
  18. Logger.Send(LogType.Information, "", "Finished updating Deletions");
  19. return true;
  20. }
  21. private static Dictionary<Type, List<Tuple<Type, string>>> _cascades = new();
  22. private static Dictionary<Type, List<Tuple<Type, List<string>>>> _setNulls = new();
  23. private static void LoadDeletions(Type type)
  24. {
  25. if (_cascades.ContainsKey(type)) return;
  26. // Get the EntityLink that is associated with this class
  27. var linkclass = CoreUtils.TypeList(
  28. new[] { type.Assembly },
  29. x => typeof(IEntityLink).GetTypeInfo().IsAssignableFrom(x) && x.GetInheritedGenericTypeArguments().FirstOrDefault() == type
  30. ).FirstOrDefault();
  31. // if The entitylink does not exist, we don't need to do anything
  32. if (linkclass == null)
  33. return;
  34. var cascades = new List<Tuple<Type, string>>();
  35. var setNulls = new List<Tuple<Type, List<string>>>();
  36. var childtypes = DbFactory.ProviderFactory.Types.Where(x => x.IsSubclassOf(typeof(Entity)) && x.GetCustomAttribute<AutoEntity>() == null);
  37. foreach (var childtype in childtypes)
  38. {
  39. // Get all registered types for this entitylink
  40. var fields = new List<string>();
  41. var bDelete = false;
  42. // Find any IEntityLink<> properties that refer back to this class
  43. var childprops = CoreUtils.PropertyList(childtype, x => x.PropertyType == linkclass);
  44. foreach (var childprop in childprops)
  45. {
  46. var fieldname = string.Format("{0}.ID", childprop.Name);
  47. var attr = childprop.GetCustomAttributes(typeof(EntityRelationshipAttribute), true).FirstOrDefault();
  48. if (attr != null && ((EntityRelationshipAttribute)attr).Action.Equals(DeleteAction.Cascade))
  49. {
  50. cascades.Add(new(childtype, fieldname));
  51. bDelete = true;
  52. break;
  53. }
  54. fields.Add(fieldname);
  55. }
  56. if (!bDelete && fields.Any())
  57. {
  58. setNulls.Add(new(childtype, fields));
  59. }
  60. }
  61. _cascades[type] = cascades;
  62. _setNulls[type] = setNulls;
  63. }
  64. private static bool GetCascades(Type type, [NotNullWhen(true)] out List<Tuple<Type, string>>? cascades)
  65. {
  66. LoadDeletions(type);
  67. return _cascades.TryGetValue(type, out cascades);
  68. }
  69. private static bool GetSetNulls(Type type, [NotNullWhen(true)] out List<Tuple<Type, List<string>>>? setNulls)
  70. {
  71. LoadDeletions(type);
  72. return _setNulls.TryGetValue(type, out setNulls);
  73. }
  74. private static MethodInfo _deleteEntitiesMethod = typeof(Update_7_06).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
  75. .Single(x => x.Name == nameof(DeleteEntity) && x.IsGenericMethod);
  76. private static void DeleteEntity<T>(Deletion deletion, Guid parentID, string parentField, DeletionData deletionData) where T : Entity, new()
  77. {
  78. var columns = DeletionData.DeletionColumns<T>();
  79. var delEntities = DbFactory.NewProvider(Logger.Main).QueryDeleted(deletion, new Filter<T>(parentField).IsEqualTo(parentID), columns);
  80. var nDelntities = DbFactory.NewProvider(Logger.Main).Query(new Filter<T>(parentField).IsEqualTo(parentID), columns);
  81. foreach (var row in delEntities.Rows.Concat(nDelntities.Rows))
  82. {
  83. deletionData.DeleteEntity<T>(row);
  84. CascadeDelete(typeof(T), deletion, row.Get<T, Guid>(x => x.ID), deletionData);
  85. }
  86. }
  87. private static void DeleteEntity(Type T, Deletion deletion, Guid parentID, string parentField, DeletionData deletionData)
  88. {
  89. _deleteEntitiesMethod.MakeGenericMethod(T).Invoke(null, new object?[] { deletion, parentID, parentField, deletionData });
  90. }
  91. private static MethodInfo _setNullEntityMethod = typeof(Update_7_06).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
  92. .Single(x => x.Name == nameof(SetNullEntity) && x.IsGenericMethod);
  93. private static void SetNullEntity<T>(List<string> properties, Guid parentID, DeletionData deletionData) where T : Entity, new()
  94. {
  95. foreach (var property in properties)
  96. {
  97. var entities = DbFactory.NewProvider(Logger.Main).Query(new Filter<T>(property).IsEqualTo(parentID), Columns.None<T>().Add(x => x.ID));
  98. foreach (var row in entities.Rows)
  99. {
  100. deletionData.SetNullEntity<T>(row.Get<T, Guid>(x => x.ID), property, parentID);
  101. }
  102. }
  103. }
  104. private static void SetNullEntity(Type T, List<string> properties, Guid parentID, DeletionData deletionData)
  105. {
  106. _setNullEntityMethod.MakeGenericMethod(T).Invoke(null, new object?[] { properties, parentID, deletionData });
  107. }
  108. private static void CascadeDelete(Type type, Deletion deletion, Guid parentID, DeletionData deletionData)
  109. {
  110. if (GetCascades(type, out var cascades))
  111. {
  112. foreach (var cascade in cascades)
  113. {
  114. DeleteEntity(cascade.Item1, deletion, parentID, cascade.Item2, deletionData);
  115. }
  116. }
  117. if (GetSetNulls(type, out var setNulls))
  118. {
  119. foreach (var setNull in setNulls)
  120. {
  121. SetNullEntity(setNull.Item1, setNull.Item2, parentID, deletionData);
  122. }
  123. }
  124. }
  125. // Referenced via reflection.
  126. private static void PurgeEntityType<T>(Deletion deletion) where T : Entity, new()
  127. {
  128. var entities = DbFactory.NewProvider(Logger.Main).QueryDeleted(deletion, null, DeletionData.DeletionColumns<T>()).ToObjects<T>().ToList();
  129. var deletionData = new DeletionData();
  130. foreach (var entity in entities)
  131. {
  132. deletionData.DeleteEntity(entity);
  133. CascadeDelete(typeof(T), deletion, entity.ID, deletionData);
  134. }
  135. if (deletionData.Cascades.Count > 0 || deletionData.SetNulls.Count > 0)
  136. {
  137. var tableName = typeof(T).Name;
  138. var newDeletion = new Deletion()
  139. {
  140. DeletionDate = DateTime.Now,
  141. HeadTable = tableName,
  142. Description = $"Deleted {entities.Count} entries",
  143. DeletedBy = deletion.DeletedBy,
  144. Data = Serialization.Serialize(deletionData)
  145. };
  146. DbFactory.NewProvider(Logger.Main).Save(newDeletion);
  147. }
  148. DbFactory.NewProvider(Logger.Main).Purge(entities);
  149. }
  150. public static void Purge(Deletion deletion)
  151. {
  152. if (deletion.ID == Guid.Empty)
  153. {
  154. Logger.Send(LogType.Error, "", "Empty Deletion ID");
  155. return;
  156. }
  157. var entityType = CoreUtils.Entities.FirstOrDefault(x => x.Name == deletion.HeadTable);
  158. if (entityType is null)
  159. {
  160. Logger.Send(LogType.Error, "", $"Entity {deletion.HeadTable} does not exist");
  161. return;
  162. }
  163. var purgeMethod = typeof(Update_7_06).GetMethod(nameof(PurgeEntityType), BindingFlags.NonPublic | BindingFlags.Static)!;
  164. purgeMethod.MakeGenericMethod(entityType).Invoke(null, new object[] { deletion });
  165. DbFactory.NewProvider(Logger.Main).Purge<Deletion>(deletion);
  166. }
  167. }