Browse Source

Performance improvements to BaseObject GetValues() functions.

Kenric Nugteren 11 months ago
parent
commit
2ed926955e

+ 83 - 97
InABox.Core/BaseObject.cs

@@ -332,6 +332,7 @@ namespace InABox.Core
 
 
         private UserProperties _userproperties;
         private UserProperties _userproperties;
 
 
+        [DoNotPersist]
         public UserProperties UserProperties
         public UserProperties UserProperties
         {
         {
             get
             get
@@ -368,6 +369,35 @@ namespace InABox.Core
         #endregion
         #endregion
     }
     }
 
 
+    public class BaseObjectSnapshot<T>
+        where T : BaseObject
+    {
+        private List<(IProperty, object?)> Values;
+
+        private T Object;
+
+        public BaseObjectSnapshot(T obj)
+        {
+            Values = new List<(IProperty, object?)>();
+            foreach(var property in DatabaseSchema.Properties(obj.GetType()))
+            {
+                Values.Add((property, property.Getter()(obj)));
+            }
+            Object = obj;
+        }
+
+        public void ResetObject()
+        {
+            Object.CancelChanges();
+
+            var bObs = Object.IsObserving();
+            Object.SetObserving(false);
+            foreach(var (prop, value) in Values)
+                prop.Setter()(Object, value);
+            Object.SetObserving(bObs);
+        }
+    }
+
     public static class BaseObjectExtensions
     public static class BaseObjectExtensions
     {
     {
         public static bool HasColumn<T>(this T sender, string column)
         public static bool HasColumn<T>(this T sender, string column)
@@ -393,122 +423,78 @@ namespace InABox.Core
                 : default;
                 : default;
         }
         }
 
 
-        public static Dictionary<string, object> GetValues<T>(this T sender, bool all, bool followlinks = false) where T : BaseObject
+        public static Dictionary<string, object?> GetValues<T>(this T sender, bool all) where T : BaseObject
         {
         {
-            var result = new Dictionary<string, object>();
-            LoadValues(sender, result, "", all, false, followlinks);
-            return result;
-        }
+            var result = new Dictionary<string, object?>();
 
 
-        public static void SetValues<T>(this T sender, Dictionary<string, object> values)
-        {
-            foreach (var value in values)
-                CoreUtils.SetPropertyValue(sender, value.Key, value.Value);
-        }
-
-        public static Dictionary<string, object> GetOriginaValues<T>(this T sender, bool all) where T : BaseObject
-        {
-            var result = new Dictionary<string, object>();
-            LoadValues(sender, result, "", all, true);
-            return result;
-        }
-
-        private static void LoadValues(BaseObject sender, Dictionary<string, object?> values, string prefix, bool all, bool original, bool followlinks = false)
-        {
-            try
+            foreach(var property in DatabaseSchema.Properties(sender.GetType()))
             {
             {
-                var props = sender.GetType().GetProperties().Where(x =>
-                    x.GetCustomAttribute<DoNotSerialize>() == null
-                    && x.GetCustomAttribute<DoNotPersist>() == null
-                    && 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)))
-                    {
-                        var child = prop.GetValue(sender) as BaseObject;
-                        if (child != null)
-                            LoadValues(child, values, string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name, all, original, followlinks);
-                    }
-                    else if (prop.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)))
+                if (property.IsDBColumn)
+                {
+                    var isLocal = !property.HasParentEntityLink()
+                        || (property.Parent?.HasParentEntityLink() != true && property.Name.EndsWith(".ID"));
+                    if (isLocal)
                     {
                     {
-                        var child = prop.GetValue(sender) as BaseObject;
-                        if (child != null)
+                        if (all)
                         {
                         {
-                            if (all)
-                            {
-                                values[string.IsNullOrWhiteSpace(prefix) ? prop.Name + ".ID" : prefix + "." + prop.Name + ".ID"] =
-                                    original
-                                        ? child.OriginalValues.ContainsKey("ID") ? child.OriginalValues["ID"] : Guid.Empty
-                                        : CoreUtils.GetPropertyValue(child, "ID");
-                            }
-                            else
-                            {
-                                if (child.HasOriginalValue("ID"))
-                                    values[string.IsNullOrWhiteSpace(prefix) ? prop.Name + ".ID" : prefix + "." + prop.Name + ".ID"] =
-                                        original
-                                            ? child.OriginalValues.ContainsKey("ID") ? child.OriginalValues["ID"] : Guid.Empty
-                                            : CoreUtils.GetPropertyValue(child, "ID");
-                            }
-                            if (followlinks)
-                                LoadValues(child, values, string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name, all, original, followlinks);
+                            result[property.Name] = property.Getter()(sender);
                         }
                         }
-                    }
-                    else if (prop.PropertyType.GetInterfaces().Contains(typeof(IBaseObject)))
-                    {
-                        var child = prop.GetValue(sender) as IBaseObject;
-                        if (child != null)
+                        else
                         {
                         {
-                            if (all)
+                            if(property is StandardProperty stdProp)
                             {
                             {
-                                values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] = child;
+                                var parent = property.Parent != null ? property.Parent.Getter()(sender) as BaseObject : sender;
+                                if(parent?.HasOriginalValue(stdProp.Property.Name) == true)
+                                {
+                                    result[property.Name] = property.Getter()(sender);
+                                }
                             }
                             }
-                            else
+                            else if(property is CustomProperty customProp)
                             {
                             {
-                                if (child.IsChanged())
-                                    values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] = child;
+                                if (sender.HasOriginalValue(customProp.Name))
+                                {
+                                    result[property.Name] = property.Getter()(sender);
+                                }
                             }
                             }
                         }
                         }
                     }
                     }
