Procházet zdrojové kódy

Added Support for Cartesian Joins and SQLite date() and datetime() functions, as well as filters in Union Joins

Frank van den Bos před 2 roky
rodič
revize
c77a7bb0fa

+ 20 - 0
InABox.Core/AutoEntity/AutoEntity.cs

@@ -0,0 +1,20 @@
+using System;
+
+namespace InABox.Core
+{
+    public interface IAutoEntityGenerator
+    {
+        bool Distinct { get; }
+        Type Definition { get; }
+    }
+    
+    public class AutoEntity : Attribute
+    {
+        public IAutoEntityGenerator? Generator { get; private set; }
+        
+        public AutoEntity(Type generator) : base()
+        {
+            Generator = Activator.CreateInstance(generator) as IAutoEntityGenerator;
+        }
+    }
+}

+ 133 - 0
InABox.Core/AutoEntity/AutoEntityCartesianGenerator.cs

@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace InABox.Core
+{
+    
+    public interface IAutoEntityCartesianMapping
+    {
+        IColumn Column { get;}
+        IColumn Mapping { get; }        
+    }
+    
+    public struct AutoEntityCartesianMapping : IAutoEntityCartesianMapping
+    {
+        public IColumn Column { get; private set; }
+        public IColumn Mapping { get; private set; }
+
+        public AutoEntityCartesianMapping(IColumn column, IColumn mapping)
+        {
+            Column = column;
+            Mapping = mapping;
+        }
+        
+    }
+    
+    public interface IAutoEntityCartesianConstant
+    {
+        object Constant { get;}
+        IColumn Mapping { get; }        
+    }
+    
+    public struct AutoEntityCartesianConstant: IAutoEntityCartesianConstant
+    {
+        public object Constant { get; private set; }
+        public IColumn Mapping { get; private set; }
+
+        public AutoEntityCartesianConstant(object constant, IColumn mapping)
+        {
+            Constant = constant;
+            Mapping = mapping;
+        }
+        
+    }
+
+    public interface IAutoEntityCartesianTable
+    {
+        Type Type { get; }
+
+        IFilter? Filter { get; }
+        
+        IColumns? Columns { get; }
+
+        IAutoEntityCartesianMapping[] Mappings { get; }
+    }
+
+    public class AutoEntityCartesianTable<T, TInterface> : IAutoEntityCartesianTable
+    {
+        public Type Type => typeof(T);
+        
+        public IFilter? Filter { get; private set; }
+        
+        private List<IAutoEntityCartesianMapping> _mappings = new List<IAutoEntityCartesianMapping>();
+        public IAutoEntityCartesianMapping[] Mappings => _mappings.ToArray();
+
+        public AutoEntityCartesianTable(Filter<T> filter)
+        {
+            Filter = filter;
+        }
+
+        public IColumns Columns
+        {
+            get
+            {
+                var columns = new Columns<T>();
+                foreach (var mapping in _mappings)
+                {
+                    columns.Add((mapping.Column as IColumn).Property);
+                }
+                return columns;
+            }
+        }
+
+        public AutoEntityCartesianTable<T, TInterface> AddMapping(Expression<Func<T, object?>> source, Expression<Func<TInterface, object?>> mapping)
+        {
+            _mappings.Add(new AutoEntityCartesianMapping(new Column<T>(source), new Column<TInterface>(mapping)));
+            return this;
+        }
+        
+    }
+    
+    public interface IAutoEntityCartesianGenerator : IAutoEntityGenerator
+    {
+
+        IAutoEntityCartesianTable[] Tables { get; }
+        IAutoEntityCartesianConstant[] Constants { get; }
+        
+        void Configure();
+    }
+    
+    public abstract class AutoEntityCartesianGenerator<TInterface>: IAutoEntityCartesianGenerator
+    {
+        
+        public AutoEntityCartesianGenerator()
+        {
+            Configure();
+        }
+        
+        private List<IAutoEntityCartesianTable> _tables = new List<IAutoEntityCartesianTable>();
+        public IAutoEntityCartesianTable[] Tables => _tables.ToArray();
+
+        private List<IAutoEntityCartesianConstant> _constants = new List<IAutoEntityCartesianConstant>();
+        public IAutoEntityCartesianConstant[] Constants => _constants.ToArray();
+
+        public abstract void Configure();
+        
+        protected AutoEntityCartesianTable<T,TInterface> AddTable<T>(Filter<T> filter)
+        {
+            var table = new AutoEntityCartesianTable<T,TInterface>(filter);
+            _tables.Add(table);
+            return table;
+        }
+        
+        public void AddConstant<TType>(TType constant, Expression<Func<TInterface, object?>> mapping)
+        {
+            _constants.Add(new AutoEntityCartesianConstant(constant, new Column<TInterface>(mapping)));
+        }
+        
+        public Type Definition => typeof(TInterface);
+
+        public abstract bool Distinct { get; }
+    }
+}

