Browse Source

Merge remote-tracking branch 'origin/kenric' into frank

Frank van den Bos 2 years ago
parent
commit
acf1bfaab0

+ 18 - 0
InABox.Core/Column.cs

@@ -90,9 +90,11 @@ namespace InABox.Core
 
     public interface IColumns
     {
+        bool Any();
         IEnumerable<string> ColumnNames();
         Dictionary<String, Type> AsDictionary();
         IColumns Add(string column);
+        IColumns Add(IColumn column);
         IColumns Add<T>(Expression<Func<T, object?>> column);
         IColumns DefaultColumns(params ColumnType[] types);
     }
@@ -160,6 +162,8 @@ namespace InABox.Core
             }
         }
 
+        public bool Any() => columns.Any();
+
         public IColumns Add(string column)
         {
             if(CoreUtils.TryGetProperty(typeof(T), column, out var propertyInfo))
@@ -189,6 +193,20 @@ namespace InABox.Core
         {
             return Add(CoreUtils.GetFullPropertyName(expression, "."));
         }
+        public Columns<T> Add(Column<T> column)
+        {
+            if(!columns.Any(x => x.Property.Equals(column.Property)))
+            {
+                columns.Add(column);
+            }
+            return this;
+        }
+        public IColumns Add(IColumn column)
+        {
+            if (column is Column<T> col)
+                return Add(col);
+            return this;
+        }
 
         public IEnumerable<string> ColumnNames()
         {

+ 10 - 11
InABox.Core/CoreUtils.cs

@@ -1404,29 +1404,28 @@ namespace InABox.Core
             return filename;
         }
 
-        public static Columns<T> GetColumns<T>(Columns<T>? columns) where T : Entity
+        public static Columns<T> GetColumns<T>(Columns<T>? columns)
+            => (GetColumns(typeof(T), columns) as Columns<T>)!;
+        
+        public static IColumns GetColumns(Type T, IColumns? columns)
         {
             //var cols = columns;
-            if (columns == null || !columns.Items.Any())
+            if (columns == null || !columns.Any())
             {
-                var result = new Columns<T>();
-                if (_columnscache.ContainsKey(typeof(T)))
-                {
-                    result = (_columnscache[typeof(T)] as Columns<T>)!;
-                }
-                else
+                if (!_columnscache.TryGetValue(T, out var result))
                 {
-                    var props = PropertyList(typeof(T),
+                    result = Columns.Create(T);
+                    var props = PropertyList(T,
                         x =>
                             x.GetCustomAttribute<DoNotSerialize>() == null &&
                             x.GetCustomAttribute<DoNotPersist>() == null &&
                             x.CanWrite && x.PropertyType != typeof(UserProperties), true);
                     foreach (var prop in props)
                         result.Add(prop.Key);
-                    var custom = DatabaseSchema.Properties(typeof(T)).Where(x => x is CustomProperty);
+                    var custom = DatabaseSchema.Properties(T).Where(x => x is CustomProperty);
                     foreach (var prop in custom)
                         result.Add(prop.Name);
-                    _columnscache[typeof(T)] = result;
+                    _columnscache[T] = result;
                 }
 
                 return result;

+ 8 - 0
InABox.Core/SortOrder.cs

@@ -16,6 +16,12 @@ namespace InABox.Core
 
     public interface ISortOrder
     {
+        SortDirection Direction { get; set; }
+
+        Expression Expression { get; set; }
+
+        IEnumerable<ISortOrder> Thens { get; }
+
         IEnumerable<String> ColumnNames();
     }
 
@@ -38,6 +44,8 @@ namespace InABox.Core
 
         public List<SortOrder<T>> Thens { get; private set; }
 
+        IEnumerable<ISortOrder> ISortOrder.Thens => Thens;
+
         //public SortOrder<T> Ascending()
         //{
         //	Direction = SortOrder.Ascending;

+ 2 - 1
InABox.DynamicGrid/DynamicGrid.cs

@@ -1854,7 +1854,8 @@ namespace InABox.DynamicGrid
             // for no reason. I think perhaps the image columns were trying to refer to data that didn't exist anymore when calling DataGrid.Columns.Refresh(),
             // and thus some mega problems (perhaps even exceptions within Syncfusion) were occurring, and this seems to fix it.
             // I don't pretend to know why it works; this is probably the strangest problem I've ever come across.
-            DataGrid.ItemsSource = null;
+            if(reloadcolumns)
+                DataGrid.ItemsSource = null;
 
             //using (var d = Dispatcher.DisableProcessing())
             {

+ 185 - 223
inabox.database.sqlite/SQLiteProvider.cs

@@ -1,12 +1,12 @@
 using System.Collections;
 using System.Data;
 using System.Data.SQLite;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq.Expressions;
 using System.Reflection;
 using System.Runtime.Serialization.Formatters.Binary;
 using System.Text;
 using InABox.Core;
-using NPOI.SS.UserModel;
 
 
 namespace InABox.Database.SQLite
@@ -98,7 +98,7 @@ namespace InABox.Database.SQLite
 
         public string URL { get; set; }
 
-        public event LogEvent OnLog;
+        public event LogEvent? OnLog;
 
         public Type[] Types { get; set; }
 
@@ -309,9 +309,9 @@ namespace InABox.Database.SQLite
                     result = command.ExecuteNonQuery();
                 }
             }
-            catch (Exception e)
+            catch (Exception)
             {
-                throw e;
+                throw;
             }
 
             return result;
@@ -566,7 +566,7 @@ namespace InABox.Database.SQLite
                 }
                 else if (property.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)))
                 {
-                    var subprop = property.PropertyType.GetProperty("ID");
+                    var subprop = property.PropertyType.GetProperty("ID")!; // Not-null because IEntityLink has ID
                     var subname = ColumnName(prefixes.Concat(new[] { property, subprop }).ToArray());
                     var subtype = ColumnType(subprop.PropertyType);
                     fields[subname] = subtype;
@@ -594,7 +594,7 @@ namespace InABox.Database.SQLite
             foreach (var property in properties)
                 if (property.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)))
                 {
-                    var subprop = property.PropertyType.GetProperty("ID");
+                    var subprop = property.PropertyType.GetProperty("ID")!; // Not-null because IEntityLink has ID
                     var subname = ColumnName(property, subprop);
                     var tablename = type.EntityName().Split('.').Last();
                     result.Add(string.Format("CREATE INDEX idx{0}{1} ON {0} ([{2}])", tablename, subname.Replace(".", ""), subname));
@@ -711,7 +711,7 @@ namespace InABox.Database.SQLite
             }
         }
 
