Sfoglia il codice sorgente

Added ChildEntity EntityLink type

Kenric Nugteren 1 anno fa
parent
commit
d946093b8a

+ 44 - 0
InABox.Core/Aggregate.cs

@@ -481,4 +481,48 @@ namespace InABox.Core
     }
 
     #endregion
+
+    #region ChildEntity
+
+    public interface IChildEntityDefinition
+    {
+        string ParentColumn { get; }
+
+        Type EntityType { get; }
+
+        IFilter? Filter { get; }
+
+        ISortOrder? Sort { get; }
+    }
+
+    public interface IChildEntityDefinition<TEntity> : IChildEntityDefinition
+        where TEntity : Entity
+    {
+        new Filter<TEntity>? Filter { get; }
+
+        new SortOrder<TEntity>? Sort { get; }
+
+        Expression<Func<TEntity, Guid>> Parent { get; }
+
+        Type IChildEntityDefinition.EntityType => typeof(TEntity);
+
+        IFilter? IChildEntityDefinition.Filter => Filter;
+
+        ISortOrder? IChildEntityDefinition.Sort => Sort;
+
+        string IChildEntityDefinition.ParentColumn => CoreUtils.GetFullPropertyName(Parent, ".");
+    }
+
+    public class ChildEntityAttribute : Attribute
+    {
+        public IChildEntityDefinition Calculator { get; set; }
+
+        public ChildEntityAttribute(Type definition)
+        {
+            Calculator = (Activator.CreateInstance(definition) as IChildEntityDefinition)
+                ?? throw new Exception($"{definition} is not an {nameof(IChildEntityDefinition)}");
+        }
+    }
+
+    #endregion
 }

+ 1 - 0
InABox.Core/BaseObject.cs

@@ -402,6 +402,7 @@ namespace InABox.Core
                     && x.GetCustomAttribute<AggregateAttribute>() == null
                     && x.GetCustomAttribute<FormulaAttribute>() == null
                     && x.GetCustomAttribute<ConditionAttribute>() == null
+                    && x.GetCustomAttribute<ChildEntityAttribute>() == null
                     && x.CanWrite);
                 foreach (var prop in props)
                     if (prop.PropertyType.GetInterfaces().Contains(typeof(IEnclosedEntity)))

+ 11 - 2
InABox.Core/Column.cs

@@ -174,8 +174,17 @@ namespace InABox.Core
             var result = Activator.CreateInstance(type) as IColumns;
             return result!;
         }
-        
-        public static IColumns Create(Type concrete, params String[] columns)
+
+        public static IColumns Create(Type concrete, IEnumerable<string> columns)
+        {
+            var type = typeof(Columns<>).MakeGenericType(concrete);
+            var result = (IColumns)Activator.CreateInstance(type);
+            foreach (var column in columns)
+                result.Add(column);
+            return result;
+        }
+
+        public static IColumns Create(Type concrete, params string[] columns)
         {
             var type = typeof(Columns<>).MakeGenericType(concrete);
             var result = (IColumns)Activator.CreateInstance(type);

+ 2 - 1
InABox.Core/DatabaseSchema/StandardProperty.cs

@@ -119,7 +119,8 @@ namespace InABox.Core
                 {
                     _calculated = Property.GetCustomAttribute<AggregateAttribute>() != null
                         || Property.GetCustomAttribute<FormulaAttribute>() != null
-                        || Property.GetCustomAttribute<ConditionAttribute>() != null;
+                        || Property.GetCustomAttribute<ConditionAttribute>() != null
+                        || Property.GetCustomAttribute<ChildEntityAttribute>() != null;
                 }
                 return _calculated.Value;
             }

+ 1 - 0
InABox.Core/Serialization.cs

@@ -666,6 +666,7 @@ namespace InABox.Core
                 && x.GetCustomAttribute<AggregateAttribute>() == null
                 && x.GetCustomAttribute<FormulaAttribute>() == null
                 && x.GetCustomAttribute<ConditionAttribute>() == null
+                && x.GetCustomAttribute<ChildEntityAttribute>() == null
                 && x.CanWrite);
             foreach (var prop in props)
             {

+ 142 - 90
inabox.database.sqlite/SQLiteProvider.cs

@@ -564,6 +564,7 @@ namespace InABox.Database.SQLite
                 && x.GetCustomAttributes().FirstOrDefault(a => a.GetType().Equals(typeof(AggregateAttribute))) == null
                 && x.GetCustomAttributes().FirstOrDefault(a => a.GetType().Equals(typeof(FormulaAttribute))) == null
                 && x.GetCustomAttributes().FirstOrDefault(a => a.GetType().Equals(typeof(ConditionAttribute))) == null
+                && x.GetCustomAttributes().FirstOrDefault(a => a.GetType().Equals(typeof(ChildEntityAttribute))) == null
                 && x.CanWrite
                 && x.PropertyType != typeof(UserProperties)
             );