+ 51 - 0
InABox.Core/AutoEntity/AutoEntityCrossGenerator.cs

@@ -0,0 +1,51 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace InABox.Core
+{
+    
+    public interface IAutoEntityCrossGenerator : IAutoEntityGenerator
+    {
+        String LeftEntity();
+        String LeftProperty();
+        String LeftMapping();
+        String LeftLink();
+
+        String RightEntity();
+        String RightProperty();
+        String RightMapping();
+        String RightLink();
+    }
+    
+    public abstract class AutoEntityCrossGenerator<TInterface, TLeft, TRight> : IAutoEntityCrossGenerator
+    {
+        public abstract Expression<Func<TLeft, Guid>> LeftProperty { get; }
+        public abstract Expression<Func<TInterface, Guid>> LeftMapping { get; }
+        public abstract Expression<Func<TLeft, Guid>> LeftLink { get; }
+        
+        public abstract Expression<Func<TRight, Guid>> RightProperty { get; }
+        public abstract Expression<Func<TInterface, Guid>> RightMapping { get; }
+        public abstract Expression<Func<TRight, Guid>> RightLink { get; }
+
+        public string LeftEntity() => typeof(TLeft).EntityName().Split('.').Last();
+    
+        string IAutoEntityCrossGenerator.LeftProperty() => CoreUtils.GetFullPropertyName(LeftProperty, ".");
+
+        string IAutoEntityCrossGenerator.LeftMapping() => CoreUtils.GetFullPropertyName(LeftMapping, ".");
+
+        string IAutoEntityCrossGenerator.LeftLink() => CoreUtils.GetFullPropertyName(LeftLink, ".");
+
+        public string RightEntity() => typeof(TRight).EntityName().Split('.').Last();
+
+        string IAutoEntityCrossGenerator.RightProperty() => CoreUtils.GetFullPropertyName(RightProperty, ".");
+
+        string IAutoEntityCrossGenerator.RightMapping() => CoreUtils.GetFullPropertyName(RightMapping, ".");
+
+        string IAutoEntityCrossGenerator.RightLink() => CoreUtils.GetFullPropertyName(RightLink, ".");
+
+        public abstract bool Distinct { get; }
+        
+        public Type Definition => typeof(TInterface);
+    }
+}

+ 38 - 0
InABox.Core/AutoEntity/AutoEntityUnionGenerator.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace InABox.Core
+{
+    
+    public interface IAutoEntityUnionGenerator : IAutoEntityGenerator
+    {
+        Type[] Entities { get; }
+        IFilter? Filter(Type type);
+    }
+    
+    public abstract class AutoEntityUnionGenerator<TInterface> : IAutoEntityUnionGenerator
+    {
+        public AutoEntityUnionGenerator()
+        {
+            Configure();
+        }
+
+        private Dictionary<Type,IFilter> _entities = new Dictionary<Type, IFilter>();
+                
+        public Type[] Entities => _entities.Keys.ToArray();
+
+        public IFilter? Filter(Type type) => _entities.ContainsKey(type) ? _entities[type] : null;
+
+        public void Add<TType>(Filter<TType> filter = null) where TType : TInterface
+        {
+            _entities[typeof(TType)] = filter;
+        }
+
+        protected abstract void Configure(); 
+        
+        public Type Definition => typeof(TInterface);
+        
+        public abstract bool Distinct { get; }
+    }
+}

+ 1 - 1
InABox.Core/CoreUtils.cs