-        private Dictionary<String, object?> CheckDefaultColumns(IAutoEntityGenerator generator)
+        private Dictionary<string, object?> CheckDefaultColumns(IAutoEntityGenerator generator)
         {
             var viewfields = new Dictionary<string, string>();
             LoadFields(generator.Definition, viewfields, null, new CustomProperty[] { });
@@ -734,7 +734,7 @@ namespace InABox.Database.SQLite
         private void CreateTable(SQLiteWriteAccessor access, Type type, bool includeconstraints, CustomProperty[] customproperties)
         {
             var ddl = new List<string>();
-            AutoEntity view = type.GetCustomAttribute<AutoEntity>();
+            var view = type.GetCustomAttribute<AutoEntity>();
             if (view != null)
             {
                 using (var command = access.CreateCommand())
@@ -760,9 +760,9 @@ namespace InABox.Database.SQLite
                     List<String> queries = new List<String>();
                     foreach (var table in union.Tables)
                     {
-                        
-                        var columns = Activator.CreateInstance(typeof(Columns<>).MakeGenericType(table.Entity)) as IColumns;
-                        Dictionary<String, object?> constants = CheckDefaultColumns(union);
+
+                        var columns = Columns.Create(table.Entity);
+                        var constants = CheckDefaultColumns(union);
                         
                         var interfacefields = new Dictionary<string, string>();
                         LoadFields(union.Definition, interfacefields, null, new CustomProperty[] { });
@@ -783,22 +783,11 @@ namespace InABox.Database.SQLite
                                     constants[field] = null;
                             }
                         }
-                        
-                        
-                        var selectFnc = typeof(SQLiteProvider).GetMethod("PrepareSelect").MakeGenericMethod(table.Entity);
-                        var query = selectFnc.Invoke(this, new object[]
-                        {
-                            new SQLiteCommand(),
-                            'A',
-                            table.Filter,
-                            columns,
-                            null,
-                            null,
-                            constants,
-                            int.MaxValue,
-                            union.Distinct,
-                            false
-                        }) as string;
+
+
+                        var query = PrepareSelectNonGeneric(table.Entity, new SQLiteCommand(), 'A',
+                            table.Filter, columns, null,
+                            null, constants, int.MaxValue, union.Distinct, false);
                         
                         queries.Add(query);
                     }
@@ -858,21 +847,9 @@ namespace InABox.Database.SQLite
                     int iTable = 0;
                     foreach (var table in cartesian.Tables)
                     {
-                        
-                        var selectFnc = typeof(SQLiteProvider).GetMethod("PrepareSelect").MakeGenericMethod(table.Type);
-                        var subQueryText = selectFnc.Invoke(this, new object[]
-                        {
-                            new SQLiteCommand(),
-                            'A',
-                            table.Filter,
-                            table.Columns,
-                            null,
-                            null,
-                            null,
-                            int.MaxValue,
-                            cartesian.Distinct,
-                            false
-                        }) as string;
+                        var subQueryText = PrepareSelectNonGeneric(table.Type, new SQLiteCommand(), 'A',
+                            table.Filter, table.Columns, null,
+                            null, null, int.MaxValue, cartesian.Distinct, false);
                         
                         tables.Add($"({subQueryText}) T{iTable}");
                         
@@ -928,7 +905,7 @@ namespace InABox.Database.SQLite
             catch (Exception e)
             {
                 OnLog?.Invoke(LogType.Error, "Unable to Create Table: " + e.Message);
-                throw e;
+                throw;
             }
         }
 
@@ -965,7 +942,7 @@ namespace InABox.Database.SQLite
                 catch (Exception e)
                 {
                     OnLog?.Invoke(LogType.Error, string.Format("RebuildTable({0}) [VIEW] failed: {1}\n{2}", table, e.Message, e.StackTrace));
-                    throw e;
+                    throw;
                 }
             }
             else
@@ -1036,7 +1013,7 @@ namespace InABox.Database.SQLite
                 catch (Exception e)
                 {
                     OnLog?.Invoke(LogType.Error, string.Format("RebuildTable({0}) [TABLE] failed: {1}\n{2}", table, e.Message, e.StackTrace));
-                    throw e;
+                    throw;
                 }
             }
             