@@ -599,7 +600,9 @@ namespace InABox.Database.SQLite
 
             var properties = type.GetProperties().Where(x =>
                 x.GetCustomAttribute<DoNotSerialize>() == null && x.GetCustomAttribute<DoNotPersist>() == null &&
-                x.GetCustomAttributes().FirstOrDefault(a => a.GetType().IsSubclassOf(typeof(AggregateAttribute))) == null && x.CanWrite);
+                x.GetCustomAttributes().FirstOrDefault(a => a.GetType().IsSubclassOf(typeof(AggregateAttribute))) == null
+                && x.GetCustomAttributes().FirstOrDefault(a => a.GetType().Equals(typeof(ChildEntityAttribute))) == null
+                && x.CanWrite);
             foreach (var property in properties)
                 if (property.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)))
                 {
@@ -1767,10 +1770,9 @@ namespace InABox.Database.SQLite
                     if (prop != null)
                     {
                         // LogStart();
-                        var attr = prop.GetCustomAttributes().FirstOrDefault(x => x.GetType().Equals(typeof(AggregateAttribute)));
                         // LogStop("GetCustomAttributes(Aggregate)");
 
-                        if (attr is AggregateAttribute agg)
+                        if (prop.GetCustomAttribute<AggregateAttribute>() is AggregateAttribute agg)
                         {
                             bool internalaggregate = agg.Calculator.GetType().GetInterfaces()
                                 .Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICoreAggregate<,>));
@@ -1862,83 +1864,69 @@ namespace InABox.Database.SQLite
                                 //fieldmap[columnname] = String.Format("{0}.[{1}]", tuple.Item2, agg.AggregateProperty);
                             }
                         }
-                        else
+                        else if (prop.GetCustomAttribute<FormulaAttribute>() is FormulaAttribute fnc)
                         {
-                            // LogStart();
-                            attr = prop.GetCustomAttributes().FirstOrDefault(x => x.GetType().Equals(typeof(FormulaAttribute)));
-                            // LogStop("GetAttribute(Formula)");
 
-                            if (attr is FormulaAttribute fnc)
+                            // var functionmap = new Dictionary<string, string>();
+                            // CheckColumn(columns, fnc.Value);
+                            // LoadFieldsandTables(command, type, prefix, functionmap, tables, columns, fnc.Value, useparams);
+                            // foreach (var column in fnc.Modifiers)
+                            // {
+                            //     CheckColumn(columns, column);
+                            //     LoadFieldsandTables(command, type, prefix, functionmap, tables, columns, column, useparams);
+                            // }
+                            // fieldmap[columnname] = GetFunction(fnc, functionmap, columnname);
+
+                            foreach (var field in fnc.Modifiers.Prepend(fnc.Value))
                             {
-                                
-                                // var functionmap = new Dictionary<string, string>();
-                                // CheckColumn(columns, fnc.Value);
-                                // LoadFieldsandTables(command, type, prefix, functionmap, tables, columns, fnc.Value, useparams);
-                                // foreach (var column in fnc.Modifiers)
-                                // {
-                                //     CheckColumn(columns, column);
-                                //     LoadFieldsandTables(command, type, prefix, functionmap, tables, columns, column, useparams);
-                                // }
-                                // fieldmap[columnname] = GetFunction(fnc, functionmap, columnname);
-
-                                foreach (var field in fnc.Modifiers.Prepend(fnc.Value))
-                                {
-                                    CheckColumn(columns, field);
-                                    LoadFieldsandTables(command, type, prefix, fieldmap, tables, columns, field, useparams);
-                                }
-                                fieldmap[columnname] = GetFunction(fnc, fieldmap, columnname);
-                                
+                                CheckColumn(columns, field);
+                                LoadFieldsandTables(command, type, prefix, fieldmap, tables, columns, field, useparams);
                             }
-                            else
-                            {
-                                // LogStart();
-                                attr = prop.GetCustomAttributes().FirstOrDefault(x => x.GetType().Equals(typeof(ConditionAttribute)));
-                                // LogStop("GetAttribute(Condition)");
+                            fieldmap[columnname] = GetFunction(fnc, fieldmap, columnname);
 
-                                if (attr is ConditionAttribute cnd)
-                                {
-                                    var cndmap = new Dictionary<string, string>();
-                                    // LogStart();
-                                    CheckColumn(columns, cnd.Left);
-                                    // LogStop("CheckColumn(Left)");
+                        }
+                        else if(prop.GetCustomAttribute<ConditionAttribute>() is ConditionAttribute cnd)
+                        {
+                            var cndmap = new Dictionary<string, string>();
+                            // LogStart();
+                            CheckColumn(columns, cnd.Left);
+                            // LogStop("CheckColumn(Left)");
 
-                                    // LogStart();
-                                    CheckColumn(columns, cnd.Right);
-                                    // LogStop("CheckColumn(Right)");
+                            // LogStart();
+                            CheckColumn(columns, cnd.Right);
+                            // LogStop("CheckColumn(Right)");
 
-                                    // LogStart();
-                                    CheckColumn(columns, cnd.True);
-                                    // LogStop("CheckColumn(True)");
+                            // LogStart();
+                            CheckColumn(columns, cnd.True);
+                            // LogStop("CheckColumn(True)");
 
-                                    // LogStart();
-                                    CheckColumn(columns, cnd.False);
-                                    // LogStop("CheckColumn(False)");
+                            // LogStart();
+                            CheckColumn(columns, cnd.False);
+                            // LogStop("CheckColumn(False)");
 
-                                    //// LogStart();
-                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.Left, useparams);
-                                    //// LogStop("LoadFieldsAndTables(Left)");
+                            //// LogStart();
+                            LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.Left, useparams);
+                            //// LogStop("LoadFieldsAndTables(Left)");
 
-                                    //// LogStart();
-                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.Right, useparams);
-                                    //// LogStop("LoadFieldsAndTables(Right)");
+                            //// LogStart();
+                            LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.Right, useparams);
+                            //// LogStop("LoadFieldsAndTables(Right)");
 