@@ -1392,7 +1392,7 @@ 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) where T : Entity
         {
             //var cols = columns;
             if (columns == null || !columns.Items.Any())

+ 1 - 99
InABox.Core/Entity.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq.Expressions;
 using System.Reflection;
@@ -18,105 +19,6 @@ namespace InABox.Core
     {
     }
     
-    //[JsonObject]
-    //public class EntityHistory : BaseObject
-    //{
-    //    public DateTime Timestamp { get; set; }
-    //    public String User { get; set; }
-    //    public String Note { get; set; }
-    //}
-    
-    
-    public class AutoEntity : Attribute
-    {
-        public IAutoEntityGenerator? Generator { get; private set; }
-        
-        public AutoEntity(Type generator) : base()
-        {
-            Generator = Activator.CreateInstance(generator) as IAutoEntityGenerator;
-        }
-    }
-    
-    
-    public interface IAutoEntityGenerator
-    {
-         bool Distinct { get; }
-         Type Definition { get; }
-    }
-
-    public interface IAutoEntityUnionGenerator : IAutoEntityGenerator
-    {
-        Type[] Entities { get; }
-    }
-
-    public abstract class AutoEntityUnionGenerator<TInterface> : IAutoEntityUnionGenerator
-    {
-        public AutoEntityUnionGenerator()
-        {
-            Configure();
-        }
-
-        private List<Type> _entities = new List<Type>();
-
-        public void Add<TType>()
-            where TType : TInterface
-        {
-            _entities.Add(typeof(TType));
-        }
-
-        protected abstract void Configure(); 
-        
-        public Type[] Entities => _entities.ToArray();
-        
-        public Type Definition => typeof(TInterface);
-
-        public abstract bool Distinct { get; }
-    }
-
-    public interface IAutoEntityCrossGenerator : IAutoEntityGenerator
-    {
-        String LeftEntity();
-        String LeftProperty();
-        String LeftMapping();
-        String LeftLink();
-
-        String RightEntity();
-        String RightProperty();
-        String RightMapping();
-        String RightLink();
-    }
-    
-    public abstract class AutoEntityCrossGenerator<TInterface, TLeft, TRight> : IAutoEntityCrossGenerator
-    {
-        public abstract Expression<Func<TLeft, Guid>> LeftProperty { get; }
-        public abstract Expression<Func<TInterface, Guid>> LeftMapping { get; }
-        public abstract Expression<Func<TLeft, Guid>> LeftLink { get; }
-        
-        public abstract Expression<Func<TRight, Guid>> RightProperty { get; }
-        public abstract Expression<Func<TInterface, Guid>> RightMapping { get; }
-        public abstract Expression<Func<TRight, Guid>> RightLink { get; }
-
-        public string LeftEntity() => typeof(TLeft).EntityName().Split('.').Last();
-    
-        string IAutoEntityCrossGenerator.LeftProperty() => CoreUtils.GetFullPropertyName(LeftProperty, ".");
-
-        string IAutoEntityCrossGenerator.LeftMapping() => CoreUtils.GetFullPropertyName(LeftMapping, ".");
-
-        string IAutoEntityCrossGenerator.LeftLink() => CoreUtils.GetFullPropertyName(LeftLink, ".");
-
-        public string RightEntity() => typeof(TRight).EntityName().Split('.').Last();
-
-        string IAutoEntityCrossGenerator.RightProperty() => CoreUtils.GetFullPropertyName(RightProperty, ".");
-
-        string IAutoEntityCrossGenerator.RightMapping() => CoreUtils.GetFullPropertyName(RightMapping, ".");
-
-        string IAutoEntityCrossGenerator.RightLink() => CoreUtils.GetFullPropertyName(RightLink, ".");
-
-        public abstract bool Distinct { get; }
-        
-        public Type Definition => typeof(TInterface);
-    }
-
     public interface IEntity
     {
         Guid ID { get; set; }

+ 6 - 0
InABox.Core/Filter.cs

@@ -87,6 +87,12 @@ namespace InABox.Core
         Excludes
     }
 
+    public enum FilterConstant
+    {
+        Today,
+        Now,
+    }
+
     public interface IFilter
     {
         Expression Expression { get; set; }

+ 235 - 135
inabox.database.sqlite/SQLiteProvider.cs

@@ -1,5 +1,4 @@
 using System.Collections;
-using System.Collections.Generic;
 using System.Data;
 using System.Data.SQLite;
 using System.Linq.Expressions;
@@ -7,7 +6,6 @@ using System.Reflection;
 using System.Runtime.Serialization.Formatters.Binary;
 using System.Text;
 using InABox.Core;
-using NPOI.POIFS.FileSystem;
 
 
 namespace InABox.Database.SQLite
@@ -711,6 +709,26 @@ namespace InABox.Database.SQLite
                 }
             }
         }
