|  | @@ -4,6 +4,7 @@ using System.Data.SQLite;
 | 
	
		
			
				|  |  |  using System.Diagnostics.CodeAnalysis;
 | 
	
		
			
				|  |  |  using System.Linq.Expressions;
 | 
	
		
			
				|  |  |  using System.Reflection;
 | 
	
		
			
				|  |  | +using System.Resources;
 | 
	
		
			
				|  |  |  using System.Runtime.Serialization.Formatters.Binary;
 | 
	
		
			
				|  |  |  using System.Text;
 | 
	
		
			
				|  |  |  using InABox.Core;
 | 
	
	
		
			
				|  | @@ -93,14 +94,10 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private bool RebuildTriggers = false;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private SQLiteProvider()
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          public SQLiteProvider(string filename)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              var path = Path.GetDirectoryName(filename);
 | 
	
		
			
				|  |  | -            if (!Directory.Exists(path))
 | 
	
		
			
				|  |  | +            if (!path.IsNullOrWhiteSpace())
 | 
	
		
			
				|  |  |                  Directory.CreateDirectory(path);
 | 
	
		
			
				|  |  |              URL = filename;
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -109,7 +106,10 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public event LogEvent? OnLog;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public Type[] Types { get; set; }
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// An array containing every <see cref="Entity"/> type in the database.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        public Type[] Types { get; set; } = [];
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public void Start()
 | 
	
		
			
				|  |  |          {
 | 
	
	
		
			
				|  | @@ -150,32 +150,35 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              //ExecuteSQL("PRAGMA foreign_keys = on;");
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +            //using var profiler = new Profiler(true);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              // Need to arrange the typelist to ensure that foreign keys
 | 
	
		
			
				|  |  |              // refer to tables that already exist
 | 
	
		
			
				|  |  |              var ordered = new List<Type>();
 | 
	
		
			
				|  |  |              foreach (var type in Types)
 | 
	
		
			
				|  |  |                  LoadType(type, ordered);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +            //profiler.Log("Ordered");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              //Load up the metadata
 | 
	
		
			
				|  |  |              var metadata = LoadMetaData();
 | 
	
		
			
				|  |  |              
 | 
	
		
			
				|  |  |              var table = typeof(CustomProperty).EntityName().Split('.').Last();
 | 
	
		
			
				|  |  | -            if (!metadata.ContainsKey(table))
 | 
	
		
			
				|  |  | +            if (!metadata.TryGetValue(table, out var value))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                OnLog?.Invoke(LogType.Information, "Creating Table: " + typeof(CustomProperty).EntityName().Split('.').Last());
 | 
	
		
			
				|  |  | -                using (var access = GetWriteAccess())
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    CreateTable(access, typeof(CustomProperty), true, new CustomProperty[] { });
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +                OnLog?.Invoke(LogType.Information, $"Creating Table: {nameof(CustomProperty)}");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                using var access = GetWriteAccess();
 | 
	
		
			
				|  |  | +                CreateTable(access, typeof(CustomProperty), true, []);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              else
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                using (var access = GetWriteAccess())
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    CheckFields(access, typeof(CustomProperty), metadata[table].Item1, new CustomProperty[] { });
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +                using var access = GetWriteAccess();
 | 
	
		
			
				|  |  | +                CheckFields(access, typeof(CustomProperty), value.Item1, []);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +            //profiler.Log("Ordered");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              var customproperties = Load<CustomProperty>(); // new Filter<CustomProperty>(x => x.Class).IsEqualTo(type.EntityName()))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -218,7 +221,6 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |                  if (type.GetCustomAttribute<AutoEntity>() == null)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    LoadDeletions(type);
 | 
	
		
			
				|  |  |                      table = type.EntityName().Split('.').Last();
 | 
	
		
			
				|  |  |                      using (var access = GetWriteAccess())
 | 
	
		
			
				|  |  |                      {
 | 
	
	
		
			
				|  | @@ -625,65 +627,80 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |              return result;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private List<string> LoadTriggers(Type type)
 | 
	
		
			
				|  |  | +        private void LoadDeletions(Type type)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var result = new List<string>();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            // 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 result;
 | 
	
		
			
				|  |  | +            var cascades = new List<Tuple<Type, List<string>>>();
 | 
	
		
			
				|  |  | +            var setNulls = new List<Tuple<Type, List<string>>>();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var actions = new List<string>();
 | 
	
		
			
				|  |  | -            var childtypes = Types.Where(x => /* (x != type) && */ x.IsSubclassOf(typeof(Entity)) && x.GetCustomAttribute<AutoEntity>() == null);
 | 
	
		
			
				|  |  | -            foreach (var childtype in childtypes)
 | 
	
		
			
				|  |  | +            foreach(var otherType in Types.Where(x => x.GetCustomAttribute<AutoEntity>() is null))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                // Get all registererd types for this entitylink
 | 
	
		
			
				|  |  | -                var fields = new List<string>();
 | 
	
		
			
				|  |  | -                var bDelete = false;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                var tablename = childtype.EntityName().Split('.').Last();
 | 
	
		
			
				|  |  | -                // Find any IEntityLink<> properties that refer back to this class
 | 
	
		
			
				|  |  | -                var childprops = CoreUtils.PropertyList(childtype, x => x.PropertyType == linkclass);
 | 
	
		
			
				|  |  | +                var setNullFields = new List<string>();
 | 
	
		
			
				|  |  | +                var cascadeFields = new List<string>();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                foreach (var childprop in childprops)
 | 
	
		
			
				|  |  | +                var props = DatabaseSchema.RootProperties(otherType)
 | 
	
		
			
				|  |  | +                    .Where(x => x.IsEntityLink && x.PropertyType.GetInterfaceDefinition(typeof(IEntityLink<>))?.GenericTypeArguments[0] == type);
 | 
	
		
			
				|  |  | +                foreach(var prop in props)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    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))
 | 
	
		
			
				|  |  | +                    var fieldname = $"{prop.Name}.ID";
 | 
	
		
			
				|  |  | +                    if(prop.GetAttribute<EntityRelationshipAttribute>() is EntityRelationshipAttribute attr
 | 
	
		
			
				|  |  | +                        && attr.Action == DeleteAction.Cascade)
 | 
	
		
			
				|  |  |                      {
 | 
	
		
			
				|  |  | -                        fields.Clear();
 | 
	
		
			
				|  |  | -                        bDelete = true;
 | 
	
		
			
				|  |  | -                        fields.Add(fieldname);
 | 
	
		
			
				|  |  | -                        break;
 | 
	
		
			
				|  |  | +                        cascadeFields.Add(fieldname);
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  | +                    else
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        setNullFields.Add(fieldname);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                cascadeFields.Sort();
 | 
	
		
			
				|  |  | +                setNullFields.Sort();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                    fields.Add(fieldname);
 | 
	
		
			
				|  |  | +                cascades.Add(new(otherType, cascadeFields));
 | 
	
		
			
				|  |  | +                setNulls.Add(new(otherType, setNullFields));
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                    //actions[childtype] = String.Format("UPDATE {1} SET [{0}.ID] = NULL WHERE [id] = old.[ID];", tablename)
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +            if(cascades.Count > 0)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                _cascades[type] = cascades;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if(setNulls.Count > 0)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                _setNulls[type] = setNulls;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        private string? LoadTrigger(Type type)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var actions = new List<string>();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                if (fields.Any())
 | 
	
		
			
				|  |  | +            if(_setNulls.TryGetValue(type, out var setNulls))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                foreach(var (otherType, fields) in setNulls)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  | -                    if (bDelete)
 | 
	
		
			
				|  |  | -                        actions.Add(string.Format("DELETE FROM {0} WHERE {1} = old.ID;", tablename, fields.First()));
 | 
	
		
			
				|  |  | -                    else
 | 
	
		
			
				|  |  | -                        foreach (var field in fields)
 | 
	
		
			
				|  |  | -                            actions.Add(string.Format("UPDATE {0} SET {1} = NULL WHERE {1} = old.ID;", tablename, field));
 | 
	
		
			
				|  |  | +                    foreach(var field in fields)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        actions.Add($"UPDATE {otherType.Name} SET [{field}] = NULL WHERE [{field}] = old.ID;");
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if(_cascades.TryGetValue(type, out var cascades))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                foreach(var (otherType, fields) in cascades)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    foreach(var field in fields)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        actions.Add($"DELETE FROM {otherType.Name} WHERE [{field}] = old.ID;");
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (actions.Any())
 | 
	
		
			
				|  |  | -                result.Add(string.Format("CREATE TRIGGER {0}_BEFOREDELETE BEFORE DELETE ON {0} FOR EACH ROW BEGIN {1} END",
 | 
	
		
			
				|  |  | -                    type.EntityName().Split('.').Last(), string.Join(" ", actions)));
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            return result;
 | 
	
		
			
				|  |  | +            if (actions.Count != 0)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return $"CREATE TRIGGER {type.Name}_BEFOREDELETE BEFORE DELETE ON {type.Name} FOR EACH ROW BEGIN {string.Join(' ', actions)} END";
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            else
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return null;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public void ForceRecreateViews()
 | 
	
	
		
			
				|  | @@ -1134,19 +1151,24 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private void CheckTriggers(SQLiteWriteAccessor access, Type type, Dictionary<string, string> db_triggers)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | +            LoadDeletions(type);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              /*
 | 
	
		
			
				|  |  |  #if PURGE
 | 
	
		
			
				|  |  |              foreach (var trigger in db_triggers.Keys)
 | 
	
		
			
				|  |  |                  ExecuteSQL(access, string.Format("DROP TRIGGER {0}", trigger));
 | 
	
		
			
				|  |  |  #else*/
 | 
	
		
			
				|  |  | -            var type_triggers = LoadTriggers(type);
 | 
	
		
			
				|  |  | +            var type_trigger = LoadTrigger(type);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            foreach (var trigger in db_triggers.Keys)
 | 
	
		
			
				|  |  | -                if (!type_triggers.Contains(db_triggers[trigger]))
 | 
	
		
			
				|  |  | -                    ExecuteSQL(access, string.Format("DROP TRIGGER {0}", trigger));
 | 
	
		
			
				|  |  | -            foreach (var trigger in type_triggers)
 | 
	
		
			
				|  |  | -                if (!db_triggers.ContainsValue(trigger))
 | 
	
		
			
				|  |  | -                    ExecuteSQL(access, trigger);
 | 
	
		
			
				|  |  | +            foreach (var (key, trigger) in db_triggers)
 | 
	
		
			
				|  |  | +                if (!Equals(type_trigger, trigger))
 | 
	
		
			
				|  |  | +                    ExecuteSQL(access, $"DROP TRIGGER {key}");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if(type_trigger is not null)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if (!db_triggers.ContainsValue(type_trigger))
 | 
	
		
			
				|  |  | +                    ExecuteSQL(access, type_trigger);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |  //#endif
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2363,29 +2385,6 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          #endregion
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        #region Schema Handling
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        public Dictionary<string, Type> GetSchema()
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            var result = new Dictionary<string, Type>();
 | 
	
		
			
				|  |  | -            return result;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        public void CreateSchema(params Type[] types)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        public void SaveSchema(Dictionary<string, Type> schema)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        public void UpgradeSchema(params Type[] types)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        #endregion
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          #region CRUD Operations
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  |          public static object[] GetValues(IDataReader reader, int count)
 | 
	
	
		
			
				|  | @@ -2886,68 +2885,14 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private Dictionary<Type, List<Tuple<Type, string>>> _cascades = new();
 | 
	
		
			
				|  |  | +        private Dictionary<Type, List<Tuple<Type, List<string>>>> _cascades = new();
 | 
	
		
			
				|  |  |          private Dictionary<Type, List<Tuple<Type, List<string>>>> _setNulls = new();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private const int deleteBatchSize = 100;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private void LoadDeletions(Type type)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            // 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 = 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));
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            if(cascades.Count > 0)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                _cascades[type] = cascades;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            if(setNulls.Count > 0)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                _setNulls[type] = setNulls;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          private readonly MethodInfo _deleteEntitiesMethod = typeof(SQLiteProvider).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
 | 
	
		
			
				|  |  |              .Single(x => x.Name == nameof(DeleteEntity) && x.IsGenericMethod);
 | 
	
		
			
				|  |  | -        private void DeleteEntity<T>(Guid[] parentIDs, string parentField, DeletionData deletionData) where T : Entity, new()
 | 
	
		
			
				|  |  | +        private void DeleteEntity<T>(Guid[] parentIDs, List<string> parentFields, DeletionData deletionData) where T : Entity, new()
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              var columns = DeletionData.DeletionColumns<T>();
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2956,7 +2901,15 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |              for (int i = 0; i < parentIDs.Length; i += deleteBatchSize)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  var items = new ArraySegment<Guid>(parentIDs, i, Math.Min(deleteBatchSize, parentIDs.Length - i));
 | 
	
		
			
				|  |  | -                var entities = Query(new Filter<T>(parentField).InList(items.ToArray()), columns);
 | 
	
		
			
				|  |  | +                var ids = items.ToArray();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                var filter = new Filters<T>();
 | 
	
		
			
				|  |  | +                foreach(var field in parentFields)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    filter.Add(new Filter<T>(field).InList(ids));
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                var entities = Query(filter.CombineOr(), columns);
 | 
	
		
			
				|  |  |                  foreach (var row in entities.Rows)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      deletionData.DeleteEntity<T>(row);
 | 
	
	
		
			
				|  | @@ -2966,9 +2919,9 @@ namespace InABox.Database.SQLite
 | 
	
		
			
				|  |  |              CascadeDelete(typeof(T), entityIDs.ToArray(), deletionData);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |         
 | 
	
		
			
				|  |  | -        private void DeleteEntity(Type T, Guid[] parentIDs, string parentField, DeletionData deletionData)
 | 
	
		
			
				|  |  | +        private void DeleteEntity(Type T, Guid[] parentIDs, List<string> parentFields, DeletionData deletionData)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            _deleteEntitiesMethod.MakeGenericMethod(T).Invoke(this, new object?[] { parentIDs, parentField, deletionData });
 | 
	
		
			
				|  |  | +            _deleteEntitiesMethod.MakeGenericMethod(T).Invoke(this, new object?[] { parentIDs, parentFields, deletionData });
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          private readonly MethodInfo _setNullEntityMethod = typeof(SQLiteProvider).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
 |