-                                    //// LogStart();
-                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.True, useparams);
-                                    //// LogStop("LoadFieldsAndTables(True)");
+                            //// LogStart();
+                            LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.True, useparams);
+                            //// LogStop("LoadFieldsAndTables(True)");
 
-                                    //// LogStart();
-                                    LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.False, useparams);
-                                    //// LogStop("LoadFieldsAndTables(False)");
+                            //// LogStart();
+                            LoadFieldsandTables(command, type, prefix, cndmap, tables, columns, cnd.False, useparams);
+                            //// LogStop("LoadFieldsAndTables(False)");
 
-                                    // LogStart();
-                                    fieldmap[columnname] = GetCondition(cnd, cndmap, columnname);
-                                    // LogStop("GetCondition");
-                                }
-                                else
-                                {
-                                    fieldmap[columnname] = string.Format("{0}1.[{1}]", prefix, columnname);
-                                }
-                            }
+                            // LogStart();
+                            fieldmap[columnname] = GetCondition(cnd, cndmap, columnname);
+                            // LogStop("GetCondition");
+                        }
+                        else
+                        {
+                            fieldmap[columnname] = string.Format("{0}1.[{1}]", prefix, columnname);
                         }
                     }
                     else
@@ -1964,46 +1952,61 @@ namespace InABox.Database.SQLite
 
                         if (prop.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)))
                         {
-                            var linkedtype = prop.PropertyType.GetInheritedGenericTypeArguments().First();
-                            var remote = linkedtype.GetProperty(bits.Last());
+                            var linkedType = prop.PropertyType.GetInheritedGenericTypeArguments().First();
+                            var remote = linkedType.GetProperty(bits.Last());
 
                             // Are there any other properties for this link?  These will form the columns for our subquery
                             var siblings = columns.Where(x => x.Split('.').First().Equals(bits.First()))
                                 .Select(x => string.Join(".", x.Split('.').Skip(combinecount))).ToList();
                             if (remote != null && !siblings.Contains(remote.Name))
                                 siblings.Add(remote.Name);
-                            
-                            if (siblings.Count.Equals(1) && siblings.First().Equals("ID"))
-                                fieldmap[columnname] = string.Format("{0}1.[{1}]", prefix, columnname);
-                            
-                            else
-                            {
-                                if (!siblings.Contains("ID"))
-                                    siblings.Insert(0, "ID");
-
-                                var subcols = Columns.Create(linkedtype);
-
-                                foreach (var sibling in siblings)
-                                    subcols.Add(sibling);
 
-                                var linkedtable = string.Format("({0})",
-                                    PrepareSelectNonGeneric(linkedtype, command, newprefix, null, subcols, null, null, null, int.MaxValue, false, useparams));
+                            if(prop.GetCustomAttribute<ChildEntityAttribute>() is ChildEntityAttribute child)
+                            {
+                                var parent = child.Calculator.ParentColumn;
+
+                                if (!siblings.Contains(nameof(Entity.ID)))
+                                    siblings.Insert(0, nameof(Entity.ID));
+                                if (!siblings.Contains(parent))
+                                    siblings.Add(parent);
+
+                                var subcols = Columns.Create(linkedType, siblings);
+
+                                var subPrefix = (char)(newprefix + 1);
+
+                                var innerSQL = string.Format("({0})",
+                                    PrepareSelectNonGeneric(
+                                        linkedType,
+                                        command,
+                                        subPrefix,
+                                        child.Calculator.Filter,
+                                        subcols,
+                                        child.Calculator.Sort,
+                                        null,
+                                        null,
+                                        int.MaxValue,
+                                        false,
+                                        useparams));
+
+                                var linkedTable = $"(SELECT {string.Join(", ", siblings.Select(x => $"{newprefix}1.[{x}] as [{x}]"))}"
+                                    + $" FROM {innerSQL} {newprefix}1"
+                                    + $" GROUP BY [{parent}])";
 
                                 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)));