+
+        private Dictionary<String, object?> CheckDefaultColumns(IAutoEntityGenerator generator)
+        {
+            var viewfields = new Dictionary<string, string>();
+            LoadFields(generator.Definition, viewfields, null, new CustomProperty[] { });
+            Dictionary<String, object> result = new Dictionary<string, object>();
+            if (!viewfields.ContainsKey("ID"))
+                result["ID"] = null;
+            if (!viewfields.ContainsKey("Deleted"))
+                result["Deleted"] = null;
+            if (!viewfields.ContainsKey("Created"))
+                result["Created"] = null;
+            if (!viewfields.ContainsKey("CreatedBy"))
+                result["CreatedBy"] = null;
+            if (!viewfields.ContainsKey("LastUpdate"))
+                result["LastUpdate"] = null;
+            if (!viewfields.ContainsKey("LastUpdateBy"))
+                result["LastUpdateBy"] = null;
+            return result;
+        }
         
         private void CreateTable(SQLiteWriteAccessor access, Type type, bool includeconstraints, CustomProperty[] customproperties)
         {
@@ -735,58 +753,133 @@ namespace InABox.Database.SQLite
                 ddl.Add("CREATE VIEW");
                 ddl.Add(type.EntityName().Split('.').Last());
                 ddl.Add("AS");
-
-                var union = view.Generator as IAutoEntityUnionGenerator;
-                if (union != null)
+                
+                if (view.Generator is IAutoEntityUnionGenerator union)
                 {
-                    
-                    var viewfields = new Dictionary<string, string>();
-                    LoadFields(union.Definition, viewfields, null, new CustomProperty[] { });
-                    var fields = viewfields.Keys.Select(x => String.Format("[{0}]", x)).ToList();
-                    if (!fields.Contains("[ID]"))
-                        fields.Add(String.Format("NULL as [ID]"));
-                    if (!fields.Contains("[Deleted]"))
-                        fields.Add(String.Format("NULL as [Deleted]"));
-                    if (!fields.Contains("[Created]"))
-                        fields.Add(String.Format("NULL as [Created]"));
-                    if (!fields.Contains("[CreatedBy]"))
-                        fields.Add(String.Format("NULL as [CreatedBy]"));
-                    if (!fields.Contains("[LastUpdate]"))
-                        fields.Add(String.Format("NULL as [LastUpdate]"));
-                    if (!fields.Contains("[LastUpdateBy]"))
-                        fields.Add(String.Format("NULL as [LastUpdateBy]"));
-                    
                     List<String> queries = new List<String>();
                     foreach (var entity in union.Entities)
-                        queries.Add(String.Format("SELECT {0} {1} FROM {2} WHERE [Deleted] is NULL",
-                            union.Distinct ? "DISTINCT" : "",
-                            String.Join(", ", fields),
-                            entity.EntityName().Split('.').Last())
-                        );
+                    {
+                        
+                        var columns = Activator.CreateInstance(typeof(Columns<>).MakeGenericType(entity)) as IColumns;
+                        var viewfields = new Dictionary<string, string>();
+                        LoadFields(union.Definition, viewfields, null, new CustomProperty[] { });
+                        foreach (var field in viewfields.Keys)
+                            columns.Add(field);
+
+                        var selectFnc = typeof(SQLiteProvider).GetMethod("PrepareSelect").MakeGenericMethod(entity);
+                        var query = selectFnc.Invoke(this, new object[]
+                        {
+                            new SQLiteCommand(),
+                            'A',
+                            union.Filter(entity),
+                            columns,
+                            null,
+                            null,
+                            CheckDefaultColumns(union),
+                            int.MaxValue,
+                            union.Distinct,
+                            false
+                        }) as string;
+                        
+                        queries.Add(query);
+                    }
+                    // var viewfields = new Dictionary<string, string>();
+                    //LoadFields(union.Definition, viewfields, null, new CustomProperty[] { });
+                    // var fields = viewfields.Keys.Select(x => String.Format("[{0}]", x)).ToList();
+                    // if (!fields.Contains("[ID]"))
+                    //     fields.Add(String.Format("NULL as [ID]"));
+                    // if (!fields.Contains("[Deleted]"))
+                    //     fields.Add(String.Format("NULL as [Deleted]"));
+                    // if (!fields.Contains("[Created]"))
+                    //     fields.Add(String.Format("NULL as [Created]"));
+                    // if (!fields.Contains("[CreatedBy]"))
+                    //     fields.Add(String.Format("NULL as [CreatedBy]"));
+                    // if (!fields.Contains("[LastUpdate]"))
+                    //     fields.Add(String.Format("NULL as [LastUpdate]"));
+                    // if (!fields.Contains("[LastUpdateBy]"))
+                    //     fields.Add(String.Format("NULL as [LastUpdateBy]"));
+                    //
+                    // List<String> queries = new List<String>();
+                    // foreach (var entity in union.Entities)
+                    //     queries.Add(String.Format("SELECT {0} {1} FROM {2} WHERE [Deleted] is NULL",
+                    //         union.Distinct ? "DISTINCT" : "",
+                    //         String.Join(", ", fields),
+                    //         entity.EntityName().Split('.').Last())
+                    //     );
                     ddl.Add(String.Join(" UNION ", queries));
                 }
-                else
+                else if ( view.Generator is IAutoEntityCrossGenerator cross)
+                {
+                    List<String> constants = new List<string>();
+                    foreach (var constant in CheckDefaultColumns(cross))
+                        constants.Add($"{EscapeValue(constant.Value)} as [{constant.Key}]");
+                    
+                    String query = String.Format(
+                        "SELECT {0} {1}.[{2}] as [{3}], {4}.[{5}] as [{6}], {7} FROM {1}, {4} WHERE {1}.[Deleted] is NULL and {4}.[Deleted] is NULL and {1}.[{8}] = {4}.[{9}]",
+                        cross.Distinct ? "DISTINCT" : "",
+                        cross.LeftEntity(),
+                        cross.LeftProperty(),
+                        cross.LeftMapping(),
+                        cross.RightEntity(),
+                        cross.RightProperty(),
+                        cross.RightMapping(),
+                        String.Join(", ", constants),
+                        cross.LeftLink(),
+                        cross.RightLink()
+                    );
+                    
+                    ddl.Add(query);
+                }
+                else if ( view.Generator is IAutoEntityCartesianGenerator cartesian)
                 {
-                    var cross = view.Generator as IAutoEntityCrossGenerator;
-                    if (cross != null)
+
+                    List<String> fields = new List<string>();
+                    List<String> tables = new List<String>();
+                    List<String> filters = new List<String>();
+                    int iTable = 0;
+                    foreach (var table in cartesian.Tables)
                     {
-                        String query = String.Format(
-                            "SELECT {0} {1}.[{2}] as [{3}], {4}.[{5}] as [{6}], NULL as [ID], NULL as [Deleted], NULL as [Created], NULL as [CreatedBy], NULL as [LastUpdate], NULL as [LastUpdateBy] FROM {1}, {4} WHERE {1}.[Deleted] is NULL and {4}.[Deleted] is NULL and {1}.[{7}] = {4}.[{8}]",
-                            cross.Distinct ? "DISTINCT" : "",
-                            cross.LeftEntity(),
-                            cross.LeftProperty(),
-                            cross.LeftMapping(),
-                            cross.RightEntity(),
-                            cross.RightProperty(),
-                            cross.RightMapping(),
-                            cross.LeftLink(),
-                            cross.RightLink()
-                        );
                         
-                        ddl.Add(query);
+                        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;
+                        
+                        tables.Add($"({subQueryText}) T{iTable}");
+                        
+                        foreach (var mapping in table.Mappings)
+                            fields.Add($"T{iTable}.[{mapping.Column.Property}] as [{mapping.Mapping.Property}]");
+                        
+                        iTable++;
                     }
+
+                    foreach (var constant in cartesian.Constants)
+                        fields.Add($"{EscapeValue(constant.Constant)} as [{constant.Mapping.Property}]");
+                    
+                    foreach (var constant in CheckDefaultColumns(cartesian))
+                        fields.Add($"{EscapeValue(constant.Value)} as [{constant.Key}]");
+
+                    StringBuilder sb = new StringBuilder();
+                    sb.Append("SELECT ");
+                    sb.Append(String.Join(", ", fields));
+                    sb.Append(" FROM ");
+                    sb.Append(String.Join(", ", tables));
+                    if (filters.Any())
+                        sb.Append($" WHERE {String.Join(" AND ", filters)}");
+                    ddl.Add(sb.ToString());
                 }
 
+
                 ddl.Add(";");
                 var viewstatement = string.Join(" ", ddl);
             }
