From 1c937abfe3cc1cca6a3d22b0b4efdd08dd8431b9 Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Fri, 18 Sep 2020 01:06:21 +0200 Subject: [PATCH] START references + inheritance --- zero.Core/Entities/Ref.cs | 22 +++- zero.Core/Extensions/ObjectExtensions.cs | 3 +- .../RavenDocumentStoreExtensions.cs | 2 + .../Extensions/RavenQueryableExtensions.cs | 16 +-- zero.Core/Utils/ObjectTraverser.cs | 117 ++++++++++++++++-- zero.Core/Utils/RefsJsonConverter.cs | 64 ++++++++++ .../MigrationController.Blueprints.cs | 67 +++++++--- zero.Web/ZeroBuilder.cs | 5 +- 8 files changed, 255 insertions(+), 41 deletions(-) create mode 100644 zero.Core/Utils/RefsJsonConverter.cs diff --git a/zero.Core/Entities/Ref.cs b/zero.Core/Entities/Ref.cs index 9b99a944..9d9cc460 100644 --- a/zero.Core/Entities/Ref.cs +++ b/zero.Core/Entities/Ref.cs @@ -2,7 +2,21 @@ namespace zero.Core.Entities { - public class Ref where T : IZeroIdEntity + public class Ref : Ref where T : IZeroIdEntity + { + public Ref() : base() { } + public Ref(string id) : base(id) { } + } + + + public class Refs : Refs where T : IZeroIdEntity + { + public Refs() : base() { } + public Refs(params string[] ids) : base(ids) { } + } + + + public class Ref { public Ref() { } @@ -11,7 +25,7 @@ namespace zero.Core.Entities Id = id; } - public string Id { get; private set; } + public string Id { get; set; } public override string ToString() @@ -21,7 +35,7 @@ namespace zero.Core.Entities } - public class Refs where T : IZeroIdEntity + public class Refs { public Refs() { } @@ -30,6 +44,6 @@ namespace zero.Core.Entities Ids = ids; } - public string[] Ids { get; private set; } = new string[0] { }; + public string[] Ids { get; set; } = new string[0] { }; } } diff --git a/zero.Core/Extensions/ObjectExtensions.cs b/zero.Core/Extensions/ObjectExtensions.cs index 4ed05921..15216b09 100644 --- a/zero.Core/Extensions/ObjectExtensions.cs +++ b/zero.Core/Extensions/ObjectExtensions.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using System; +using zero.Core.Utils; namespace zero.Core.Extensions { @@ -20,7 +21,7 @@ namespace zero.Core.Extensions public static T Clone(this T obj) { Type type = obj.GetType(); - return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), type); + return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj, new RefJsonConverter(), new RefsJsonConverter()), type, new RefJsonConverter(), new RefsJsonConverter()); } } } diff --git a/zero.Core/Extensions/RavenDocumentStoreExtensions.cs b/zero.Core/Extensions/RavenDocumentStoreExtensions.cs index e5816c1b..58cdfb4d 100644 --- a/zero.Core/Extensions/RavenDocumentStoreExtensions.cs +++ b/zero.Core/Extensions/RavenDocumentStoreExtensions.cs @@ -29,10 +29,12 @@ namespace zero.Core.Extensions (store.Conventions.Serialization as NewtonsoftJsonSerializationConventions).CustomizeJsonDeserializer = x => { x.Converters.Add(new RefJsonConverter()); + x.Converters.Add(new RefsJsonConverter()); }; (store.Conventions.Serialization as NewtonsoftJsonSerializationConventions).CustomizeJsonSerializer = x => { x.Converters.Add(new RefJsonConverter()); + x.Converters.Add(new RefsJsonConverter()); }; store.Conventions.FindCollectionName = type => diff --git a/zero.Core/Extensions/RavenQueryableExtensions.cs b/zero.Core/Extensions/RavenQueryableExtensions.cs index 845dc01e..dc4e7e15 100644 --- a/zero.Core/Extensions/RavenQueryableExtensions.cs +++ b/zero.Core/Extensions/RavenQueryableExtensions.cs @@ -24,10 +24,10 @@ namespace zero.Core.Extensions HashSet ids = new HashSet(); ids.Add(appId); - if (includeShared) - { - ids.Add(Constants.Database.SharedAppId); - } + //if (includeShared) + //{ + // ids.Add(Constants.Database.SharedAppId); + //} return source.Where(item => (item as IAppAwareEntity).AppId.In(ids)); } @@ -48,10 +48,10 @@ namespace zero.Core.Extensions HashSet ids = new HashSet(); ids.Add(scope.AppId); - if (scope.IncludeShared) - { - ids.Add(Constants.Database.SharedAppId); - } + //if (scope.IncludeShared) + //{ + // ids.Add(Constants.Database.SharedAppId); + //} return source.Where(item => ((IAppAwareEntity)item).AppId.In(ids)); } diff --git a/zero.Core/Utils/ObjectTraverser.cs b/zero.Core/Utils/ObjectTraverser.cs index d669ba8b..15a42e4e 100644 --- a/zero.Core/Utils/ObjectTraverser.cs +++ b/zero.Core/Utils/ObjectTraverser.cs @@ -24,7 +24,21 @@ namespace zero.Core.Utils HashSet exploredObjects = new HashSet(); List> found = new List>(); - Find(value, String.Empty, exploredObjects, found); + Find(value, null, String.Empty, null, exploredObjects, found); + + return found; + } + + + /// + /// Find all instances of a given type in an object (features nested + enumerable content) + /// + public static List Find(Type type, object value) + { + HashSet exploredObjects = new HashSet(); + List found = new List(); + + Find(type, value, null, String.Empty, null, exploredObjects, found); return found; } @@ -47,7 +61,7 @@ namespace zero.Core.Utils /// /// Recursively ind all instances of a given type in an object /// - private static void Find(object value, string key, HashSet exploredObjects, List> found) where T : class + private static void Find(object value, object parent, string key, PropertyInfo property, HashSet exploredObjects, List> found) where T : class { if (value == null || value is string || exploredObjects.Contains(value) || value.GetType().FullName.StartsWith(NEWTONSOFT_NS)) { @@ -64,36 +78,102 @@ namespace zero.Core.Utils int idx = 0; foreach (object item in enumerable) { - Find(item, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), exploredObjects, found); + Find(item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), property, exploredObjects, found); idx += 1; } return; } + // get type of value + Type type = typeof(T); + Type valueType = value.GetType(); + Type genericValueType = valueType.IsGenericType ? valueType.GetGenericTypeDefinition() : null; + // check if the current property is our searched type - if (value is T) + if (valueType == type || genericValueType == type || type.IsAssignableFrom(valueType) || (genericValueType != null && type.IsAssignableFrom(genericValueType))) { found.Add(new Result() { Path = key, - Item = value as T + Item = value as T, + Parent = parent, + Property = property }); return; } // check if our property is a primitive type - Type type = value.GetType(); - if (type.IsPrimitive) + if (value == null || valueType.IsPrimitive || value is string || value is DateTime || value is DateTimeOffset || value is Uri) { return; } // iterate over nested properties - PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); - foreach (PropertyInfo property in properties) + PropertyInfo[] properties = valueType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); + foreach (PropertyInfo nestedProperty in properties) { - object propertyValue = property.GetValue(value, null); - Find(propertyValue, CombineKey(SEPARATOR, key, property.Name), exploredObjects, found); + object propertyValue = nestedProperty.GetValue(value, null); + Find(propertyValue, value, CombineKey(SEPARATOR, key, nestedProperty.Name), nestedProperty, exploredObjects, found); + } + } + + + /// + /// Recursively ind all instances of a given type in an object + /// + private static void Find(Type type, object value, object parent, string key, PropertyInfo property, HashSet exploredObjects, List found) + { + if (value == null || value is string || exploredObjects.Contains(value) || value.GetType().FullName.StartsWith(NEWTONSOFT_NS)) + { + return; + } + + exploredObjects.Add(value); + + System.Collections.ICollection enumerable = value as System.Collections.ICollection; + + // this property is a list + if (enumerable != null) + { + int idx = 0; + foreach (object item in enumerable) + { + Find(type, item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), property, exploredObjects, found); + idx += 1; + } + return; + } + + // get type of value + Type valueType = value.GetType(); + Type genericValueType = valueType.IsGenericType ? valueType.GetGenericTypeDefinition() : null; + + // check if the current property is our searched type + if (valueType == type || genericValueType == type || type.IsAssignableFrom(valueType) || (genericValueType != null && type.IsAssignableFrom(genericValueType))) + { + found.Add(new Result() + { + Path = key, + Item = value, + Type = valueType, + Parent = parent, + Property = property + }); + return; + } + + // check if our property is a primitive type + if (value == null || valueType.IsPrimitive || value is string || value is DateTime || value is DateTimeOffset || value is Uri) + { + return; + } + + // iterate over nested properties + PropertyInfo[] properties = valueType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); + foreach (PropertyInfo nestedProperty in properties) + { + object propertyValue = nestedProperty.GetValue(value, null); + Find(type, propertyValue, value, CombineKey(SEPARATOR, key, nestedProperty.Name), nestedProperty, exploredObjects, found); } } @@ -178,5 +258,20 @@ namespace zero.Core.Utils public T Item { get; set; } } + + public class Result + { + public string Path { get; set; } + + public PropertyInfo Property { get; set; } + + public object Parent { get; set; } + + public object Item { get; set; } + + public Type Type { get; set; } + + public int Index { get; set; } + } } } diff --git a/zero.Core/Utils/RefsJsonConverter.cs b/zero.Core/Utils/RefsJsonConverter.cs new file mode 100644 index 00000000..d8c22522 --- /dev/null +++ b/zero.Core/Utils/RefsJsonConverter.cs @@ -0,0 +1,64 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sparrow.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using zero.Core.Entities; +using zero.Core.Extensions; + +namespace zero.Core.Utils +{ + public class RefsJsonConverter : JsonConverter + { + private readonly Type type; + + private readonly string idProperty; + + public RefsJsonConverter() + { + type = typeof(Refs<>); + idProperty = nameof(Refs.Ids); + } + + + public override bool CanRead => true; + + public override bool CanWrite => true; + + public override bool CanConvert(Type objectType) => objectType.IsGenericType && objectType.GetGenericTypeDefinition() == type; + + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + JToken t = JToken.FromObject(value); + JArray idArray = t?.Value(idProperty); + string[] ids = idArray.Values().ToArray(); + + writer.WriteStartArray(); + + if (ids?.Length > 0) + { + foreach (string id in ids) + { + writer.WriteValue(id); + } + } + + writer.WriteEndArray(); + } + + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.StartArray) + { + JArray array = JArray.Load(reader); + string[] items = array.Select(x => x.Value()).ToArray(); + return Activator.CreateInstance(objectType, new object[1] { items }); + } + + return Activator.CreateInstance(objectType); + } + } +} diff --git a/zero.Debug/Controllers/MigrationController.Blueprints.cs b/zero.Debug/Controllers/MigrationController.Blueprints.cs index 8c49719a..e0bf4aac 100644 --- a/zero.Debug/Controllers/MigrationController.Blueprints.cs +++ b/zero.Debug/Controllers/MigrationController.Blueprints.cs @@ -29,7 +29,7 @@ namespace zero.Debug.Controllers apps = await session.Query().ToListAsync(); } - async Task Handle() where T : IZeroIdEntity + async Task Handle() where T : IZeroEntity { return await CreateBlueprintsForAll(apps); } @@ -64,7 +64,7 @@ namespace zero.Debug.Controllers } - async Task> CreateBlueprintsForAll(IList apps) where T : IZeroIdEntity + async Task> CreateBlueprintsForAll(IList apps) where T : IZeroEntity { HashSet ids = new HashSet(); IList items; @@ -87,32 +87,67 @@ namespace zero.Debug.Controllers { ((IZeroEntity)variant).BlueprintId = item.Id; - // find all references - List> references = ObjectTraverser.FindAttribute(variant); + //await UpdateReferences(variant); - foreach (ObjectTraverser.Result reference in references) - { - object value = reference.Property.GetValue(reference.Parent, null); - - //if (String.IsNullOrWhiteSpace(id) || id.StartsWith(NEW_ID)) - //{ - // item.Property.SetValue(item.Parent, item.Item.Length.HasValue ? Raven.Id(item.Item.Length.Value) : Raven.Id()); - //} - } - - //await session.StoreAsync(variant); + await session.StoreAsync(variant); ids.Add(variant.Id); } } } - //await session.SaveChangesAsync(); + await session.SaveChangesAsync(); + } + + using (IAsyncDocumentSession session = Raven.OpenAsyncSession()) + { + session.Advanced.MaxNumberOfRequestsPerSession = 10000; + + items = await session.Query().ToListAsync(); //.Where(x => x.BlueprintId != null).ToListAsync(); + + foreach (T item in items) + { + await UpdateReferences(item, session); + + ids.Add(item.Id); + } + + await session.SaveChangesAsync(); } return ids; } + async Task UpdateReferences(IZeroIdEntity entity, IAsyncDocumentSession session) + { + await Task.Delay(1); + + List> references = ObjectTraverser.Find(entity); + + foreach (ObjectTraverser.Result reference in references) + { + if (reference.Item != null && reference.Item.Ids.Length > 0) + { + Type type = reference.Item.GetType(); + Type generic = type.GetGenericArguments().FirstOrDefault(); + + string collection = session.Advanced.DocumentStore.Conventions.FindCollectionName(generic); + + for (int idx = 0; idx < reference.Item.Ids.Length; idx++) + { + string id = reference.Item.Ids[idx]; + IZeroEntity xentity = await session.Query(collectionName: collection).FirstOrDefaultAsync(x => x.BlueprintId == id); + + if (xentity != null) + { + reference.Item.Ids[idx] = xentity.Id; + } + } + } + } + } + + HashSet ExtractBlueprints(T model, IList apps) where T : IZeroIdEntity { HashSet newModels = new HashSet(); diff --git a/zero.Web/ZeroBuilder.cs b/zero.Web/ZeroBuilder.cs index 4dcf5a41..ecc55ea5 100644 --- a/zero.Web/ZeroBuilder.cs +++ b/zero.Web/ZeroBuilder.cs @@ -86,16 +86,19 @@ namespace zero.Web // TODO this shall only be configurated for backoffice controllers x.SerializerSettings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy())); x.SerializerSettings.Converters.Add(new RefJsonConverter()); + x.SerializerSettings.Converters.Add(new RefsJsonConverter()); x.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; x.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects; + + //JsonConvert.DefaultSettings = () => x.SerializerSettings; }); + if (Environment.GetEnvironmentVariable("DOTNET_WATCH") == "1") { Mvc.AddRazorRuntimeCompilation(); } - // configure Raven + Identity ConfigureDatabase(); ConfigureIdentity();