-                                if (tuple == null)
+                                    x.Item1.Equals(linkedTable) && x.Item4.Equals(link) && !x.Item2.Equals(string.Format("{0}1", prefix)));
+                                if (tuple is null)
                                 {
                                     var alias = tables.Count + 1;
                                     tuple = new Tuple<string, string, string, string>(
-                                        linkedtable,
+                                        linkedTable,
                                         prefix + alias.ToString(),
                                         string.Format(
-                                            "LEFT OUTER JOIN {0} {1}{2} ON {1}{2}.[ID] = {1}1.[{3}.ID]",
-                                            linkedtable,
+                                            "LEFT OUTER JOIN {0} {1}{2} ON {1}{2}.[{3}] = {1}1.[ID]",
+                                            linkedTable,
                                             prefix,
                                             alias,
-                                            string.Join(".", bits.Take(combinecount)) //prop.Name
+                                            parent
                                         ),
                                         link
                                     );
@@ -2021,6 +2024,55 @@ namespace InABox.Database.SQLite
                                         fieldmap[subcol] = string.Format("{0}.[{1}]", tuple.Item2, sibling);
                                 }
                             }
+                            else
+                            {
+                                if (siblings.Count.Equals(1) && siblings.First().Equals("ID"))
+                                {
+                                    fieldmap[columnname] = string.Format("{0}1.[{1}]", prefix, columnname);
+                                }
+                                else
+                                {
+                                    if (!siblings.Contains("ID"))
+                                        siblings.Insert(0, "ID");
+
+                                    var subcols = Columns.Create(linkedType, siblings);
+
+                                    var linkedtable = string.Format("({0})",
+                                        PrepareSelectNonGeneric(linkedType, command, newprefix, null, subcols, null, null, null, int.MaxValue, false, useparams));
+
+                                    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)));
+                                    if (tuple == null)
+                                    {
+                                        var alias = tables.Count + 1;
+                                        tuple = new Tuple<string, string, string, string>(
+                                            linkedtable,
+                                            prefix + alias.ToString(),
+                                            string.Format(
+                                                "LEFT OUTER JOIN {0} {1}{2} ON {1}{2}.[ID] = {1}1.[{3}.ID]",
+                                                linkedtable,
+                                                prefix,
+                                                alias,
+                                                string.Join(".", bits.Take(combinecount)) //prop.Name
+                                            ),
+                                            link
+                                        );
+                                        tables.Add(tuple);
+                                    }
+
+                                    //if (bits.Last().Equals("ID"))
+                                    //    fieldmap[columnname] = String.Format("{0}1.[{1}]", prefix, columnname);
+                                    //else
+                                    fieldmap[columnname] = string.Format("{0}.[{1}]", tuple.Item2, string.Join(".", bits.Skip(combinecount)));
+                                    foreach (var sibling in siblings)
+                                    {
+                                        var subcol = string.Format("{0}.{1}", string.Join(".", bits.Take(combinecount)), sibling);
+                                        if (!subcol.Equals(columnname))
+                                            fieldmap[subcol] = string.Format("{0}.[{1}]", tuple.Item2, sibling);
+                                    }
+                                }
+                            }
                         }
                         else if (prop.PropertyType.GetInterfaces().Contains(typeof(IEnclosedEntity)))
                         {