@@ -952,7 +1045,7 @@ namespace InABox.Database.SQLite
                     else
                         tType = CoreUtils.GetProperty(type, field).PropertyType;
 
-                    if (tType == typeof(TimeSpan) && current_fields.ContainsKey(field) && !current_fields[field].Equals(type_fields[field]))
+                    if ((view == null) && (tType == typeof(TimeSpan)) && current_fields.ContainsKey(field) && !current_fields[field].Equals(type_fields[field]))
                     {
                         var sql = string.Format(
                             "update {0} set [{1}] = cast(substr([{1}],1,2) as double) + cast(substr([{1}],4,2) as double)/60 + cast(substr([{1}],7,2) as double)/3600 where [{1}] like \"%:%:%\"",
@@ -1218,8 +1311,33 @@ namespace InABox.Database.SQLite
             { Operator.InQuery, "{0} IN ({1})" }
         };
 
+        private String EscapeValue(object value)
+        {
+            if (IsNull(value))
+                return "NULL";
+            return (value is String) || (value is Enum)
+                ? String.Format("\'"+"{0}"+"\'", value.ToString().Replace("\'", "\'\'"))
+                : value is String[] 
+                    ? string.Format("hex({0})", BitConverter.ToString(Encoding.ASCII.GetBytes(value.ToString())).Replace("-", string.Empty))
+                    : value.ToString();
+
+        }
+
+
+        private String GetFilterConstant(FilterConstant constant)
+        {
+            switch (constant)
+            {
+                case FilterConstant.Now :
+                    return "datetime()";
+                case FilterConstant.Today :
+                    return "date()";
+            }
+            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) where T : Entity
+            Dictionary<string, string> fieldmap, List<string> columns, bool useparams) where T : Entity
         {
             if (filter == null || filter.Expression == null)
                 return "";
@@ -1249,7 +1367,7 @@ namespace InABox.Database.SQLite
                     mexp = CoreUtils.GetMemberExpression(typeof(T), prop);
                 }
 
-                LoadFieldsandTables(command, typeof(T), prefix, fieldmap, tables, columns, prop);
+                LoadFieldsandTables(command, typeof(T), prefix, fieldmap, tables, columns, prop, useparams);
                 if (fieldmap.ContainsKey(prop))
                     prop = fieldmap[prop];
 
@@ -1300,39 +1418,52 @@ namespace InABox.Database.SQLite
                         subColumns,
                         null,
                         null,
+                        null,
                         int.MaxValue,
