|
@@ -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
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
}
|