From df5bbe89ec5c8bfe5046b6719b1ab929de2e7ea2 Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Thu, 18 Mar 2021 15:54:29 +0100 Subject: [PATCH] new document conventions builder --- zero.Core/Api/SetupApi.cs | 8 +- .../ZeroDocumentConventionsBuilder.cs | 156 ++++++++++++++++++ .../RavenDocumentStoreExtensions.cs | 109 ------------ zero.Web.UI/App/pages/media/item.vue | 2 +- zero.Web/ZeroBuilder.cs | 8 +- 5 files changed, 168 insertions(+), 115 deletions(-) create mode 100644 zero.Core/Database/ZeroDocumentConventionsBuilder.cs delete mode 100644 zero.Core/Extensions/RavenDocumentStoreExtensions.cs diff --git a/zero.Core/Api/SetupApi.cs b/zero.Core/Api/SetupApi.cs index 73aad742..ee3feac1 100644 --- a/zero.Core/Api/SetupApi.cs +++ b/zero.Core/Api/SetupApi.cs @@ -28,11 +28,14 @@ namespace zero.Core.Api protected IPasswordHasher PasswordHasher { get; private set; } + protected IZeroDocumentConventionsBuilder ConventionsBuilder { get; private set; } - public SetupApi(IZeroOptions options, IPasswordHasher passwordHasher) + + public SetupApi(IZeroOptions options, IPasswordHasher passwordHasher, IZeroDocumentConventionsBuilder conventionsBuilder) { Options = options; PasswordHasher = passwordHasher; + ConventionsBuilder = conventionsBuilder; } @@ -59,7 +62,8 @@ namespace zero.Core.Api Database = model.Database.Name }; - raven.Setup(Options).Initialize(); + ConventionsBuilder.Run(raven.Conventions); + raven.Initialize(); // create application Application app = Prepare(new Application() diff --git a/zero.Core/Database/ZeroDocumentConventionsBuilder.cs b/zero.Core/Database/ZeroDocumentConventionsBuilder.cs new file mode 100644 index 00000000..c8db9d93 --- /dev/null +++ b/zero.Core/Database/ZeroDocumentConventionsBuilder.cs @@ -0,0 +1,156 @@ +using Raven.Client.Documents.Conventions; +using Raven.Client.Json.Serialization; +using Raven.Client.Json.Serialization.NewtonsoftJson; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using zero.Core.Attributes; +using zero.Core.Entities; +using zero.Core.Extensions; +using zero.Core.Options; +using zero.Core.Utils; + +namespace zero.Core.Database +{ + public class ZeroDocumentConventionsBuilder : IZeroDocumentConventionsBuilder + { + protected HashSet PolymorphTypes { get; private set; } = new(); + + protected Type AcceptsZeroConventionsType { get; set; } = typeof(IZeroDbConventions); + + protected char IdentityPartsSeparator { get; set; } = '.'; + + protected IZeroOptions Options { get; private set; } + + protected static Dictionary CachedTypeCollectionNameMap = new(); + + + public ZeroDocumentConventionsBuilder(IZeroOptions options) + { + Options = options; + } + + + /// + public void Run(DocumentConventions conventions) + { + conventions.MaxNumberOfRequestsPerSession = 100; + conventions.IdentityPartsSeparator = IdentityPartsSeparator; + conventions.TransformTypeCollectionNameToDocumentIdPrefix = TransformTypeCollectionNameToDocumentIdPrefix; + conventions.FindCollectionName = FindCollectionName; + conventions.RegisterAsyncIdConvention((_, entity) => GetDocumentId(conventions, entity)); + + ConfigureJsonSerializer(conventions.Serialization); + } + + + /// + /// Get a document ID from an entity + /// + protected virtual Task GetDocumentId(DocumentConventions conventions, T entity) + { + string collection = conventions.GetCollectionName(entity); + + StringBuilder documentId = new(); + documentId.Append(conventions.TransformTypeCollectionNameToDocumentIdPrefix(collection)); + documentId.Append(IdentityPartsSeparator); + documentId.Append(IdGenerator.Create()); + + return Task.FromResult(documentId.ToString()); + } + + + /// + /// Finds the collection name for a certain type based on internal rules + /// + protected virtual string FindCollectionName(Type type) + { + string collection = null; + + Func cache = name => + { + CachedTypeCollectionNameMap.Add(type, name); + return name; + }; + + if (CachedTypeCollectionNameMap.TryGetValue(type, out collection)) + { + return collection; + } + + // do not alter non-internal entities + if (!AcceptsZeroConventionsType.IsAssignableFrom(type)) + { + return cache(DocumentConventions.DefaultGetCollectionName(type)); + } + + // use name from attribute if available + CollectionAttribute collectionAttribute = type.GetCustomAttribute(true); + + if (collectionAttribute != null) + { + return cache(collectionAttribute.Name); + } + + // use base interface if available + Type interfaceBaseType = type.GetInterfaces().FirstOrDefault(x => x.IsInterface && AcceptsZeroConventionsType.IsAssignableFrom(x) && x.Name != AcceptsZeroConventionsType.Name); + + if (interfaceBaseType != null) + { + // use name from attribute if available + collectionAttribute = interfaceBaseType.GetCustomAttribute(true); + if (collectionAttribute != null) + { + return cache(collectionAttribute.Name); + } + } + + // use base type for polymorphism + Type polymorphBaseType = PolymorphTypes.FirstOrDefault(x => type.IsSubclassOf(x)); + + if (polymorphBaseType != null) + { + return cache(DocumentConventions.DefaultGetCollectionName(polymorphBaseType)); + } + + return cache(DocumentConventions.DefaultGetCollectionName(type)); + } + + + /// + /// Configures the JSON serializer + deserializer for document retrieval from RavenDB + /// + protected virtual void ConfigureJsonSerializer(ISerializationConventions conventions) + { + NewtonsoftJsonSerializationConventions typedConventions = conventions as NewtonsoftJsonSerializationConventions; + typedConventions.CustomizeJsonDeserializer = x => x.Converters.Add(new RefJsonConverter()); + typedConventions.CustomizeJsonSerializer = x => x.Converters.Add(new RefJsonConverter()); + } + + + /// + /// Translates the types collection name to the document id prefix + /// + protected virtual string TransformTypeCollectionNameToDocumentIdPrefix(string name) + { + if (!Options.Raven.CollectionPrefix.IsNullOrWhiteSpace()) + { + name = Options.Raven.CollectionPrefix.EnsureEndsWith(IdentityPartsSeparator) + name.TrimStart(Options.Raven.CollectionPrefix); + } + + return name.ToCamelCaseId(); + } + } + + + public interface IZeroDocumentConventionsBuilder + { + /// + /// Applies internal rules to the RavenDB document conventions + /// + void Run(DocumentConventions conventions); + } +} diff --git a/zero.Core/Extensions/RavenDocumentStoreExtensions.cs b/zero.Core/Extensions/RavenDocumentStoreExtensions.cs deleted file mode 100644 index e73ba62e..00000000 --- a/zero.Core/Extensions/RavenDocumentStoreExtensions.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Conventions; -using Raven.Client.Documents.Operations.CompareExchange; -using Raven.Client.Json.Serialization.NewtonsoftJson; -using System; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using zero.Core.Attributes; -using zero.Core.Entities; -using zero.Core.Options; -using zero.Core.Utils; - -namespace zero.Core.Extensions -{ - public static class RavenDocumentStoreExtensions - { - const char DOT = '.'; - - /// - /// Setup conventions for the document store - /// - public static IDocumentStore Setup(this IDocumentStore store, IZeroOptions options) - { - Type[] polymorphTypes = new Type[2] { typeof(SpaceContent), typeof(Page) }; - - Type dbConventionType = typeof(IZeroDbConventions); - - store.Conventions.IdentityPartsSeparator = DOT; - - store.Conventions.RegisterAsyncIdConvention((_, entity) => - { - string guid = IdGenerator.Create((entity is IBackofficeUser or IBackofficeUserRole) ? 8 : -1); - string collection = store.Conventions.GetCollectionName(entity); - - string tag = store.Conventions.TransformTypeCollectionNameToDocumentIdPrefix(collection); - return Task.FromResult(tag + store.Conventions.IdentityPartsSeparator + guid); - }); - - store.Conventions.RegisterAsyncIdConvention((_, entity) => - { - return store.Conventions.AsyncDocumentIdGenerator(_, entity); - }); - - (store.Conventions.Serialization as NewtonsoftJsonSerializationConventions).CustomizeJsonDeserializer = x => - { - x.Converters.Add(new RefJsonConverter()); - }; - (store.Conventions.Serialization as NewtonsoftJsonSerializationConventions).CustomizeJsonSerializer = x => - { - x.Converters.Add(new RefJsonConverter()); - }; - - store.Conventions.FindCollectionName = type => - { - Type finalType = type; - - // do not alter non-internal entities - if (!dbConventionType.IsAssignableFrom(type)) - { - return DocumentConventions.DefaultGetCollectionName(type); - } - - // use name from attribute if available - CollectionAttribute collection = type.GetCustomAttribute(true); - if (collection != null) - { - return collection.Name; - } - - // use base interface if available - Type interfaceBaseType = type.GetInterfaces().FirstOrDefault(x => x.IsInterface && dbConventionType.IsAssignableFrom(x) && x.Name != dbConventionType.Name); - - if (interfaceBaseType != null) - { - // use name from attribute if available - collection = interfaceBaseType.GetCustomAttribute(true); - if (collection != null) - { - return options.Raven.CollectionPrefix + collection.Name; - } - } - - // use base type for polymorphism - Type polymorphBaseType = polymorphTypes.FirstOrDefault(x => type.IsSubclassOf(x)); - - if (polymorphBaseType != null) - { - finalType = polymorphBaseType; - } - - return options.Raven.CollectionPrefix + DocumentConventions.DefaultGetCollectionName(finalType); - }; - - - store.Conventions.TransformTypeCollectionNameToDocumentIdPrefix = name => - { - if (!options.Raven.CollectionPrefix.IsNullOrWhiteSpace()) - { - name = options.Raven.CollectionPrefix.EnsureEndsWith(store.Conventions.IdentityPartsSeparator) + name.TrimStart(options.Raven.CollectionPrefix); - } - - return name.ToCamelCaseId(); - }; - - return store; - } - } -} diff --git a/zero.Web.UI/App/pages/media/item.vue b/zero.Web.UI/App/pages/media/item.vue index 5297b88c..a35cd589 100644 --- a/zero.Web.UI/App/pages/media/item.vue +++ b/zero.Web.UI/App/pages/media/item.vue @@ -3,7 +3,7 @@
- +

{{value.name}} diff --git a/zero.Web/ZeroBuilder.cs b/zero.Web/ZeroBuilder.cs index e4bc2637..2433ea7a 100644 --- a/zero.Web/ZeroBuilder.cs +++ b/zero.Web/ZeroBuilder.cs @@ -143,19 +143,21 @@ namespace zero.Web /// void ConfigureDatabase() { - // add raven + Services.AddSingleton(); + Services.AddSingleton(context => { IZeroOptions options = context.GetService(); + IZeroDocumentConventionsBuilder conventionsBuilder = context.GetService(); IDocumentStore store = new ZeroStore(options) { Urls = new string[1] { options.Raven.Url } }; - store.Conventions.MaxNumberOfRequestsPerSession = 100; + conventionsBuilder.Run(store.Conventions); - IDocumentStore raven = store.Setup(options).Initialize(); + IDocumentStore raven = store.Initialize(); // create all indexes var assemblies = AssemblyDiscovery.Current.GetAssemblies().ToList();