-                    else
+                }
+            }
+
+            return result;
+        }
+
+        public static Dictionary<string, object?> GetOriginaValues<T>(this T sender) where T : BaseObject
+        {
+            var result = new Dictionary<string, object?>();
+
+            foreach(var property in DatabaseSchema.Properties(sender.GetType()))
+            {
+                if (property.IsDBColumn)
+                {
+                    var isLocal = !property.HasParentEntityLink()
+                        || (property.Parent?.HasParentEntityLink() != true && property.Name.EndsWith(".ID"));
+                    if (isLocal)
                     {
                     {
-                        if (all)
-                        {
-                            values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] =
-                                original
-                                    ? sender.OriginalValues.ContainsKey(prop.Name) ? sender.OriginalValues[prop.Name] : prop.PropertyType.GetDefault()
-                                    : prop.GetValue(sender);
-                        }
-                        else
+                        var parent = property.Parent != null ? property.Parent.Getter()(sender) as BaseObject : sender;
+                        var localPropName = property is StandardProperty stdProp ? stdProp.Property.Name : property.Name;
+                        if(parent?.OriginalValues.TryGetValue(localPropName, out var original) == true)
                         {
                         {
-                            if (sender.HasOriginalValue(prop.Name))
-                                values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] =
-                                    original
-                                        ? sender.OriginalValues.ContainsKey(prop.Name)
-                                            ? sender.OriginalValues[prop.Name]
-                                            : prop.PropertyType.GetDefault()
-                                        : prop.GetValue(sender);
+                            result[property.Name] = original;
                         }
                         }
                     }
                     }
-
-                var iprops = DatabaseSchema.Properties(sender.GetType()).Where(x => x is CustomProperty);
-                foreach (var iprop in iprops)
-                    if (all || sender.HasOriginalValue(iprop.Name))
-                        values[string.IsNullOrWhiteSpace(prefix) ? iprop.Name : prefix + "." + iprop.Name] =
-                            original
-                                ? sender.OriginalValues.ContainsKey(iprop.Name)
-                                    ? sender.OriginalValues[iprop.Name]
-                                    : iprop.PropertyType.GetDefault()
-                                : sender.UserProperties[iprop.Name];
-            }
-            catch (Exception e)
-            {
-                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
+                }
             }
             }
+
+            return result;
         }
         }