-                        false
+                        false,
+                        useparams
                     }) as string;
 
                     result = string.Format("(" + operators[filter.Operator] + ")", prop, subQueryText);
                 }
                 else
                 {
-                    var value = Encode(filter.Value, mexp.Type);
-                    if (IsNull(value))
-                    {
-                        result = string.Format("({0} {1} NULL)", prop,
-                            filter.Operator == Operator.IsEqualTo || filter.Operator == Operator.IsLessThan ||
-                            filter.Operator == Operator.IsLessThanOrEqualTo
-                                ? "IS"
-                                : "IS NOT");
-                    }
+                    if (filter.Value is FilterConstant)
+                        result = string.Format("(" + operators[filter.Operator] + ")", prop, GetFilterConstant((FilterConstant)filter.Value));
                     else
                     {
-                        var sParam = string.Format("@p{0}", command.Parameters.Count);
-
-                        if (filter.Expression.Type == typeof(string[]))
+                        var value = Encode(filter.Value, mexp.Type);
+                        if (IsNull(value))
                         {
-                            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);
+                            result = string.Format("({0} {1} NULL)", prop,
+                                filter.Operator == Operator.IsEqualTo || filter.Operator == Operator.IsLessThan ||
+                                filter.Operator == Operator.IsLessThanOrEqualTo
+                                    ? "IS"
+                                    : "IS NOT");
                         }
                         else
                         {
-                            result = string.Format("(" + operators[filter.Operator] + ")", prop, sParam);
+                            if (useparams)
+                            {
+                                var sParam = string.Format("@p{0}", command.Parameters.Count);
+
+                                if (filter.Expression.Type == typeof(string[]))
+                                {
+                                    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);
+                                }
+                                else
+                                {
+                                    result = string.Format("(" + operators[filter.Operator] + ")", prop, sParam);
+                                }
+
+                                command.Parameters.AddWithValue(sParam, value);
+                            }
+                            else
+                                result = string.Format("(" + operators[filter.Operator] + ")", prop, EscapeValue(filter.Value));
                         }
 
-                        command.Parameters.AddWithValue(sParam, value);
                     }
                 }
             }
@@ -1342,7 +1473,7 @@ namespace InABox.Database.SQLite
             {
                 foreach (var and in filter.Ands)
                 {
-                    var AndResult = GetFilterClause(command, prefix, and, tables, fieldmap, columns);
+                    var AndResult = GetFilterClause(command, prefix, and, tables, fieldmap, columns, useparams);
                     if (!string.IsNullOrEmpty(AndResult))
                     {
                         result = string.Format("{0} and {1}", result, AndResult);
@@ -1359,7 +1490,7 @@ namespace InABox.Database.SQLite
             {
                 foreach (var or in filter.Ors)
                 {
-                    var OrResult = GetFilterClause(command, prefix, or, tables, fieldmap, columns);
+                    var OrResult = GetFilterClause(command, prefix, or, tables, fieldmap, columns, useparams);
                     if (!string.IsNullOrEmpty(OrResult))
                     {
                         result = string.Format("{0} or {1}", result, OrResult);
@@ -1375,7 +1506,7 @@ namespace InABox.Database.SQLite
         }
 
         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) where T : Entity
+            Dictionary<string, string> fieldmap, List<string> columns, bool useparams) where T : Entity
         {
             if (sort == null)
                 return "";
@@ -1392,7 +1523,7 @@ namespace InABox.Database.SQLite
                 var prop = CoreUtils.GetProperty(typeof(T), result);
                 if (prop.GetCustomAttribute<DoNotSerialize>() == null && prop.GetCustomAttribute<DoNotPersist>() == null && prop.CanWrite)
                 {
-                    LoadFieldsandTables(command, typeof(T), prefix, fieldmap, tables, columns, result);
+                    LoadFieldsandTables(command, typeof(T), prefix, fieldmap, tables, columns, result, useparams);
                     if (fieldmap.ContainsKey(result))
                         result = fieldmap[result];
 
@@ -1411,7 +1542,7 @@ namespace InABox.Database.SQLite
             }
 
             foreach (var then in sort.Thens)
-                result = result + ", " + GetSortClause(command, then, prefix, tables, fieldmap, columns);
+                result = result + ", " + GetSortClause(command, then, prefix, tables, fieldmap, columns, useparams);
 
             return result;
         }
@@ -1562,7 +1693,7 @@ namespace InABox.Database.SQLite
         //}
 
         private void LoadFieldsandTables(SQLiteCommand command, Type type, char prefix, Dictionary<string, string> fieldmap,
-            List<Tuple<string, string, string, string>> tables, List<string> columns, string columnname)
+            List<Tuple<string, string, string, string>> tables, List<string> columns, string columnname, bool useparams)
         {
             if (fieldmap.ContainsKey(columnname))
                 return;
@@ -1643,7 +1774,6 @@ namespace InABox.Database.SQLite
 
                                 // LogStart();
                                 var preparemethod = GetType().GetMethod("PrepareSelect").MakeGenericMethod(linkedtype);
-                                // LogStop("PrepareSelect<>.MakeGenericMethod");
 
                                 var filter = Activator.CreateInstance(typeof(Filter<>).MakeGenericType(linkedtype), "Deleted") as IFilter;
                                 filter!.Operator = Operator.IsEqualTo;
@@ -1657,12 +1787,10 @@ namespace InABox.Database.SQLite
                                         subcols.Add(ff);
                                     filter.And(aggFilter);
                                 }
-
-                                // LogStart();
+                                
                                 var linkedtable = string.Format("({0})",
-                                    preparemethod.Invoke(this, new object[] { command, newprefix, filter, subcols, null, scols, int.MaxValue, false }));
-                                // LogStop("PrepareSelect<>.Invoke");
-
+                                    preparemethod.Invoke(this, new object[] { command, newprefix, filter, subcols, null, scols, null, int.MaxValue, false, useparams }));
+                                
                                 var alias = tables.Count + 1;
 
                                 var link = string.Join(" , ", agg.Links.Keys.Select(x => string.Format("{0}{1}.{2}", prefix, alias, x)));
@@ -1714,7 +1842,7 @@ namespace InABox.Database.SQLite
                                 // LogStop("Formula.CheckColumn");
 
                                 //// LogStart();
-                                LoadFieldsandTables(command, type, prefix, functionmap, tables, columns, fnc.Value);
+                                LoadFieldsandTables(command, type, prefix, functionmap, tables, columns, fnc.Value, useparams);
                                 //// LogStop("Formula.LoadFieldsAndTables");
 
                                 foreach (var column in fnc.Modifiers)
@@ -1724,7 +1852,7 @@ namespace InABox.Database.SQLite
                                     // LogStop("Formula.Modifiers.CheckColumn");
 
                                     //// LogStart();
-                                    LoadFieldsandTables(command, type, prefix, functionmap, tables, columns, column);
+                                    LoadFieldsandTables(command, type, prefix, functionmap, tables, columns, column, useparams);
                                     //// LogStop("Formula.Modifiers.LoadFieldsAndTables");
                                 }
 