@@ -1057,7 +1034,7 @@ namespace InABox.Database.SQLite
             {
                 try
                 {
-                    Type tType = null;
+                    Type tType;
                     var cprop = customproperties.FirstOrDefault(x => string.Equals(x.Name, field));
                     if (cprop != null)
                         tType = cprop.PropertyType;
@@ -1130,17 +1107,17 @@ namespace InABox.Database.SQLite
         //         if (!db_views.ContainsValue(type_view))
         //             ExecuteSQL(access, type_view);            
         // }
-        
+
         #endregion
 
         #region CRUD Operations
 
-        private bool IsNull(object o)
+        private static bool IsNull([NotNullWhen(false)] object? o)
         {
             return o == null || o is DBNull;
         }
 
-        public object Decode(object o, Type type)
+        public object? Decode(object o, Type type)
         {
             if (IsNull(o))
                 return type == typeof(string) ? "" : type.GetDefault();
@@ -1149,54 +1126,56 @@ namespace InABox.Database.SQLite
             {
                 if (!IsNull(o))
                 {
-                    var array = o as byte[];
-                    if (o is byte[])
-                        using (var ms = new MemoryStream((byte[])o))
+                    if (o is byte[] array)
+                        using (var ms = new MemoryStream(array))
                         {
                             var deser = new BinaryFormatter().Deserialize(ms);
                             return deser as string[];
                         }
 
-                    return new string[] { };
+                    return Array.Empty<string>();
                 }
 
-                return new string[] { };
+                return Array.Empty<string>();
             }
 
             if (type.GetInterfaces().Contains(typeof(IPackable)))
             {
-                var packable = Activator.CreateInstance(type) as IPackable;
+                var packable = (Activator.CreateInstance(type) as IPackable)!; // Not-null because of above check
                 if (!IsNull(o))
                 {
-                    var array = o as byte[];
-                    if (o is byte[])
-                        packable.Unpack(new FastBinaryReader((byte[])o));
+                    if (o is byte[] array)
+                        packable.Unpack(new FastBinaryReader(array));
                 }
 
                 return packable;
             }
 
             if (type == typeof(DateTime))
-                return IsNull(o) || string.IsNullOrEmpty(o.ToString()) ? DateTime.MinValue : DateTime.Parse(o.ToString());
+                return IsNull(o) || string.IsNullOrEmpty(o.ToString()) ? DateTime.MinValue : DateTime.Parse(o.ToString() ?? "");
 
             if (type == typeof(TimeSpan))
             {
                 if (IsNull(o))
                     return TimeSpan.MinValue;
-                if (double.TryParse(o.ToString(), out var hrs))
+                var oStr = o.ToString() ?? "";
+                if (double.TryParse(oStr, out var hrs))
                     return TimeSpan.FromHours(hrs);
-                if (o.ToString().Contains(":"))
-                    return TimeSpan.Parse(o.ToString());
+                if (oStr.Contains(':'))
+                    return TimeSpan.Parse(oStr);
             }
 
             if (type == typeof(Guid))
-                return IsNull(o) ? Guid.Empty : Guid.Parse(o.ToString());
+                return IsNull(o) ? Guid.Empty : Guid.Parse(o.ToString() ?? "");
 
             if (type == typeof(bool))
-                return IsNull(o) ? false : o.ToString().Equals("1") || o.ToString().ToUpper().Equals("TRUE");
+            {
+                var oStr = o.ToString() ?? "";
+                return !IsNull(o) && (oStr.Equals("1") || oStr.ToUpper().Equals("TRUE"));
+            }
 
             if (type.IsEnum)
-                return Enum.Parse(type, o.ToString());
+                return Enum.Parse(type, o.ToString() ?? "");
 
             if (IsNull(o))
                 return null;
@@ -1208,29 +1187,29 @@ namespace InABox.Database.SQLite
             return result;
         }
 
-        public object Encode(object o, Type type)
+        public static object Encode(object? o, Type? type)
         {
-            if (IsNull(o))
+            if (IsNull(o) || type is null)
                 return DBNull.Value;
 
             if (type == typeof(DateTime) && o.GetType() == typeof(string))
-                o = DateTime.Parse(o.ToString());
+                o = DateTime.Parse(o.ToString() ?? "");
 
             if (type == typeof(double) && o.GetType() == typeof(string))
-                o = double.Parse(o.ToString());
+                o = double.Parse(o.ToString() ?? "");
 
             if (type == typeof(float) && o.GetType() == typeof(string))
-                o = float.Parse(o.ToString());
+                o = float.Parse(o.ToString() ?? "");
 
             if (type == typeof(long) && o.GetType() == typeof(string))
-                o = long.Parse(o.ToString());
+                o = long.Parse(o.ToString() ?? "");
 
             if (type.IsEnum && o.GetType() == typeof(string))
-                o = Enum.Parse(type, o.ToString());
+                o = Enum.Parse(type, o.ToString() ?? "");
 
             if (type == typeof(TimeSpan) && o.GetType() == typeof(string))
             {
-                if (o.ToString().Contains(":"))
+                if (o.ToString()?.Contains(':') == true)
                 {
                     if (TimeSpan.TryParse(o.ToString(), out var time))
                         o = time;
@@ -1244,7 +1223,7 @@ namespace InABox.Database.SQLite
             if (type == typeof(bool))
             {
                 if (o.GetType() == typeof(string))
-                    o = bool.Parse(o.ToString());
+                    o = bool.Parse(o.ToString() ?? "");
                 if (o.Equals(false))
                     return DBNull.Value;
                 return o;
@@ -1255,7 +1234,7 @@ namespace InABox.Database.SQLite
 
 
             if (type == typeof(Guid) && o.GetType() == typeof(string))
-                o = Guid.Parse(o.ToString());
+                o = Guid.Parse(o.ToString() ?? "");
 
             if (o is string[])
                 using (var ms = new MemoryStream())
@@ -1301,7 +1280,7 @@ namespace InABox.Database.SQLite
             {
                 if (o.Equals(Guid.Empty))
                     return DBNull.Value;
-                return o.ToString();
+                return o.ToString() ?? "";
             }
 
             if (type == typeof(double) && o.GetType() == typeof(string))
@@ -1309,7 +1288,7 @@ namespace InABox.Database.SQLite
                     o = value;
 
             if (o.GetType().IsEnum)
-                return o.ToString();
+                return o.ToString() ?? "";
 
             return o;
         }
@@ -1330,33 +1309,31 @@ namespace InABox.Database.SQLite
             { Operator.InQuery, "{0} IN ({1})" }
         };
 
-        private String EscapeValue(object value)
+        private static string EscapeValue(object? value)
         {
             if (IsNull(value))
                 return "NULL";
-            if ((value is String) || (value is Enum) || (value is Guid))
-                return String.Format("\'" + "{0}" + "\'", value.ToString().Replace("\'", "\'\'"));
-            if (value is String[])
-                return string.Format("hex({0})", BitConverter.ToString(Encoding.ASCII.GetBytes(value.ToString())).Replace("-", string.Empty));
-            return value.ToString();
+            if ((value is string) || (value is Enum) || (value is Guid))
+                return string.Format("\'" + "{0}" + "\'", value.ToString()?.Replace("\'", "\'\'"));
+            if (value is string[])
+                return string.Format("hex({0})", BitConverter.ToString(Encoding.ASCII.GetBytes(value.ToString() ?? "")).Replace("-", string.Empty));
+            return value.ToString() ?? "";
 
         }
 
 
-        private String GetFilterConstant(FilterConstant constant)
+        private static string GetFilterConstant(FilterConstant constant)
         {
-            switch (constant)
+            return constant switch
             {
-                case FilterConstant.Now :
-                    return "datetime()";
-                case FilterConstant.Today :
-                    return "strftime('%Y-%m-%d 00:00:00')";
-            }
-            throw new Exception($"FilterConstant.{constant} is not implemented!");
+                FilterConstant.Now => "datetime()",
+                FilterConstant.Today => "strftime('%Y-%m-%d 00:00:00')",
+                _ => throw new Exception($"FilterConstant.{constant} is not implemented!"),
+            };
         }
-        
-        private string GetFilterClause<T>(SQLiteCommand command, char prefix, Filter<T> filter, List<Tuple<string, string, string, string>> tables,
-            Dictionary<string, string> fieldmap, List<string> columns, bool useparams) where T : Entity
+
+        private string GetFilterClauseNonGeneric(Type T, SQLiteCommand command, char prefix, IFilter? filter, List<Tuple<string, string, string, string>> tables,
+            Dictionary<string, string> fieldmap, List<string> columns, bool useparams)
         {
             if (filter == null || filter.Expression == null)
                 return "";
@@ -1366,15 +1343,14 @@ namespace InABox.Database.SQLite
             {
                 result = "1 = 1";
             }
-            else if(filter.Operator == Operator.None)
+            else if (filter.Operator == Operator.None)
             {
                 result = "1 = 0";
             }
             else
             {
-                var prop = "";
-                MemberExpression mexp = null;
-                if (CoreUtils.TryFindMemberExpression(filter.Expression, out mexp))
+                string prop;
+                if (CoreUtils.TryFindMemberExpression(filter.Expression, out var mexp))
                 {
                     prop = CoreUtils.GetFullPropertyName(mexp, ".");
                 }
@@ -1383,10 +1359,10 @@ namespace InABox.Database.SQLite
                     prop = filter.Expression.ToString();
                     if (prop.Contains("=>"))
                         prop = string.Join(".", prop.Split('.').Skip(1));
-                    mexp = CoreUtils.GetMemberExpression(typeof(T), prop);
+                    mexp = CoreUtils.GetMemberExpression(T, prop);
                 }
 
-                LoadFieldsandTables(command, typeof(T), prefix, fieldmap, tables, columns, prop, useparams);
+                LoadFieldsandTables(command, T, prefix, fieldmap, tables, columns, prop, useparams);
                 if (fieldmap.ContainsKey(prop))
                     prop = fieldmap[prop];
 
@@ -1394,16 +1370,14 @@ namespace InABox.Database.SQLite
                 {
                     // if, and only if the list contains Guids, we can safely bypass the 
                     // 1000-parameter limit by using building the string ourselves
-                    if (filter.Value is Guid[])
+                    if (filter.Value is Guid[] list)
                     {
-                        var list = filter.Value as Guid[];
                         result = string.Format("(" + operators[filter.Operator] + ")", prop, string.Format("\"{0}\"", string.Join("\",\"", list)));
                     }
-                    else if (filter.Value is IEnumerable)
+                    else if (filter.Value is IEnumerable enumerable)
                     {
-                        var list = filter.Value as IEnumerable;
                         var paramlist = new List<object>();
-                        foreach (var item in list)
+                        foreach (var item in enumerable)
                         {
                             var sParam = string.Format("@p{0}", command.Parameters.Count);
                             command.Parameters.AddWithValue(sParam, Encode(item, mexp.Type));
@@ -1415,40 +1389,26 @@ namespace InABox.Database.SQLite
                 }
                 else if (filter.Operator == Operator.InQuery)
                 {
-                    var subQuery = filter.Value;
-
-                    var subEntityType = subQuery.GetType().GenericTypeArguments[0];
-                    var selectFnc = typeof(SQLiteProvider).GetMethod("PrepareSelect").MakeGenericMethod(subEntityType);
+                    if(filter.Value is ISubQuery subQuery)
+                    {
+                        var subEntityType = subQuery.GetQueryType();
 
-                    var columnsType = typeof(Columns<>).MakeGenericType(subEntityType);
+                        var subColumns = Columns.Create(subEntityType);
+                        var subColumn = subQuery.GetColumn();
 
-                    var subColumns = Activator.CreateInstance(columnsType);
-                    var subColumn = subQuery.GetType().GetProperty("Column").GetValue(subQuery);
-                    var arr = Array.CreateInstance(subColumn.GetType(), 1);
-                    arr.SetValue(subColumn, 0);
+                        subColumns.Add(subColumn);
 
-                    columnsType.GetProperty("Items").SetValue(subColumns, arr);
+                        var subQueryText = PrepareSelectNonGeneric(subEntityType, command, 'A',
+                            subQuery.GetFilter(), subColumns, null,
+                            null, null, int.MaxValue, false, useparams);
 
-                    var subQueryText = selectFnc.Invoke(this, new[]
-                    {
-                        command,
-                        'A',
-                        subQuery.GetType().GetProperty("Filter").GetValue(subQuery),
-                        subColumns,
-                        null,
-                        null,
-                        null,
-                        int.MaxValue,
-                        false,
-                        useparams
-                    }) as string;
-
-                    result = string.Format("(" + operators[filter.Operator] + ")", prop, subQueryText);
+                        result = string.Format("(" + operators[filter.Operator] + ")", prop, subQueryText);
+                    }
                 }
                 else
                 {
-                    if (filter.Value is FilterConstant)
-                        result = string.Format("(" + operators[filter.Operator] + ")", prop, GetFilterConstant((FilterConstant)filter.Value));
+                    if (filter.Value is FilterConstant constant)
+                        result = string.Format("(" + operators[filter.Operator] + ")", prop, GetFilterConstant(constant));
                     else
                     {
                         var value = Encode(filter.Value, mexp.Type);
@@ -1468,7 +1428,7 @@ namespace InABox.Database.SQLite
 
                                 if (filter.Expression.Type == typeof(string[]))
                                 {
-                                    var bytes = Encoding.ASCII.GetBytes(value.ToString());
+                                    var bytes = Encoding.ASCII.GetBytes(value.ToString() ?? "");
                                     value = BitConverter.ToString(bytes).Replace("-", string.Empty);
                                     result = string.Format("(" + operators[filter.Operator] + ")", string.Format("hex({0})", prop), sParam);
                                 }
@@ -1488,11 +1448,11 @@ namespace InABox.Database.SQLite
             }
 
             var bChanged = false;
-            if (filter.Ands != null && filter.Ands.Count > 0)
+            if (filter.Ands != null && filter.Ands.Count() > 0)
             {
                 foreach (var and in filter.Ands)
                 {
-                    var AndResult = GetFilterClause(command, prefix, and, tables, fieldmap, columns, useparams);
+                    var AndResult = GetFilterClauseNonGeneric(T, command, prefix, and, tables, fieldmap, columns, useparams);
                     if (!string.IsNullOrEmpty(AndResult))
                     {
                         result = string.Format("{0} and {1}", result, AndResult);
@@ -1505,11 +1465,11 @@ namespace InABox.Database.SQLite
             }
 
             bChanged = false;
-            if (filter.Ors != null && filter.Ors.Count > 0)
+            if (filter.Ors != null && filter.Ors.Count() > 0)
             {
                 foreach (var or in filter.Ors)
                 {
-                    var OrResult = GetFilterClause(command, prefix, or, tables, fieldmap, columns, useparams);
+                    var OrResult = GetFilterClauseNonGeneric(T, command, prefix, or, tables, fieldmap, columns, useparams);
                     if (!string.IsNullOrEmpty(OrResult))
                     {
                         result = string.Format("{0} or {1}", result, OrResult);
@@ -1524,8 +1484,13 @@ namespace InABox.Database.SQLite
             return result;
         }
 
-        private string GetSortClause<T>(SQLiteCommand command, SortOrder<T> sort, char prefix, List<Tuple<string, string, string, string>> tables,
+        private string GetFilterClause<T>(SQLiteCommand command, char prefix, Filter<T>? filter, List<Tuple<string, string, string, string>> tables,
             Dictionary<string, string> fieldmap, List<string> columns, bool useparams) where T : Entity
+            => GetFilterClauseNonGeneric(typeof(T), command, prefix, filter, tables, fieldmap, columns, useparams);
+
+        private string GetSortClauseNonGeneric(Type T, 
+            SQLiteCommand command, ISortOrder? sort, char prefix, List<Tuple<string, string, string, string>> tables,
+            Dictionary<string, string> fieldmap, List<string> columns, bool useparams)
         {
             if (sort == null)
                 return "";
@@ -1533,16 +1498,15 @@ namespace InABox.Database.SQLite
             var result = "";
             if (sort.Expression != null)
             {
-                MemberExpression mexp = null;
-                if (CoreUtils.TryFindMemberExpression(sort.Expression, out mexp))
+                if (CoreUtils.TryFindMemberExpression(sort.Expression, out var mexp))
                     result = CoreUtils.GetFullPropertyName(mexp, ".");
                 else
                     result = sort.Expression.ToString();
 
-                var prop = CoreUtils.GetProperty(typeof(T), result);
+                var prop = CoreUtils.GetProperty(T, result);
                 if (prop.GetCustomAttribute<DoNotSerialize>() == null && prop.GetCustomAttribute<DoNotPersist>() == null && prop.CanWrite)
                 {
-                    LoadFieldsandTables(command, typeof(T), prefix, fieldmap, tables, columns, result, useparams);
+                    LoadFieldsandTables(command, T, prefix, fieldmap, tables, columns, result, useparams);
                     if (fieldmap.ContainsKey(result))
                         result = fieldmap[result];
 
@@ -1561,28 +1525,26 @@ namespace InABox.Database.SQLite
             }
 
             foreach (var then in sort.Thens)
-                result = result + ", " + GetSortClause(command, then, prefix, tables, fieldmap, columns, useparams);
+                result = result + ", " + GetSortClauseNonGeneric(T, command, then, prefix, tables, fieldmap, columns, useparams);
 
             return result;
         }
 
-        private string GetCalculation(AggregateAttribute attribute, string columnname)
+        private string GetSortClause<T>(SQLiteCommand command, SortOrder<T>? sort, char prefix, List<Tuple<string, string, string, string>> tables,
+            Dictionary<string, string> fieldmap, List<string> columns, bool useparams)
+            => GetSortClauseNonGeneric(typeof(T), command, sort, prefix, tables, fieldmap, columns, useparams);
+
+        private static string GetCalculation(AggregateAttribute attribute, string columnname)
         {
-            switch (attribute.Calculation)
+            return attribute.Calculation switch
             {
-                case AggregateCalculation.Sum:
-                    return "TOTAL";
-                case AggregateCalculation.Count:
-                    return "COUNT";
-                case AggregateCalculation.Maximum:
-                    return "MAX";
-                case AggregateCalculation.Minimum:
-                    return "MIN";
-                case AggregateCalculation.Average:
-                    return "AVERAGE";
-            }
-
-            throw new Exception(string.Format("{0}.{1} is not a valid aggregate", columnname, attribute.Calculator.GetType().Name));
+                AggregateCalculation.Sum => "TOTAL",
+                AggregateCalculation.Count => "COUNT",
+                AggregateCalculation.Maximum => "MAX",
+                AggregateCalculation.Minimum => "MIN",
+                AggregateCalculation.Average => "AVERAGE",
+                _ => throw new Exception(string.Format("{0}.{1} is not a valid aggregate", columnname, attribute.Calculator.GetType().Name)),
+            };
         }
 
         private string GetFunction(FormulaAttribute attribute, Dictionary<string, string> fieldmap, string columnname)
@@ -1639,6 +1601,9 @@ namespace InABox.Database.SQLite
         {
             var intf = attribute.Calculator.GetType().GetInterfaces()
                 .FirstOrDefault(x => x.Name.StartsWith("ICondition") && x.GenericTypeArguments.Length.Equals(3));
+            if (intf is null)
+                throw new Exception($"Attribute calculate {attribute.Calculator} is not an ICondition");
+
             var valuetype = intf.GenericTypeArguments[1];
             var defvalue = valuetype.GetDefault();
 
@@ -1739,11 +1704,10 @@ namespace InABox.Database.SQLite
                         var attr = prop.GetCustomAttributes().FirstOrDefault(x => x.GetType().Equals(typeof(AggregateAttribute)));
                         // LogStop("GetCustomAttributes(Aggregate)");
 
-                        if (attr != null)
+                        if (attr is AggregateAttribute agg)
                         {
-                            var agg = attr as AggregateAttribute;
-                            
-                            bool internalaggregate = agg.Calculator.GetType().GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition()== typeof(ICoreAggregate<,>));
+                            bool internalaggregate = agg.Calculator.GetType().GetInterfaces()
+                                .Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICoreAggregate<,>));
 
                             if (internalaggregate)
                             {
@@ -1766,23 +1730,15 @@ namespace InABox.Database.SQLite
                                     // LogStart();
                                     var sattr = prop.GetCustomAttributes().FirstOrDefault(x => x.GetType().Equals(typeof(AggregateAttribute)));
                                     // LogStop("GetCustomAttributes(Sibling.Aggregate)");
-                                    if (sattr != null)
+                                    if (sattr is AggregateAttribute sagg)
                                     {
                                         // LogStart();
-                                        var sagg = sattr as AggregateAttribute;
                                         scols[sagg.Aggregate] = GetCalculation(sagg, sibling);
                                         // LogStop("GetCalculation(Sibling)");
                                     }
                                 }
 
-                                // LogStart();
-                                var subcoltype = typeof(Columns<>).MakeGenericType(linkedtype);
-                                // LogStop("Columns<>.MakeGnericType");
-
-                                // LogStart();
-                                var subcols = Activator.CreateInstance(subcoltype) as IColumns;
-                                // LogStop("Columns<>.CreateInstance");
-
+                                var subcols = Columns.Create(linkedtype);
 
                                 foreach (var key in agg.Links.Keys)
                                     subcols.Add(key);
@@ -1792,7 +1748,6 @@ namespace InABox.Database.SQLite
                                     subcols.Add(scol);
 
                                 // LogStart();
-                                var preparemethod = GetType().GetMethod("PrepareSelect").MakeGenericMethod(linkedtype);
 
                                 var filter = Activator.CreateInstance(typeof(Filter<>).MakeGenericType(linkedtype), "Deleted") as IFilter;
                                 filter!.Operator = Operator.IsEqualTo;
@@ -1801,16 +1756,15 @@ namespace InABox.Database.SQLite
                                 var aggFilter = agg.Filter;
                                 if(aggFilter is not null)
                                 {
-                                    var filterfields = this.GetType().GetMethod("FilterFields").MakeGenericMethod(linkedtype);
                                     List<String> ffs = new List<string>();
-                                    filterfields.Invoke(this, new object[] { aggFilter, ffs });
+                                    FilterFields(aggFilter, ffs);
                                     //foreach (var ff in ffs)
                                     //    subcols.Add(ff);
                                     filter.And(aggFilter);
                                 }
-                                
+
                                 var linkedtable = string.Format("({0})",
-                                    preparemethod.Invoke(this, new object[] { command, newprefix, filter, subcols, null, scols, null, int.MaxValue, false, useparams }));
+                                    PrepareSelectNonGeneric(linkedtype, command, newprefix, filter, subcols, null, scols, null, int.MaxValue, false, useparams));
                                 
                                 var alias = tables.Count + 1;
 
@@ -1853,9 +1807,8 @@ namespace InABox.Database.SQLite
                             attr = prop.GetCustomAttributes().FirstOrDefault(x => x.GetType().Equals(typeof(FormulaAttribute)));
                             // LogStop("GetAttribute(Formula)");
 
-                            if (attr != null)
+                            if (attr is FormulaAttribute fnc)
                             {
-                                var fnc = attr as FormulaAttribute;
                                 var functionmap = new Dictionary<string, string>();
 
                                 // LogStart();
@@ -1885,10 +1838,8 @@ namespace InABox.Database.SQLite
                                 attr = prop.GetCustomAttributes().FirstOrDefault(x => x.GetType().Equals(typeof(ConditionAttribute)));
                                 // LogStop("GetAttribute(Condition)");
 
-                                if (attr != null)
+                                if (attr is ConditionAttribute cnd)
                                 {
-                                    var cnd = attr as ConditionAttribute;
-
                                     var cndmap = new Dictionary<string, string>();
                                     // LogStart();
                                     CheckColumn(columns, cnd.Left);
@@ -1983,27 +1934,19 @@ namespace InABox.Database.SQLite
                                 if (!siblings.Contains("Deleted"))
                                     siblings.Insert(1, "Deleted");
 
-                                // LogStart();
-                                var subcoltype = typeof(Columns<>).MakeGenericType(linkedtype);
-                                // LogStop("Columns<>.MakeGenericType");
-
-                                // LogStart();
-                                var subcols = Activator.CreateInstance(subcoltype) as IColumns;
-                                // LogStop("Columns<>.CreateInstance");
+                                var subcols = Columns.Create(linkedtype);
 
                                 foreach (var sibling in siblings)
                                     subcols.Add(sibling);
 
 
-                                var preparemethod = GetType().GetMethod("PrepareSelect")!.MakeGenericMethod(linkedtype);
-
                                 var filter = Activator.CreateInstance(typeof(Filter<>).MakeGenericType(linkedtype), "Deleted") as IFilter;
                                 filter!.Operator = Operator.IsEqualTo;
                                 filter.Value = Guid.Empty;
 
 
                                 var linkedtable = string.Format("({0})",
-                                    preparemethod.Invoke(this, new object?[] { command, newprefix, filter, subcols, null, null, null, int.MaxValue, false, useparams }));
+                                    PrepareSelectNonGeneric(linkedtype, command, newprefix, filter, subcols, null, null, null, int.MaxValue, false, useparams));
 
                                 var link = string.Format("{0}.ID", prop.Name);
                                 var tuple = tables.FirstOrDefault(x =>
@@ -2061,7 +2004,7 @@ namespace InABox.Database.SQLite
                 columns.Add(column);
         }
 
-        public void FilterFields<T>(Filter<T>? filter, List<string> fields)
+        public void FilterFields(IFilter? filter, List<string> fields)
         {
             if (filter == null)
                 return;
@@ -2077,7 +2020,7 @@ namespace InABox.Database.SQLite
                 FilterFields(or, fields);
         }
 
-        public void SortFields<T>(SortOrder<T>? sort, List<string> fields)
+        public void SortFields(ISortOrder? sort, List<string> fields)
         {
             if (sort == null)
                 return;
@@ -2095,18 +2038,19 @@ namespace InABox.Database.SQLite
             Count
         }
 
-        public string PrepareSelect<T>(SQLiteCommand command, char prefix, Filter<T>? filter, Columns<T>? columns, SortOrder<T>? sort,
-            Dictionary<string, string>? aggregates, Dictionary<string, object>? constants, int top, bool distinct, bool useparams) where T : Entity
+        public string PrepareSelectNonGeneric(Type T, SQLiteCommand command, char prefix,
+            IFilter? filter, IColumns? columns, ISortOrder? sort,
+            Dictionary<string, string>? aggregates, Dictionary<string, object?>? constants, int top, bool distinct, bool useparams)
         {
 
             var fieldmap = new Dictionary<string, string>();
-            
-            var cols = CoreUtils.GetColumns(columns);
+
+            var cols = CoreUtils.GetColumns(T, columns);
 
             var fields = new List<string>();
-            
+
             fields.AddRange(cols.ColumnNames());
-            
+
             FilterFields(filter, fields);
             SortFields(sort, fields);
 
@@ -2114,38 +2058,41 @@ namespace InABox.Database.SQLite
 
             var condition = "";
             var sortorder = "";
-            
+
+            var entityName = T.EntityName().Split('.').Last();
             tables.Add(new Tuple<string, string, string, string>(
-                typeof(T).EntityName().Split('.').Last(),
-                string.Format("{0}1", prefix),
-                string.Format("{0} {1}1", typeof(T).EntityName().Split('.').Last(), prefix),
+                entityName,
+                $"{prefix}1",
+                $"{entityName} {prefix}1",
                 "")
             );
 
-            foreach (var column in cols.Items) 
-                LoadFieldsandTables(command, typeof(T), prefix, fieldmap, tables, fields, column.Property, useparams);
+            foreach (var column in cols.ColumnNames())
+                LoadFieldsandTables(command, T, prefix, fieldmap, tables, fields, column, useparams);
 
             var parameters = new Dictionary<string, object>();
-            
-            condition = GetFilterClause(command, prefix, filter, tables, fieldmap, fields, useparams);
-            sortorder = GetSortClause(command, sort, prefix, tables, fieldmap, fields, useparams);
 
-            SortedDictionary<String,String> combined = new SortedDictionary<String, String>();
-            
+            condition = GetFilterClauseNonGeneric(T, command, prefix, filter, tables, fieldmap, fields, useparams);
+            sortorder = GetSortClauseNonGeneric(T, command, sort, prefix, tables, fieldmap, fields, useparams);
+
+            var combined = new SortedDictionary<string, string>();
+
             fields.Clear();
-            foreach (var column in cols.Items)
-                if (fieldmap.ContainsKey(column.Property))
+            foreach (var column in cols.ColumnNames())
+                if (fieldmap.ContainsKey(column))
                 {
-                    if (aggregates != null && aggregates.ContainsKey(column.Property))
-                        combined[constants != null ? column.Property : String.Format("{0:D8}",combined.Keys.Count)] = string.Format("{0}({1}) as [{2}]", aggregates[column.Property], fieldmap[column.Property], column.Property);
+                    if (aggregates != null && aggregates.ContainsKey(column))
+                        combined[constants != null ? column : String.Format("{0:D8}", combined.Keys.Count)] = string.Format("{0}({1}) as [{2}]", aggregates[column], fieldmap[column], column);
                     else
-                        combined[constants != null ? column.Property : String.Format("{0:D8}",combined.Keys.Count)] = string.Format("{0} as [{1}]", fieldmap[column.Property], column.Property);
+                        combined[constants != null ? column : String.Format("{0:D8}", combined.Keys.Count)] = string.Format("{0} as [{1}]", fieldmap[column], column);
                 }
 
             if (constants != null)
             {
-                foreach (var column in constants.Keys)
-                    combined[constants != null ? column : String.Format("{0:D8}",combined.Keys.Count)] = string.Format("{0} as [{1}]", EscapeValue(constants[column]), column);
+                foreach(var (column, value) in constants)
+                {
+                    combined[column] = string.Format("{0} as [{1}]", EscapeValue(value), column);
+                }
             }
 
             var result = new List<string>();
@@ -2170,7 +2117,7 @@ namespace InABox.Database.SQLite
             if (aggregates != null)
             {
                 var str = string.Join(", ",
-                    fieldmap.Where(x => cols.Items.Any(c => c.Property.Equals(x.Key)) && !aggregates.ContainsKey(x.Key)).Select(f => f.Value));
+                    fieldmap.Where(x => cols.ColumnNames().Contains(x.Key) && !aggregates.ContainsKey(x.Key)).Select(f => f.Value));
 
                 if (!string.IsNullOrWhiteSpace(str))
                 {
@@ -2186,6 +2133,10 @@ namespace InABox.Database.SQLite
             return string.Join(" ", result);
         }
 
+        public string PrepareSelect<T>(SQLiteCommand command, char prefix, Filter<T>? filter, Columns<T>? columns, SortOrder<T>? sort,
+            Dictionary<string, string>? aggregates, Dictionary<string, object?>? constants, int top, bool distinct, bool useparams) where T : Entity
+            => PrepareSelectNonGeneric(typeof(T), command, prefix, filter, columns, sort, aggregates, constants, top, distinct, useparams);
+
         private void PrepareUpsert<T>(SQLiteCommand command, T item, bool addDelete = false) where T : Entity
         {
             command.CommandText = "";
@@ -2214,7 +2165,7 @@ namespace InABox.Database.SQLite
                     continue;
 
                 var sParam = string.Format("@p{0}", iParam++);
-                object value = null;
+                object? value = null;
                 try
                 {
                     value = Encode(insert[key], insert[key]?.GetType());
@@ -2449,7 +2400,7 @@ namespace InABox.Database.SQLite
                                                 ReadAndDecodeValue(result, reader, row, i);
                                             }
                                         }
-                                        catch (Exception e)
+                                        catch (Exception)
                                         {
                                             row.Values.Add(result.Columns[i].DataType.GetDefault());
                                         }
@@ -2537,7 +2488,7 @@ namespace InABox.Database.SQLite
                                     entity.SetObserving(false);
                                     for (var i = 0; i < reader.FieldCount; i++)
                                     {
-                                        object value = null;
+                                        object value;
                                         if (cols.Items[i].Expression.Type == typeof(long) && !reader.IsDBNull(i))
                                             value = reader.GetInt64(i);
                                         else
@@ -2943,6 +2894,12 @@ namespace InABox.Database.SQLite
             }
 
             var entityType = CoreUtils.Entities.FirstOrDefault(x => x.Name == deletion.HeadTable);
+            if(entityType is null)
+            {
+                Logger.Send(LogType.Error, "", $"Entity {deletion.HeadTable} does not exist; Purge cancelled");
+                return;
+            }
+
             var deletionTypes = GetDeletionTypes(entityType);
 
             var purgeMethod = typeof(SQLiteProvider).GetMethod(nameof(PurgeEntityType), BindingFlags.NonPublic | BindingFlags.Instance)!;
@@ -2963,6 +2920,11 @@ namespace InABox.Database.SQLite
             }
 
             var entityType = CoreUtils.Entities.FirstOrDefault(x => x.Name == deletion.HeadTable);
+            if (entityType is null)
+            {
+                Logger.Send(LogType.Error, "", $"Entity {deletion.HeadTable} does not exist; Recovery cancelled");
+                return;
+            }
             var deletionTypes = GetDeletionTypes(entityType);
 
             var recoveryMethod = typeof(SQLiteProvider).GetMethod(nameof(RecoverEntityType), BindingFlags.NonPublic | BindingFlags.Instance)!;
@@ -2981,6 +2943,6 @@ namespace InABox.Database.SQLite
         }
 
         #endregion
-        
+
     }
 }