Explorar o código

Added new non generic PrepareSelect function, to enforce static type checking instead of invoking generic methods;
Only clearing DataGrid itemssource if columns are being reloaded

Kenric Nugteren %!s(int64=2) %!d(string=hai) anos
pai
achega
7f8ddd19ae

+ 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

@@ -1853,7 +1853,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())
             {

+ 97 - 120
inabox.database.sqlite/SQLiteProvider.cs

@@ -7,7 +7,6 @@ using System.Reflection;
 using System.Runtime.Serialization.Formatters.Binary;
 using System.Text;
 using InABox.Core;
-using NPOI.SS.UserModel;
 
 
 namespace InABox.Database.SQLite
@@ -784,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);
                     }
@@ -859,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}");
                         
@@ -1211,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;
@@ -1247,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;
@@ -1258,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())
@@ -1304,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))
@@ -1312,7 +1288,7 @@ namespace InABox.Database.SQLite
                     o = value;
 
             if (o.GetType().IsEnum)
-                return o.ToString();
+                return o.ToString() ?? "";
 
             return o;
         }
@@ -1340,7 +1316,7 @@ namespace InABox.Database.SQLite
             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 string.Format("hex({0})", BitConverter.ToString(Encoding.ASCII.GetBytes(value.ToString() ?? "")).Replace("-", string.Empty));
             return value.ToString() ?? "";
 
         }
@@ -1356,8 +1332,8 @@ namespace InABox.Database.SQLite
             };
         }
 
-        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 "";
@@ -1367,7 +1343,7 @@ namespace InABox.Database.SQLite
             {
                 result = "1 = 1";
             }
-            else if(filter.Operator == Operator.None)
+            else if (filter.Operator == Operator.None)
             {
                 result = "1 = 0";
             }
@@ -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];
 
@@ -1413,35 +1389,21 @@ 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
                 {
@@ -1466,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);
                                 }
@@ -1486,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);
@@ -1503,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);
@@ -1522,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 "";
@@ -1536,10 +1503,10 @@ namespace InABox.Database.SQLite
                 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];
 
@@ -1558,11 +1525,15 @@ 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 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)
         {
             return attribute.Calculation switch
@@ -1630,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();
 
@@ -1756,10 +1730,9 @@ 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)");
                                     }
@@ -1775,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;
@@ -1784,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;
 
@@ -1969,15 +1940,13 @@ namespace InABox.Database.SQLite
                                     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 =>
@@ -2035,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;
@@ -2051,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;
@@ -2069,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);
 
@@ -2088,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);
+
+            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>();
@@ -2144,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))
                 {
@@ -2160,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 = "";