@@ -1758,19 +1886,19 @@ namespace InABox.Database.SQLite
                                     // LogStop("CheckColumn(False)");
 
                                     //// LogStart();
-                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.Left);
+                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.Left, useparams);
                                     //// LogStop("LoadFieldsAndTables(Left)");
 
                                     //// LogStart();
-                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.Right);
+                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.Right, useparams);
                                     //// LogStop("LoadFieldsAndTables(Right)");
 
                                     //// LogStart();
-                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.True);
+                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.True, useparams);
                                     //// LogStop("LoadFieldsAndTables(True)");
 
                                     //// LogStart();
-                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.False);
+                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.False, useparams);
                                     //// LogStop("LoadFieldsAndTables(False)");
 
                                     // LogStart();
@@ -1845,20 +1973,17 @@ namespace InABox.Database.SQLite
                                 foreach (var sibling in siblings)
                                     subcols.Add(sibling);
 
-                                // LogStart();
+
                                 var preparemethod = GetType().GetMethod("PrepareSelect")!.MakeGenericMethod(linkedtype);
-                                // LogStop("PrepareSelect<>.MakeGenericMethod");
 
                                 var filter = Activator.CreateInstance(typeof(Filter<>).MakeGenericType(linkedtype), "Deleted") as IFilter;
                                 filter!.Operator = Operator.IsEqualTo;
                                 filter.Value = Guid.Empty;
 
-                                // LogStart();
+
                                 var linkedtable = string.Format("({0})",
-                                    preparemethod.Invoke(this, new object?[] { command, newprefix, filter, subcols, null, null, int.MaxValue, false }));
-                                // LogStop("PrepareSelect<>.Invoke");
+                                    preparemethod.Invoke(this, new object?[] { command, newprefix, filter, subcols, null, null, null, int.MaxValue, false, useparams }));
 
-                                //String linkedtable = linkedtype.EntityName().Split('.').Last();
                                 var link = string.Format("{0}.ID", prop.Name);
                                 var tuple = tables.FirstOrDefault(x =>
                                     x.Item1.Equals(linkedtable) && x.Item4.Equals(link) && !x.Item2.Equals(string.Format("{0}1", prefix)));
@@ -1915,7 +2040,7 @@ namespace InABox.Database.SQLite
                 columns.Add(column);
         }
 
-        public void FilterFields<T>(Filter<T> filter, List<string> fields)
+        public void FilterFields<T>(Filter<T>? filter, List<string> fields)
         {
             if (filter == null)
                 return;
@@ -1931,7 +2056,7 @@ namespace InABox.Database.SQLite
                 FilterFields(or, fields);
         }
 