-        
+
+        public static BaseObjectSnapshot<T> TakeSnapshot<T>(this T obj)
+            where T : BaseObject
+        {
+            return new BaseObjectSnapshot<T>(obj);
+        }
+
         public static List<string> Compare<T>(this T sender, Dictionary<string, object> original) where T : BaseObject
         public static List<string> Compare<T>(this T sender, Dictionary<string, object> original) where T : BaseObject
         {
         {
             var result = new List<string>();
             var result = new List<string>();

+ 5 - 0
InABox.Core/DatabaseSchema/CustomProperty.cs

@@ -224,6 +224,11 @@ namespace InABox.Core
         [DoNotSerialize]
         [DoNotSerialize]
         public bool IsCalculated => false;
         public bool IsCalculated => false;
 
 
+        [NullEditor]
+        [DoNotPersist]
+        [DoNotSerialize]
+        public bool IsDBColumn => true;
+
         private static string? NonWhiteSpaceOrNull(string? name)
         private static string? NonWhiteSpaceOrNull(string? name)
         {
         {
             return string.IsNullOrWhiteSpace(name) ? null : name;
             return string.IsNullOrWhiteSpace(name) ? null : name;

+ 2 - 0
InABox.Core/DatabaseSchema/IProperty.cs

@@ -34,6 +34,8 @@ namespace InABox.Core
 
 
         bool IsCalculated { get; }
         bool IsCalculated { get; }
 
 
+        bool IsDBColumn { get; }
+
         /// <summary>
         /// <summary>
         /// An <see cref="IProperty"/> is required if it has the <see cref="RequiredColumnAttribute"/> defined on it.<br/>
         /// An <see cref="IProperty"/> is required if it has the <see cref="RequiredColumnAttribute"/> defined on it.<br/>
         /// If it is part of an <see cref="IEntityLink"/>, then it is only required if the <see cref="IEntityLink"/> property on the parent class
         /// If it is part of an <see cref="IEntityLink"/>, then it is only required if the <see cref="IEntityLink"/> property on the parent class

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

@@ -13,8 +13,6 @@ namespace InABox.Core
 
 
         private string _name = "";
         private string _name = "";
 
 
-        private bool? _calculated;
-
         private Action<object, object?> _setter;
         private Action<object, object?> _setter;
 
 
         private bool _checkedExpressions;
         private bool _checkedExpressions;
@@ -134,6 +132,7 @@ namespace InABox.Core
 
 
         public bool IsParent { get; set; }
         public bool IsParent { get; set; }
 
 
+        private bool? _calculated;
         public bool IsCalculated
         public bool IsCalculated
         {
         {
             get
             get
@@ -149,6 +148,21 @@ namespace InABox.Core
             }
             }
         }
         }
 
 
+        private bool? _dbColumn;
+        public bool IsDBColumn
+        {
+            get
+            {
+                if (!_dbColumn.HasValue)
+                {
+                    _dbColumn = !IsCalculated
+                        && Property.GetCustomAttribute<DoNotPersist>() == null
+                        && Property.CanWrite;
+                }
+                return _dbColumn.Value;
+            }
+        }
+
         public TAttribute? GetAttribute<TAttribute>() where TAttribute : Attribute
         public TAttribute? GetAttribute<TAttribute>() where TAttribute : Attribute
         {
         {
             return Property.GetCustomAttribute<TAttribute>();
             return Property.GetCustomAttribute<TAttribute>();

+ 2 - 2
InABox.Server/RPC/Handlers/Save.cs

@@ -24,7 +24,7 @@ namespace InABox.Rpc
 
 
             for(int i = 0; i < entities.Length; ++i)
             for(int i = 0; i < entities.Length; ++i)
             {
             {
-                originals[i] = BaseObjectExtensions.GetOriginaValues(entities[i], false);
+                originals[i] = BaseObjectExtensions.GetOriginaValues(entities[i]);
             }
             }
 
 
             var store = DbFactory.FindStore(parameters.Type, user.ID, user.UserID, session.Platform, session.Version ?? "");
             var store = DbFactory.FindStore(parameters.Type, user.ID, user.UserID, session.Platform, session.Version ?? "");
@@ -38,7 +38,7 @@ namespace InABox.Rpc
             {
             {
                 var preSaveChanges = originals[i];
                 var preSaveChanges = originals[i];
                 var item = entities[i];
                 var item = entities[i];
-                var currentChanges = item.GetOriginaValues(false);
+                var currentChanges = item.GetOriginaValues();
 
 
                 var delta = new Dictionary<string, object?>();
                 var delta = new Dictionary<string, object?>();
 
 

+ 4 - 4
InABox.Server/RestService.cs

@@ -314,7 +314,7 @@ namespace InABox.API
             {
             {
                 var e = request.Item;
                 var e = request.Item;
 
 
-                var preSaveChanges = BaseObjectExtensions.GetOriginaValues(e, false);
+                var preSaveChanges = BaseObjectExtensions.GetOriginaValues(e);
 
 
                 var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
                 var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
 
 
@@ -322,7 +322,7 @@ namespace InABox.API
 
 
                 if (request.ReturnOnlyChanged)
                 if (request.ReturnOnlyChanged)
                 {
                 {
-                    var currentChanges = BaseObjectExtensions.GetOriginaValues(e, false);
+                    var currentChanges = BaseObjectExtensions.GetOriginaValues(e);
 
 
                     foreach (var (key, value) in currentChanges)
                     foreach (var (key, value) in currentChanges)
                     {
                     {
@@ -385,7 +385,7 @@ namespace InABox.API
                 var es = request.Items;
                 var es = request.Items;
 
 
                 foreach (var e in es)
                 foreach (var e in es)
-                    originals[e] = BaseObjectExtensions.GetOriginaValues(e, false);
+                    originals[e] = BaseObjectExtensions.GetOriginaValues(e);
 
 
                 var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
                 var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
 
 
@@ -399,7 +399,7 @@ namespace InABox.API
 
 
                         var e = es[i];
                         var e = es[i];
 
 
-                        var currentChanges = BaseObjectExtensions.GetOriginaValues(e, false);
+                        var currentChanges = BaseObjectExtensions.GetOriginaValues(e);
 
 
                         var preSaveChanges = originals[e];
                         var preSaveChanges = originals[e];
 
 

+ 3 - 4
inabox.wpf/DynamicGrid/DynamicGrid.cs

@@ -1712,7 +1712,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
 
 
         if (items.Length != 0)
         if (items.Length != 0)
         {
         {
-            var snaps = items.Select(x => x.GetValues(true,true)).ToArray();
+            var snaps = items.ToArray(x => x.TakeSnapshot());
             if (EditItems(items))
             if (EditItems(items))
             {
             {
                 var sel = SelectedRows;
                 var sel = SelectedRows;
@@ -1725,10 +1725,9 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
             }
             }
             else
             else
             {
             {
-                for (int i = 0; i < items.Length; i++)
+                foreach(var snap in snaps)
                 {
                 {
-                    items[i].CancelChanges();
-                    items[i].SetValues(snaps[i]);
+                    snap.ResetObject();
                 }
                 }
             } 
             } 
             return false;
             return false;