-        public void SortFields<T>(SortOrder<T> sort, List<string> fields)
+        public void SortFields<T>(SortOrder<T>? sort, List<string> fields)
         {
             if (sort == null)
                 return;
@@ -1949,36 +2074,26 @@ 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, int top = int.MaxValue, bool distinct = false) where T : Entity
+        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
         {
-            //Breadcrumb.Add(typeof(T).Name);
 
             var fieldmap = new Dictionary<string, string>();
-
-            //LogStart();
+            
             var cols = CoreUtils.GetColumns(columns);
-            //LogStop("PrepareSelect.GetColumns");
 
             var fields = new List<string>();
-
-
+            
             fields.AddRange(cols.ColumnNames());
-
-            //LogStart();
+            
             FilterFields(filter, fields);
-            //LogStop("PrepareSelect.FilterFields");
-
-            //LogStart();
             SortFields(sort, fields);
-            //LogStop("PrepareSelect.SortFields");
 
             var tables = new List<Tuple<string, string, string, string>>();
 
             var condition = "";
             var sortorder = "";
-
-
+            
             tables.Add(new Tuple<string, string, string, string>(
                 typeof(T).EntityName().Split('.').Last(),
                 string.Format("{0}1", prefix),
@@ -1986,17 +2101,13 @@ namespace InABox.Database.SQLite
                 "")
             );
 
-            foreach (var column in cols.Items) LoadFieldsandTables(command, typeof(T), prefix, fieldmap, tables, fields, column.Property);
+            foreach (var column in cols.Items) 
+                LoadFieldsandTables(command, typeof(T), prefix, fieldmap, tables, fields, column.Property, useparams);
 
             var parameters = new Dictionary<string, object>();
-
-            //LogStart();
-            condition = GetFilterClause(command, prefix, filter, tables, fieldmap, fields);
-            //LogStop("PrepareSelect.GetFilterClause");
-
-            //LogStart();
-            sortorder = GetSortClause(command, sort, prefix, tables, fieldmap, fields);
-            //LogStop("PrepareSelect.GetSortClause");
+            
+            condition = GetFilterClause(command, prefix, filter, tables, fieldmap, fields, useparams);
+            sortorder = GetSortClause(command, sort, prefix, tables, fieldmap, fields, useparams);
 
             fields.Clear();
             foreach (var column in cols.Items)
@@ -2008,6 +2119,12 @@ namespace InABox.Database.SQLite
                         fields.Add(string.Format("{0} as [{1}]", fieldmap[column.Property], column.Property));
                 }
 
+            if (constants != null)
+            {
+                foreach (var column in constants.Keys)
+                    fields.Add(string.Format("{0} as [{1}]", EscapeValue(constants[column]), column));
+            }
+
             var result = new List<string>();
             result.Add("SELECT");
             if (distinct)
@@ -2181,33 +2298,17 @@ namespace InABox.Database.SQLite
 
                     command.CommandText = "";
                     command.Parameters.Clear();
-                    command.CommandText = PrepareSelect(command, 'A', filter, cols, sortorder, null, int.MaxValue) + ";";
-
-                    //stopwatch["PrepareSelect"] = new TimeSpan(sw.ElapsedTicks);
-                    //sw.Restart();
+                    command.CommandText = PrepareSelect(command, 'A', filter, cols, sortorder, null, null, int.MaxValue, false, true) + ";";
 
                     using (var reader = command.ExecuteReader())
                     {
-                        //stopwatch["QueryDatabase"] = new TimeSpan(sw.ElapsedTicks);
-                        //sw.Restart();
-
                         foreach (var row in reader)
                         {
                             var values = GetValues(reader, cols.Items.Length);
                             result.Add(values);
                         }
-                        //yield return GetValues(reader, cols.Items.Length);
-
-                        //stopwatch["ReadData"] = new TimeSpan(sw.ElapsedTicks);
-                        //sw.Restart();
-
                         reader.Close();
-
-                        //stopwatch["Close"] = new TimeSpan(sw.ElapsedTicks);
-                        //sw.Restart();
                     }
-                    //foreach (var key in stopwatch.Keys)
-                    //    OnLog?.Invoke(String.Format("List{0}: {1} = {2,10:0.0000}", typeof(T).EntityName().Split('.').Last(), key, stopwatch[key].TotalMilliseconds), false);
                 }
             }
 
@@ -2240,10 +2341,9 @@ namespace InABox.Database.SQLite
                     command.Parameters.Clear();
 
                     //LogStart();
-                    String sql = PrepareSelect(command, 'A', filter, cols, sortorder, null, top, distinct) + ";";
+                    String sql = PrepareSelect(command, 'A', filter, cols, sortorder, null, null, top, distinct, true) + ";";
    
                     command.CommandText = sql;
-                    //LogStop("PrepareSelect");
 
                     try
                     {
@@ -2404,7 +2504,7 @@ namespace InABox.Database.SQLite
                     {
                         command.CommandText = "";
                         command.Parameters.Clear();
-                        command.CommandText = PrepareSelect(command, 'A', filter, cols, sort, null, int.MaxValue) + ";";
+                        command.CommandText = PrepareSelect(command, 'A', filter, cols, sort, null, null, int.MaxValue, false, true) + ";";
                         using (var reader = command.ExecuteReader())
                         {
                             if (reader.HasRows)