diff --git a/src/MongoFramework/Infrastructure/Commands/AddToBucketCommand.cs b/src/MongoFramework/Infrastructure/Commands/AddToBucketCommand.cs index cbfebab3..192c34b3 100644 --- a/src/MongoFramework/Infrastructure/Commands/AddToBucketCommand.cs +++ b/src/MongoFramework/Infrastructure/Commands/AddToBucketCommand.cs @@ -11,12 +11,12 @@ public class AddToBucketCommand : IWriteCommand typeof(EntityBucket); - public AddToBucketCommand(TGroup group, TSubEntity subEntity, IEntityProperty entityTimeProperty, int bucketSize) + public AddToBucketCommand(TGroup group, TSubEntity subEntity, IEntityPropertyDefinition entityTimeProperty, int bucketSize) { Group = group; SubEntity = subEntity; @@ -42,7 +42,7 @@ public IEnumerable>> GetModel(WriteM .Min(b => b.Min, itemTimeValue) .Max(b => b.Max, itemTimeValue) .SetOnInsert(b => b.BucketSize, BucketSize) - .SetOnInsert(b => b.Id, entityDefinition.KeyGenerator.Generate()); + .SetOnInsert(b => b.Id, entityDefinition.Key.KeyGenerator.Generate()); yield return new UpdateOneModel>(filter, updateDefinition) { diff --git a/src/MongoFramework/Infrastructure/DriverAbstractionRules.cs b/src/MongoFramework/Infrastructure/DriverAbstractionRules.cs new file mode 100644 index 00000000..e5ea0623 --- /dev/null +++ b/src/MongoFramework/Infrastructure/DriverAbstractionRules.cs @@ -0,0 +1,38 @@ +using System; +using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Bson.Serialization; +using MongoDB.Bson; +using System.Diagnostics; +using MongoFramework.Infrastructure.Serialization; + +namespace MongoFramework.Infrastructure; + +/// +/// Provides a single entry point to configure common areas of the driver for MongoFramework +/// +internal static class DriverAbstractionRules +{ + public static void ApplyRules() + { + RegisterSerializer(new DecimalSerializer(BsonType.Decimal128)); + RegisterSerializer(new NullableSerializer(new DecimalSerializer(BsonType.Decimal128))); + + BsonSerializer.RegisterSerializationProvider(TypeDiscoverySerializationProvider.Instance); + } + + private static void RegisterSerializer(IBsonSerializer serializer) + { + try + { + BsonSerializer.RegisterSerializer(typeof(TTarget), serializer); + } + catch (BsonSerializationException ex) when (ex.Message.Contains("already a serializer registered")) + { + // Already registered + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message, "MongoFramework"); + } + } +} diff --git a/src/MongoFramework/Infrastructure/Indexing/IndexModelBuilder.cs b/src/MongoFramework/Infrastructure/Indexing/IndexModelBuilder.cs index b8c59752..421ef488 100644 --- a/src/MongoFramework/Infrastructure/Indexing/IndexModelBuilder.cs +++ b/src/MongoFramework/Infrastructure/Indexing/IndexModelBuilder.cs @@ -43,23 +43,23 @@ public static IEnumerable> BuildModel() } } - private static CreateIndexModel CreateIndexModel(IEntityIndex indexDefinition) + private static CreateIndexModel CreateIndexModel(IEntityIndexDefinition indexDefinition) { var builder = Builders.IndexKeys; IndexKeysDefinition keyModel; if (indexDefinition.IndexType == IndexType.Text) { - keyModel = builder.Text(indexDefinition.Property.FullPath); + keyModel = builder.Text(indexDefinition.Path); } else if (indexDefinition.IndexType == IndexType.Geo2dSphere) { - keyModel = builder.Geo2DSphere(indexDefinition.Property.FullPath); + keyModel = builder.Geo2DSphere(indexDefinition.Path); } else { keyModel = indexDefinition.SortOrder == IndexSortOrder.Ascending ? - builder.Ascending(indexDefinition.Property.FullPath) : builder.Descending(indexDefinition.Property.FullPath); + builder.Ascending(indexDefinition.Path) : builder.Descending(indexDefinition.Path); } if (indexDefinition.IsTenantExclusive && typeof(IHaveTenantId).IsAssignableFrom(typeof(TEntity))) diff --git a/src/MongoFramework/Infrastructure/Internal/TypeExtensions.cs b/src/MongoFramework/Infrastructure/Internal/TypeExtensions.cs index d2f38cfc..901ad720 100644 --- a/src/MongoFramework/Infrastructure/Internal/TypeExtensions.cs +++ b/src/MongoFramework/Infrastructure/Internal/TypeExtensions.cs @@ -1,35 +1,45 @@ using System; using System.Collections.Generic; -using System.Linq; -namespace MongoFramework.Infrastructure.Internal +namespace MongoFramework.Infrastructure.Internal; + +internal static class TypeExtensions { - internal static class TypeExtensions + private static readonly HashSet CommonGenericEnumerables = new() + { + typeof(IEnumerable<>), + typeof(IList<>), + typeof(ICollection<>), + typeof(IReadOnlyList<>), + typeof(IReadOnlyCollection<>) + }; + + public static Type GetEnumerableItemTypeOrDefault(this Type type) { - public static Type GetEnumerableItemTypeOrDefault(this Type type) + if (type.IsArray) + { + return type.GetElementType(); + } + else if (type.IsGenericType) { - if (type.IsArray) + if (CommonGenericEnumerables.Contains(type.GetGenericTypeDefinition())) { - return type.GetElementType(); + return type.GetGenericArguments()[0]; } - else if (type.IsGenericType) + else { - if (type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + //Unlike when the type is directly a known generic enumerable interface, if we start making assumptions + //like that on the interfaces of the type, we can hit edge cases where a type implements multiple interfaces. + foreach (var interfaceType in type.GetInterfaces()) { - return type.GetGenericArguments()[0]; - } - else - { - var compatibleInterfaces = type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); - var targetInterface = compatibleInterfaces.FirstOrDefault(); - if (targetInterface != null) + if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { return type.GetGenericArguments()[0]; } } } - - return type; } + + return type; } } diff --git a/src/MongoFramework/Infrastructure/Mapping/DefaultMappingPack.cs b/src/MongoFramework/Infrastructure/Mapping/DefaultMappingPack.cs index b786320a..b7e7f0e7 100644 --- a/src/MongoFramework/Infrastructure/Mapping/DefaultMappingPack.cs +++ b/src/MongoFramework/Infrastructure/Mapping/DefaultMappingPack.cs @@ -13,8 +13,6 @@ public static class DefaultProcessors new EntityIdProcessor(), new NestedTypeProcessor(), new ExtraElementsProcessor(), - new DecimalSerializationProcessor(), - new TypeDiscoveryProcessor(), new BsonKnownTypesProcessor(), new IndexProcessor(), new MappingAdapterProcessor() diff --git a/src/MongoFramework/Infrastructure/Mapping/DriverMappingInterop.cs b/src/MongoFramework/Infrastructure/Mapping/DriverMappingInterop.cs new file mode 100644 index 00000000..d24dc063 --- /dev/null +++ b/src/MongoFramework/Infrastructure/Mapping/DriverMappingInterop.cs @@ -0,0 +1,64 @@ +using MongoDB.Bson.Serialization; + +namespace MongoFramework.Infrastructure.Mapping; + +/// +/// Maps the MongoFramework definition into something the MongoDB Driver will understand +/// +internal static class DriverMappingInterop +{ + /// + /// Registers the as a with all appropriate properties configured. + /// + /// + public static void RegisterDefinition(IEntityDefinition definition) + { + var classMap = new BsonClassMap(definition.EntityType); + + // Hierarchy + if (!EntityMapping.IsValidTypeToMap(definition.EntityType.BaseType)) + { + classMap.SetIsRootClass(true); + } + + // Properties + foreach (var property in definition.Properties) + { + var memberMap = classMap.MapMember(property.PropertyInfo); + memberMap.SetElementName(property.ElementName); + } + + // Key / ID + if (definition.Key is not null) + { + var idMemberMap = classMap.MapIdMember(definition.Key.Property.PropertyInfo); + idMemberMap.SetIdGenerator(new DriverKeyGeneratorWrapper(definition.Key.KeyGenerator)); + } + + // Extra Elements + if (definition.ExtraElements is not null) + { + if (definition.ExtraElements.IgnoreExtraElements) + { + classMap.SetIgnoreExtraElements(true); + classMap.SetIgnoreExtraElementsIsInherited(definition.ExtraElements.IgnoreInherited); + } + else + { + classMap.SetIgnoreExtraElements(false); + + var extraElementsProperty = definition.ExtraElements.Property; + foreach (var memberMap in classMap.DeclaredMemberMaps) + { + if (memberMap.ElementName == extraElementsProperty.ElementName) + { + classMap.SetExtraElementsMember(memberMap); + break; + } + } + } + } + + BsonClassMap.RegisterClassMap(classMap); + } +} diff --git a/src/MongoFramework/Infrastructure/Mapping/EntityDefinition.cs b/src/MongoFramework/Infrastructure/Mapping/EntityDefinition.cs index 5c979cd5..8a040373 100644 --- a/src/MongoFramework/Infrastructure/Mapping/EntityDefinition.cs +++ b/src/MongoFramework/Infrastructure/Mapping/EntityDefinition.cs @@ -1,15 +1,107 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; -namespace MongoFramework.Infrastructure.Mapping +namespace MongoFramework.Infrastructure.Mapping; + +public interface IEntityDefinition +{ + public Type EntityType { get; set; } + public string CollectionName { get; set; } + public IEntityKeyDefinition Key { get; set; } + public IEnumerable Properties { get; set; } + public IEnumerable Indexes { get; set; } + public IEntityExtraElementsDefinition ExtraElements { get; set; } +} + +public interface IEntityPropertyDefinition +{ + public IEntityDefinition EntityDefinition { get; } + public string ElementName { get; } + public PropertyInfo PropertyInfo { get; } + + public object GetValue(object entity); + public void SetValue(object entity, object value); +} + +public interface IEntityIndexDefinition +{ + public IReadOnlyCollection Properties { get; } + [Obsolete("Index definition can point to multiple properties directly")] + public IEntityPropertyDefinition Property { get; } + //TODO: This will be made redundant when the broader change to support fluent comes in + public string Path { get; } + public string IndexName { get; } + public bool IsUnique { get; } + public IndexSortOrder SortOrder { get; } + public int IndexPriority { get; } + public IndexType IndexType { get; } + public bool IsTenantExclusive { get; } +} + +public interface IEntityExtraElementsDefinition +{ + public IEntityPropertyDefinition Property { get; } + public bool IgnoreExtraElements { get; } + public bool IgnoreInherited { get; } +} + +public interface IEntityKeyDefinition +{ + public IEntityPropertyDefinition Property { get; } + public IEntityKeyGenerator KeyGenerator { get; } +} + +public class EntityDefinition : IEntityDefinition +{ + public Type EntityType { get; set; } + public string CollectionName { get; set; } + public IEntityKeyDefinition Key { get; set; } + public IEnumerable Properties { get; set; } = Enumerable.Empty(); + public IEnumerable Indexes { get; set; } = Enumerable.Empty(); + public IEntityExtraElementsDefinition ExtraElements { get; set; } +} + +public class EntityPropertyDefinition : IEntityPropertyDefinition { - public class EntityDefinition : IEntityDefinition + public IEntityDefinition EntityDefinition { get; set; } + public string ElementName { get; set; } + public PropertyInfo PropertyInfo { get; set; } + + public object GetValue(object entity) + { + return PropertyInfo.GetValue(entity); + } + + public void SetValue(object entity, object value) { - public Type EntityType { get; set; } - public string CollectionName { get; set; } - public IEntityKeyGenerator KeyGenerator { get; set; } - public IEnumerable Properties { get; set; } = Enumerable.Empty(); - public IEnumerable Indexes { get; set; } = Enumerable.Empty(); + PropertyInfo.SetValue(entity, value); } } + +public class EntityIndexDefinition : IEntityIndexDefinition +{ + public IReadOnlyCollection Properties { get; set; } + public IEntityPropertyDefinition Property { get; set; } + public string Path { get; set; } + public string IndexName { get; set; } + public bool IsUnique { get; set; } + public IndexSortOrder SortOrder { get; set; } + public int IndexPriority { get; set; } + public IndexType IndexType { get; set; } + public bool IsTenantExclusive { get; set; } +} + +public sealed record EntityKeyDefinition : IEntityKeyDefinition +{ + public IEntityPropertyDefinition Property { get; init; } + public IEntityKeyGenerator KeyGenerator { get; init; } +} + +public sealed record EntityExtraElementsDefinition : IEntityExtraElementsDefinition +{ + public IEntityPropertyDefinition Property { get; init; } + public bool IgnoreExtraElements { get; init; } + public bool IgnoreInherited { get; init; } +} \ No newline at end of file diff --git a/src/MongoFramework/Infrastructure/Mapping/EntityDefinitionExtensions.cs b/src/MongoFramework/Infrastructure/Mapping/EntityDefinitionExtensions.cs index 3bb0b60c..dc285ce4 100644 --- a/src/MongoFramework/Infrastructure/Mapping/EntityDefinitionExtensions.cs +++ b/src/MongoFramework/Infrastructure/Mapping/EntityDefinitionExtensions.cs @@ -1,15 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; -using MongoFramework.Infrastructure.Internal; namespace MongoFramework.Infrastructure.Mapping { public static class EntityDefinitionExtensions { - public static IEntityProperty GetIdProperty(this IEntityDefinition definition) + public static IEntityPropertyDefinition GetIdProperty(this IEntityDefinition definition) { - return definition.GetAllProperties().FirstOrDefault(m => m.IsKey); + if (definition.Key is null) + { + return EntityMapping.GetOrCreateDefinition(definition.EntityType.BaseType).GetIdProperty(); + } + + return definition.Key?.Property; } public static string GetIdName(this IEntityDefinition definition) @@ -24,7 +28,7 @@ public static object GetIdValue(this IEntityDefinition definition, object entity public static object GetDefaultId(this IEntityDefinition definition) { - var idPropertyType = definition.GetIdProperty()?.PropertyType; + var idPropertyType = definition.GetIdProperty()?.PropertyInfo.PropertyType; if (idPropertyType is { IsValueType: true }) { return Activator.CreateInstance(idPropertyType); @@ -32,7 +36,7 @@ public static object GetDefaultId(this IEntityDefinition definition) return null; } - public static IEnumerable GetInheritedProperties(this IEntityDefinition definition) + public static IEnumerable GetInheritedProperties(this IEntityDefinition definition) { var currentType = definition.EntityType.BaseType; while (currentType != typeof(object) && currentType != null) @@ -47,7 +51,7 @@ public static IEnumerable GetInheritedProperties(this IEntityDe } } - public static IEnumerable GetAllProperties(this IEntityDefinition definition) + public static IEnumerable GetAllProperties(this IEntityDefinition definition) { foreach (var property in definition.Properties) { @@ -60,7 +64,7 @@ public static IEnumerable GetAllProperties(this IEntityDefiniti } } - public static IEntityProperty GetProperty(this IEntityDefinition definition, string name) + public static IEntityPropertyDefinition GetProperty(this IEntityDefinition definition, string name) { foreach (var property in definition.GetAllProperties()) { @@ -72,57 +76,5 @@ public static IEntityProperty GetProperty(this IEntityDefinition definition, str return default; } - - private sealed class TraversalState - { - public HashSet SeenTypes { get; set; } - public IEnumerable Properties { get; set; } - } - - public static IEnumerable TraverseProperties(this IEntityDefinition definition) - { - var stack = new Stack(); - stack.Push(new TraversalState - { - SeenTypes = new HashSet { definition.EntityType }, - Properties = definition.GetAllProperties() - }); - - while (stack.Count > 0) - { - var state = stack.Pop(); - foreach (var property in state.Properties) - { - yield return property; - - var propertyType = property.PropertyType; - propertyType = propertyType.GetEnumerableItemTypeOrDefault(); - - if (EntityMapping.IsValidTypeToMap(propertyType) && !state.SeenTypes.Contains(propertyType)) - { - var nestedProperties = EntityMapping.GetOrCreateDefinition(propertyType) - .GetAllProperties() - .Select(p => new EntityProperty - { - EntityType = p.EntityType, - IsKey = p.IsKey, - ElementName = p.ElementName, - FullPath = $"{property.FullPath}.{p.ElementName}", - PropertyType = p.PropertyType, - PropertyInfo = p.PropertyInfo - }); - - stack.Push(new TraversalState - { - SeenTypes = new HashSet(state.SeenTypes) - { - propertyType - }, - Properties = nestedProperties - }); - } - } - } - } } } diff --git a/src/MongoFramework/Infrastructure/Mapping/EntityIndex.cs b/src/MongoFramework/Infrastructure/Mapping/EntityIndex.cs deleted file mode 100644 index b3bea7dd..00000000 --- a/src/MongoFramework/Infrastructure/Mapping/EntityIndex.cs +++ /dev/null @@ -1,14 +0,0 @@ - -namespace MongoFramework.Infrastructure.Mapping -{ - public class EntityIndex : IEntityIndex - { - public IEntityProperty Property { get; set; } - public string IndexName { get; set; } - public bool IsUnique { get; set; } - public IndexSortOrder SortOrder { get; set; } - public int IndexPriority { get; set; } - public IndexType IndexType { get; set; } - public bool IsTenantExclusive { get; set; } - } -} diff --git a/src/MongoFramework/Infrastructure/Mapping/EntityKeyGenerator.cs b/src/MongoFramework/Infrastructure/Mapping/EntityKeyGenerator.cs index f54bef45..32395f91 100644 --- a/src/MongoFramework/Infrastructure/Mapping/EntityKeyGenerator.cs +++ b/src/MongoFramework/Infrastructure/Mapping/EntityKeyGenerator.cs @@ -1,19 +1,51 @@ using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.IdGenerators; -namespace MongoFramework.Infrastructure.Mapping +namespace MongoFramework.Infrastructure.Mapping; + +public interface IEntityKeyGenerator +{ + public object Generate(); + public bool IsEmpty(object id); +} + +public static class EntityKeyGenerators +{ + public static readonly IEntityKeyGenerator StringKeyGenerator = new EntityKeyGenerator(StringObjectIdGenerator.Instance); + public static readonly IEntityKeyGenerator GuidKeyGenerator = new EntityKeyGenerator(CombGuidGenerator.Instance); + public static readonly IEntityKeyGenerator ObjectIdKeyGenerator = new EntityKeyGenerator(ObjectIdGenerator.Instance); +} + +internal sealed class EntityKeyGenerator : IEntityKeyGenerator { - internal class EntityKeyGenerator : IEntityKeyGenerator + private readonly IIdGenerator idGenerator; + + public EntityKeyGenerator(IIdGenerator idGenerator) + { + this.idGenerator = idGenerator; + } + + public object Generate() { - private IIdGenerator IdGenerator { get; } + return idGenerator.GenerateId(null, null); + } + + public bool IsEmpty(object id) + { + return idGenerator.IsEmpty(id); + } +} - public EntityKeyGenerator(IIdGenerator idGenerator) - { - IdGenerator = idGenerator; - } +internal sealed class DriverKeyGeneratorWrapper : IIdGenerator +{ + private readonly IEntityKeyGenerator entityKeyGenerator; - public object Generate() - { - return IdGenerator.GenerateId(null, null); - } + public DriverKeyGeneratorWrapper(IEntityKeyGenerator entityKeyGenerator) + { + this.entityKeyGenerator = entityKeyGenerator; } + + public object GenerateId(object container, object document) => entityKeyGenerator.Generate(); + + public bool IsEmpty(object id) => entityKeyGenerator.IsEmpty(id); } diff --git a/src/MongoFramework/Infrastructure/Mapping/EntityMapping.cs b/src/MongoFramework/Infrastructure/Mapping/EntityMapping.cs index b1017bda..2f31a2ed 100644 --- a/src/MongoFramework/Infrastructure/Mapping/EntityMapping.cs +++ b/src/MongoFramework/Infrastructure/Mapping/EntityMapping.cs @@ -17,6 +17,8 @@ public static class EntityMapping static EntityMapping() { + DriverAbstractionRules.ApplyRules(); + EntityDefinitions = new ConcurrentDictionary(); MappingProcessors = new List(); @@ -92,21 +94,19 @@ public static IEntityDefinition RegisterType(Type entityType) //We will treat success of this check as if we have registered it just now return definition; } - - var classMap = new BsonClassMap(entityType); definition = new EntityDefinition { EntityType = entityType }; EntityDefinitions.TryAdd(entityType, definition); - BsonClassMap.RegisterClassMap(classMap); foreach (var processor in MappingProcessors) { - processor.ApplyMapping(definition, classMap); + processor.ApplyMapping(definition); } + DriverMappingInterop.RegisterDefinition(definition); return definition; } finally @@ -165,20 +165,19 @@ public static bool TryRegisterType(Type entityType, out IEntityDefinition defini return true; } - var classMap = new BsonClassMap(entityType); definition = new EntityDefinition { EntityType = entityType }; EntityDefinitions.TryAdd(entityType, definition); - BsonClassMap.RegisterClassMap(classMap); foreach (var processor in MappingProcessors) { - processor.ApplyMapping(definition, classMap); + processor.ApplyMapping(definition); } + DriverMappingInterop.RegisterDefinition(definition); return true; } finally diff --git a/src/MongoFramework/Infrastructure/Mapping/EntityProperty.cs b/src/MongoFramework/Infrastructure/Mapping/EntityProperty.cs deleted file mode 100644 index cb52bbcb..00000000 --- a/src/MongoFramework/Infrastructure/Mapping/EntityProperty.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Reflection; - -namespace MongoFramework.Infrastructure.Mapping -{ - public class EntityProperty : IEntityProperty - { - public Type EntityType { get; set; } - public bool IsKey { get; set; } - public string ElementName { get; set; } - public string FullPath { get; set; } - public Type PropertyType { get; set; } - public PropertyInfo PropertyInfo { get; set; } - - public object GetValue(object entity) - { - return PropertyInfo.GetValue(entity); - } - - public void SetValue(object entity, object value) - { - PropertyInfo.SetValue(entity, value); - } - } -} diff --git a/src/MongoFramework/Infrastructure/Mapping/IEntityDefinition.cs b/src/MongoFramework/Infrastructure/Mapping/IEntityDefinition.cs deleted file mode 100644 index 0cb6347d..00000000 --- a/src/MongoFramework/Infrastructure/Mapping/IEntityDefinition.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MongoFramework.Infrastructure.Mapping -{ - public interface IEntityDefinition - { - Type EntityType { get; set; } - string CollectionName { get; set; } - IEntityKeyGenerator KeyGenerator { get; set; } - IEnumerable Properties { get; set; } - IEnumerable Indexes { get; set; } - } -} diff --git a/src/MongoFramework/Infrastructure/Mapping/IEntityIndex.cs b/src/MongoFramework/Infrastructure/Mapping/IEntityIndex.cs deleted file mode 100644 index e8834bf9..00000000 --- a/src/MongoFramework/Infrastructure/Mapping/IEntityIndex.cs +++ /dev/null @@ -1,14 +0,0 @@ - -namespace MongoFramework.Infrastructure.Mapping -{ - public interface IEntityIndex - { - IEntityProperty Property { get; } - string IndexName { get; } - bool IsUnique { get; } - IndexSortOrder SortOrder { get; } - int IndexPriority { get; } - IndexType IndexType { get; } - bool IsTenantExclusive { get; set; } - } -} diff --git a/src/MongoFramework/Infrastructure/Mapping/IEntityKeyGenerator.cs b/src/MongoFramework/Infrastructure/Mapping/IEntityKeyGenerator.cs deleted file mode 100644 index ee712aac..00000000 --- a/src/MongoFramework/Infrastructure/Mapping/IEntityKeyGenerator.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MongoFramework.Infrastructure.Mapping -{ - public interface IEntityKeyGenerator - { - object Generate(); - } -} \ No newline at end of file diff --git a/src/MongoFramework/Infrastructure/Mapping/IEntityProperty.cs b/src/MongoFramework/Infrastructure/Mapping/IEntityProperty.cs deleted file mode 100644 index 8c6bfecd..00000000 --- a/src/MongoFramework/Infrastructure/Mapping/IEntityProperty.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Reflection; - -namespace MongoFramework.Infrastructure.Mapping -{ - public interface IEntityProperty - { - Type EntityType { get; } - bool IsKey { get; } - string ElementName { get; } - string FullPath { get; } - Type PropertyType { get; } - PropertyInfo PropertyInfo { get; } - - object GetValue(object entity); - void SetValue(object entity, object value); - } -} diff --git a/src/MongoFramework/Infrastructure/Mapping/IMappingProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/IMappingProcessor.cs index 4d8b48df..d7aab265 100644 --- a/src/MongoFramework/Infrastructure/Mapping/IMappingProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/IMappingProcessor.cs @@ -4,6 +4,6 @@ namespace MongoFramework.Infrastructure.Mapping { public interface IMappingProcessor { - void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap); + void ApplyMapping(IEntityDefinition definition); } } diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/BsonKnownTypesProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/BsonKnownTypesProcessor.cs index da2d40fe..bd0940e8 100644 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/BsonKnownTypesProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/Processors/BsonKnownTypesProcessor.cs @@ -6,7 +6,7 @@ namespace MongoFramework.Infrastructure.Mapping.Processors { public class BsonKnownTypesProcessor : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { var entityType = definition.EntityType; var bsonKnownTypesAttribute = entityType.GetCustomAttribute(); diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/CollectionNameProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/CollectionNameProcessor.cs index ad4b210d..5af71ea6 100644 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/CollectionNameProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/Processors/CollectionNameProcessor.cs @@ -6,7 +6,7 @@ namespace MongoFramework.Infrastructure.Mapping.Processors { public class CollectionNameProcessor : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { var entityType = definition.EntityType; var collectionName = entityType.Name; diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/DecimalSerializationProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/DecimalSerializationProcessor.cs deleted file mode 100644 index 7b6149ae..00000000 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/DecimalSerializationProcessor.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MongoDB.Bson; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Serializers; - -namespace MongoFramework.Infrastructure.Mapping.Processors -{ - public class DecimalSerializationProcessor : IMappingProcessor - { - private bool SerializerAdded { get; set; } - - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) - { - if (!SerializerAdded) - { - SerializerAdded = true; - BsonSerializer.RegisterSerializer(typeof(decimal), new DecimalSerializer(BsonType.Decimal128)); - BsonSerializer.RegisterSerializer(typeof(decimal?), new NullableSerializer(new DecimalSerializer(BsonType.Decimal128))); - } - } - } -} diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/EntityIdProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/EntityIdProcessor.cs index 5c04a52b..db0d334d 100644 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/EntityIdProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/Processors/EntityIdProcessor.cs @@ -3,15 +3,15 @@ using System.Reflection; using MongoDB.Bson; using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.IdGenerators; namespace MongoFramework.Infrastructure.Mapping.Processors { public class EntityIdProcessor : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { - IEntityProperty idProperty = default; + var keyDefinition = definition.Key; + var idProperty = keyDefinition?.Property; foreach (var property in definition.Properties) { if (property.PropertyInfo.GetCustomAttribute() != null) @@ -28,29 +28,30 @@ public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) } } - if (idProperty is EntityProperty entityProperty) + if (idProperty is EntityPropertyDefinition entityProperty) { - classMap.MapIdMember(idProperty.PropertyInfo); - entityProperty.IsKey = true; + var keyGenerator = keyDefinition?.KeyGenerator; //Set an Id Generator based on the member type - var idMemberMap = classMap.IdMemberMap; - var memberType = BsonClassMap.GetMemberInfoType(idMemberMap.MemberInfo); + var memberType = entityProperty.PropertyInfo.PropertyType; if (memberType == typeof(string)) { - idMemberMap.SetIdGenerator(StringObjectIdGenerator.Instance); - definition.KeyGenerator = new EntityKeyGenerator(StringObjectIdGenerator.Instance); + keyGenerator = EntityKeyGenerators.StringKeyGenerator; } else if (memberType == typeof(Guid)) { - idMemberMap.SetIdGenerator(CombGuidGenerator.Instance); - definition.KeyGenerator = new EntityKeyGenerator(CombGuidGenerator.Instance); + keyGenerator = EntityKeyGenerators.GuidKeyGenerator; } else if (memberType == typeof(ObjectId)) { - idMemberMap.SetIdGenerator(ObjectIdGenerator.Instance); - definition.KeyGenerator = new EntityKeyGenerator(ObjectIdGenerator.Instance); + keyGenerator = EntityKeyGenerators.ObjectIdKeyGenerator; } + + definition.Key = new EntityKeyDefinition + { + Property = entityProperty, + KeyGenerator = keyGenerator + }; } } } diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/ExtraElementsProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/ExtraElementsProcessor.cs index 3a0eb886..71e969cd 100644 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/ExtraElementsProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/Processors/ExtraElementsProcessor.cs @@ -7,7 +7,7 @@ namespace MongoFramework.Infrastructure.Mapping.Processors { public class ExtraElementsProcessor : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { var entityType = definition.EntityType; @@ -15,30 +15,29 @@ public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) var ignoreExtraElements = entityType.GetCustomAttribute(); if (ignoreExtraElements != null) { - classMap.SetIgnoreExtraElements(true); - classMap.SetIgnoreExtraElementsIsInherited(ignoreExtraElements.IgnoreInherited); + definition.ExtraElements = new EntityExtraElementsDefinition + { + IgnoreExtraElements = true, + IgnoreInherited = ignoreExtraElements.IgnoreInherited + }; } else { - classMap.SetIgnoreExtraElements(false); - - //If any of the Entity's properties have the "ExtraElementsAttribute", assign that against the BsonClassMap - + //If any of the Entity's properties have the "ExtraElementsAttribute", use that foreach (var property in definition.Properties) { var extraElementsAttribute = property.PropertyInfo.GetCustomAttribute(); - if (extraElementsAttribute != null && typeof(IDictionary).IsAssignableFrom(property.PropertyType)) + if (extraElementsAttribute != null && typeof(IDictionary).IsAssignableFrom(property.PropertyInfo.PropertyType)) { - foreach (var memberMap in classMap.DeclaredMemberMaps) + definition.ExtraElements = new EntityExtraElementsDefinition { - if (memberMap.ElementName == property.ElementName) - { - classMap.SetExtraElementsMember(memberMap); - return; - } - } + Property = property, + IgnoreExtraElements = false + }; + break; } } + } } } diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/HierarchyProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/HierarchyProcessor.cs index c7d0b8f5..18b50632 100644 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/HierarchyProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/Processors/HierarchyProcessor.cs @@ -4,17 +4,13 @@ namespace MongoFramework.Infrastructure.Mapping.Processors { public class HierarchyProcessor : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { var entityType = definition.EntityType; if (EntityMapping.IsValidTypeToMap(entityType.BaseType)) { EntityMapping.TryRegisterType(entityType.BaseType, out _); } - else - { - classMap.SetIsRootClass(true); - } } } } diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/IndexProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/IndexProcessor.cs index ca86bb1e..c1f25b61 100644 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/IndexProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/Processors/IndexProcessor.cs @@ -1,22 +1,22 @@ using System.Collections.Generic; using System.Reflection; -using MongoDB.Bson.Serialization; using MongoFramework.Attributes; namespace MongoFramework.Infrastructure.Mapping.Processors { public class IndexProcessor : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { - var definitionIndexes = new List(); + var definitionIndexes = new List(); foreach (var property in definition.TraverseProperties()) { - foreach (var indexAttribute in property.PropertyInfo.GetCustomAttributes()) + foreach (var indexAttribute in property.Property.PropertyInfo.GetCustomAttributes()) { - definitionIndexes.Add(new EntityIndex + definitionIndexes.Add(new EntityIndexDefinition { - Property = property, + Property = property.Property, + Path = property.GetPath(), IndexName = indexAttribute.Name, IsUnique = indexAttribute.IsUnique, SortOrder = indexAttribute.SortOrder, diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/MappingAdapterProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/MappingAdapterProcessor.cs index ef5f40be..b28718e5 100644 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/MappingAdapterProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/Processors/MappingAdapterProcessor.cs @@ -7,7 +7,7 @@ namespace MongoFramework.Infrastructure.Mapping.Processors { public class MappingAdapterProcessor : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { var adapterAttribute = definition.EntityType.GetCustomAttribute(); @@ -20,7 +20,7 @@ public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) if (instance != null) { - instance.ApplyMapping(definition, classMap); + instance.ApplyMapping(definition); } } diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/NestedTypeProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/NestedTypeProcessor.cs index eb49a868..798562e9 100644 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/NestedTypeProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/Processors/NestedTypeProcessor.cs @@ -1,18 +1,17 @@ -using MongoDB.Bson.Serialization; -using MongoFramework.Infrastructure.Internal; +using MongoFramework.Infrastructure.Internal; namespace MongoFramework.Infrastructure.Mapping.Processors { public class NestedTypeProcessor : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { var entityType = definition.EntityType; var properties = definition.Properties; foreach (var property in properties) { - var propertyType = property.PropertyType; + var propertyType = property.PropertyInfo.PropertyType; propertyType = propertyType.GetEnumerableItemTypeOrDefault(); //Maps the property type for handling property nesting diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/PropertyMappingProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/PropertyMappingProcessor.cs index ee4efbad..2b36fb63 100644 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/PropertyMappingProcessor.cs +++ b/src/MongoFramework/Infrastructure/Mapping/Processors/PropertyMappingProcessor.cs @@ -7,12 +7,12 @@ namespace MongoFramework.Infrastructure.Mapping.Processors { public class PropertyMappingProcessor : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { var entityType = definition.EntityType; var properties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); - var definitionProperties = new List(); + var definitionProperties = new List(); foreach (var property in properties) { @@ -41,23 +41,19 @@ public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) continue; } - //Do the mapping - var memberMap = classMap.MapMember(property); + var elementName = property.Name; //Set custom element name with the "ColumnAttribute" var columnAttribute = property.GetCustomAttribute(); if (columnAttribute != null) { - var mappedName = columnAttribute.Name; - memberMap.SetElementName(mappedName); + elementName = columnAttribute.Name; } - definitionProperties.Add(new EntityProperty + definitionProperties.Add(new EntityPropertyDefinition { - EntityType = definition.EntityType, - ElementName = memberMap.ElementName, - FullPath = memberMap.ElementName, - PropertyType = property.PropertyType, + EntityDefinition = definition, + ElementName = elementName, PropertyInfo = property }); } diff --git a/src/MongoFramework/Infrastructure/Mapping/Processors/TypeDiscoveryProcessor.cs b/src/MongoFramework/Infrastructure/Mapping/Processors/TypeDiscoveryProcessor.cs deleted file mode 100644 index 0175ba53..00000000 --- a/src/MongoFramework/Infrastructure/Mapping/Processors/TypeDiscoveryProcessor.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MongoDB.Bson.Serialization; -using MongoFramework.Infrastructure.Serialization; - -namespace MongoFramework.Infrastructure.Mapping.Processors -{ - public class TypeDiscoveryProcessor : IMappingProcessor - { - private bool ProviderAdded { get; set; } - - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) - { - if (!ProviderAdded) - { - ProviderAdded = true; - BsonSerializer.RegisterSerializationProvider(TypeDiscoverySerializationProvider.Instance); - } - } - } -} diff --git a/src/MongoFramework/Infrastructure/Mapping/PropertyTraversalExtensions.cs b/src/MongoFramework/Infrastructure/Mapping/PropertyTraversalExtensions.cs new file mode 100644 index 00000000..26a9cce6 --- /dev/null +++ b/src/MongoFramework/Infrastructure/Mapping/PropertyTraversalExtensions.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using System; +using MongoFramework.Infrastructure.Internal; +using System.Linq; +using System.Buffers; +using System.Diagnostics; + +namespace MongoFramework.Infrastructure.Mapping; + +public interface ITraversedProperty +{ + public ITraversedProperty Parent { get; } + public IEntityPropertyDefinition Property { get; } + public int Depth { get; } + public string GetPath(); +} + +[DebuggerDisplay("Property = {Property.ElementName}, Parent = {Parent?.Property?.ElementName}, Depth = {Depth}")] +internal record TraversedProperty : ITraversedProperty +{ + private static readonly string ElementSeparator = "."; + + public ITraversedProperty Parent { get; init; } + public IEntityPropertyDefinition Property { get; init; } + public int Depth { get; init; } + + public string GetPath() + { + if (Depth == 0) + { + return Property.ElementName; + } + + var pool = ArrayPool.Shared.Rent(Depth + 1); + try + { + ITraversedProperty current = this; + for (var i = Depth; i >= 0; i--) + { + pool[i] = current.Property.ElementName; + current = current.Parent; + } + + return string.Join(ElementSeparator, pool, 0, Depth + 1); + } + finally + { + ArrayPool.Shared.Return(pool); + } + } +} + +public static class PropertyTraversalExtensions +{ + private readonly record struct TraversalState + { + public HashSet SeenTypes { get; init; } + public IEnumerable Properties { get; init; } + } + + public static IEnumerable TraverseProperties(this IEntityDefinition definition) + { + var stack = new Stack(); + stack.Push(new TraversalState + { + SeenTypes = new HashSet { definition.EntityType }, + Properties = definition.GetAllProperties().Select(p => new TraversedProperty + { + Property = p, + Depth = 0 + }) + }); + + while (stack.Count > 0) + { + var state = stack.Pop(); + foreach (var traversedProperty in state.Properties) + { + yield return traversedProperty; + + var propertyType = traversedProperty.Property.PropertyInfo.PropertyType; + propertyType = propertyType.GetEnumerableItemTypeOrDefault(); + + if (EntityMapping.IsValidTypeToMap(propertyType) && !state.SeenTypes.Contains(propertyType)) + { + var nestedProperties = EntityMapping.GetOrCreateDefinition(propertyType) + .GetAllProperties() + .Select(p => new TraversedProperty + { + Parent = traversedProperty, + Property = p, + Depth = traversedProperty.Depth + 1 + }); + + stack.Push(new TraversalState + { + SeenTypes = new HashSet(state.SeenTypes) + { + propertyType + }, + Properties = nestedProperties + }); + } + } + } + } +} \ No newline at end of file diff --git a/src/MongoFramework/Infrastructure/Serialization/TypeDiscoverySerializationProvider.cs b/src/MongoFramework/Infrastructure/Serialization/TypeDiscoverySerializationProvider.cs index 166ea364..e09b966b 100644 --- a/src/MongoFramework/Infrastructure/Serialization/TypeDiscoverySerializationProvider.cs +++ b/src/MongoFramework/Infrastructure/Serialization/TypeDiscoverySerializationProvider.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using MongoDB.Bson.Serialization; using MongoFramework.Attributes; using MongoFramework.Utilities; @@ -10,13 +9,11 @@ public class TypeDiscoverySerializationProvider : BsonSerializationProviderBase { public static TypeDiscoverySerializationProvider Instance { get; } = new TypeDiscoverySerializationProvider(); - public bool Enabled { get; set; } = true; - public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry) { Check.NotNull(type, nameof(type)); - if (Enabled && (type.GetCustomAttribute() != null || type == typeof(object))) + if (Attribute.IsDefined(type, typeof(RuntimeTypeDiscoveryAttribute)) || type == typeof(object)) { var serializerType = typeof(TypeDiscoverySerializer<>).MakeGenericType(type); return (IBsonSerializer)Activator.CreateInstance(serializerType); diff --git a/src/MongoFramework/IsExternalInit.cs b/src/MongoFramework/IsExternalInit.cs new file mode 100644 index 00000000..17a51c23 --- /dev/null +++ b/src/MongoFramework/IsExternalInit.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} \ No newline at end of file diff --git a/src/MongoFramework/Linq/LinqExtensions.cs b/src/MongoFramework/Linq/LinqExtensions.cs index 2fedd40e..3f5b5d77 100644 --- a/src/MongoFramework/Linq/LinqExtensions.cs +++ b/src/MongoFramework/Linq/LinqExtensions.cs @@ -27,26 +27,22 @@ public static string ToQuery(this IQueryable queryable) public static IQueryable WhereIdMatches(this IQueryable queryable, IEnumerable entityIds) where TEntity : class { - var idProperty = EntityMapping.GetOrCreateDefinition(typeof(TEntity)) - .GetAllProperties() - .Where(p => p.IsKey) - .FirstOrDefault(); - + var idProperty = EntityMapping.GetOrCreateDefinition(typeof(TEntity)).Key.Property; return queryable.WherePropertyMatches(idProperty, entityIds); } - public static IQueryable WherePropertyMatches(this IQueryable queryable, IEntityProperty property, IEnumerable values) where TEntity : class + public static IQueryable WherePropertyMatches(this IQueryable queryable, IEntityPropertyDefinition property, IEnumerable values) where TEntity : class { //The cast allows for handling identifiers generically as "IEnumerable". Without the Cast call, we can't handle ObjectId etc. var castMethod = typeof(Enumerable).GetMethod("Cast", BindingFlags.Public | BindingFlags.Static); - var castedIdentifiers = castMethod.MakeGenericMethod(property.PropertyType).Invoke(null, new[] { values }); + var castedIdentifiers = castMethod.MakeGenericMethod(property.PropertyInfo.PropertyType).Invoke(null, new[] { values }); //Dynamically build the LINQ query, it looks something like: e => castedIdentifiers.Contains(e.{propertyName}) var entityParameter = Expression.Parameter(typeof(TEntity), "e"); var propertyExpression = Expression.Property(entityParameter, property.PropertyInfo.Name); var identifiersExpression = Expression.Constant(castedIdentifiers); var expression = Expression.Lambda>( - Expression.Call(typeof(Enumerable), "Contains", new[] { property.PropertyType }, identifiersExpression, propertyExpression), + Expression.Call(typeof(Enumerable), "Contains", new[] { property.PropertyInfo.PropertyType }, identifiersExpression, propertyExpression), entityParameter ); diff --git a/src/MongoFramework/MongoDbBucketSet.cs b/src/MongoFramework/MongoDbBucketSet.cs index 26a2859c..ce2d47eb 100644 --- a/src/MongoFramework/MongoDbBucketSet.cs +++ b/src/MongoFramework/MongoDbBucketSet.cs @@ -19,7 +19,7 @@ public class MongoDbBucketSet : IMongoDbBucketSet EnumerableModel { get; set; } - public List ListModel { get; set; } - - } - public class NestedTraverseMappingModel - { - public string PropertyOne { get; set; } - public int PropertyTwo { get; set; } - public InnerNestedTraverseMappingModel InnerModel { get; set; } - } - public class InnerNestedTraverseMappingModel - { - public string InnerMostProperty { get; set; } - public TraverseMappingModel NestedRecursionType { get; set; } - } - public class OverridePropertyBaseModel { public virtual string TargetProperty { get; set; } @@ -75,51 +51,13 @@ public void GetIdNameChecksInheritence() Assert.AreEqual("Id", parentDefinition.GetIdName()); } - [TestMethod] - public void TraverseMapping() - { - var definition = EntityMapping.RegisterType(typeof(TraverseMappingModel)); - var result = definition.TraverseProperties().ToArray(); - - Assert.AreEqual(32, result.Length); - Assert.IsTrue(result.Any(m => m.EntityType == typeof(NestedTraverseMappingModel))); - Assert.IsTrue(result.Any(m => m.EntityType == typeof(InnerNestedTraverseMappingModel))); - - Assert.IsTrue(result.Any(m => m.FullPath == "RecursionType")); - - Assert.IsTrue(result.Any(m => m.FullPath == "NestedModel.PropertyOne")); - Assert.IsTrue(result.Any(m => m.FullPath == "NestedModel.InnerModel")); - Assert.IsTrue(result.Any(m => m.FullPath == "NestedModel.InnerModel.InnerMostProperty")); - Assert.IsTrue(result.Any(m => m.FullPath == "NestedModel.InnerModel.NestedRecursionType")); - - Assert.IsTrue(result.Any(m => m.FullPath == "RepeatedType.PropertyOne")); - Assert.IsTrue(result.Any(m => m.FullPath == "RepeatedType.InnerModel")); - Assert.IsTrue(result.Any(m => m.FullPath == "RepeatedType.InnerModel.InnerMostProperty")); - Assert.IsTrue(result.Any(m => m.FullPath == "RepeatedType.InnerModel.NestedRecursionType")); - - Assert.IsTrue(result.Any(m => m.FullPath == "ArrayModel")); - Assert.IsTrue(result.Any(m => m.FullPath == "ArrayModel.InnerModel")); - Assert.IsTrue(result.Any(m => m.FullPath == "ArrayModel.InnerModel.InnerMostProperty")); - Assert.IsTrue(result.Any(m => m.FullPath == "ArrayModel.InnerModel.NestedRecursionType")); - - Assert.IsTrue(result.Any(m => m.FullPath == "EnumerableModel")); - Assert.IsTrue(result.Any(m => m.FullPath == "EnumerableModel.InnerModel")); - Assert.IsTrue(result.Any(m => m.FullPath == "EnumerableModel.InnerModel.InnerMostProperty")); - Assert.IsTrue(result.Any(m => m.FullPath == "EnumerableModel.InnerModel.NestedRecursionType")); - - Assert.IsTrue(result.Any(m => m.FullPath == "ListModel")); - Assert.IsTrue(result.Any(m => m.FullPath == "ListModel.InnerModel")); - Assert.IsTrue(result.Any(m => m.FullPath == "ListModel.InnerModel.InnerMostProperty")); - Assert.IsTrue(result.Any(m => m.FullPath == "ListModel.InnerModel.NestedRecursionType")); - } - [TestMethod] public void GetInheritedPropertiesTakesBaseProperties() { var definition = EntityMapping.RegisterType(typeof(OverridePropertyGrandChildModel)); var inheritedProperties = definition.GetInheritedProperties().ToArray(); Assert.AreEqual(1, inheritedProperties.Length); - Assert.AreEqual(typeof(OverridePropertyBaseModel), inheritedProperties[0].EntityType); + Assert.AreEqual(typeof(OverridePropertyBaseModel), inheritedProperties[0].EntityDefinition.EntityType); } [TestMethod] public void GetAllPropertiesTakesBaseProperties() @@ -127,7 +65,7 @@ public void GetAllPropertiesTakesBaseProperties() var definition = EntityMapping.RegisterType(typeof(OverridePropertyChildModel)); var allProperties = definition.GetAllProperties().ToArray(); Assert.AreEqual(1, allProperties.Length); - Assert.AreEqual(typeof(OverridePropertyBaseModel), allProperties[0].EntityType); + Assert.AreEqual(typeof(OverridePropertyBaseModel), allProperties[0].EntityDefinition.EntityType); } [TestMethod] diff --git a/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/EntityIdProcessorTests.cs b/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/EntityIdProcessorTests.cs index fe7de834..e2496291 100644 --- a/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/EntityIdProcessorTests.cs +++ b/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/EntityIdProcessorTests.cs @@ -58,12 +58,9 @@ public void StringIdGeneratorOnStringProperty() { EntityMapping.AddMappingProcessor(new PropertyMappingProcessor()); EntityMapping.AddMappingProcessor(new EntityIdProcessor()); - EntityMapping.RegisterType(typeof(StringIdGeneratorTestModel)); + var definition = EntityMapping.RegisterType(typeof(StringIdGeneratorTestModel)); - var classMap = BsonClassMap.GetRegisteredClassMaps() - .Where(cm => cm.ClassType == typeof(StringIdGeneratorTestModel)).FirstOrDefault(); - - Assert.AreEqual(typeof(StringObjectIdGenerator), classMap.IdMemberMap.IdGenerator?.GetType()); + Assert.AreEqual(EntityKeyGenerators.StringKeyGenerator, definition.Key.KeyGenerator); } [TestMethod] @@ -71,12 +68,9 @@ public void GuidIdGeneratorOnGuidProperty() { EntityMapping.AddMappingProcessor(new PropertyMappingProcessor()); EntityMapping.AddMappingProcessor(new EntityIdProcessor()); - EntityMapping.RegisterType(typeof(GuidIdGeneratorTestModel)); - - var classMap = BsonClassMap.GetRegisteredClassMaps() - .Where(cm => cm.ClassType == typeof(GuidIdGeneratorTestModel)).FirstOrDefault(); + var definition = EntityMapping.RegisterType(typeof(GuidIdGeneratorTestModel)); - Assert.AreEqual(typeof(CombGuidGenerator), classMap.IdMemberMap.IdGenerator?.GetType()); + Assert.AreEqual(EntityKeyGenerators.GuidKeyGenerator, definition.Key.KeyGenerator); } [TestMethod] @@ -84,12 +78,9 @@ public void ObjectIdGeneratorOnObjectIdProperty() { EntityMapping.AddMappingProcessor(new PropertyMappingProcessor()); EntityMapping.AddMappingProcessor(new EntityIdProcessor()); - EntityMapping.RegisterType(typeof(ObjectIdGeneratorTestModel)); - - var classMap = BsonClassMap.GetRegisteredClassMaps() - .Where(cm => cm.ClassType == typeof(ObjectIdGeneratorTestModel)).FirstOrDefault(); + var definition = EntityMapping.RegisterType(typeof(ObjectIdGeneratorTestModel)); - Assert.AreEqual(typeof(ObjectIdGenerator), classMap.IdMemberMap.IdGenerator?.GetType()); + Assert.AreEqual(EntityKeyGenerators.ObjectIdKeyGenerator, definition.Key.KeyGenerator); } [TestMethod] diff --git a/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/ExtraElementsProcessorTests.cs b/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/ExtraElementsProcessorTests.cs index c5bfd18f..15857388 100644 --- a/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/ExtraElementsProcessorTests.cs +++ b/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/ExtraElementsProcessorTests.cs @@ -38,6 +38,7 @@ public class IgnoreExtraElementsModel [TestMethod] public void ObeysIgnoreExtraElementsAttribute() { + EntityMapping.AddMappingProcessor(new PropertyMappingProcessor()); EntityMapping.AddMappingProcessor(new ExtraElementsProcessor()); EntityMapping.RegisterType(typeof(IgnoreExtraElementsModel)); diff --git a/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/MappingAdapterProcessorTests.cs b/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/MappingAdapterProcessorTests.cs index 741b788a..ce3601e3 100644 --- a/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/MappingAdapterProcessorTests.cs +++ b/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/MappingAdapterProcessorTests.cs @@ -15,13 +15,13 @@ public class MappingAdapterProcessorTests : MappingTestBase public class AdapterTestModelMappingAdapter : IMappingProcessor { - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { definition.CollectionName = "Custom"; var definitionIndexes = definition.Indexes.ToList(); - definitionIndexes.Add(new EntityIndex + definitionIndexes.Add(new EntityIndexDefinition { Property = definition.GetProperty("UserName"), IsUnique = true, @@ -59,7 +59,7 @@ public AdapterTestModelMappingAdapterConstructor(string test) } - public void ApplyMapping(IEntityDefinition definition, BsonClassMap classMap) + public void ApplyMapping(IEntityDefinition definition) { throw new NotImplementedException(); } diff --git a/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/TypeDiscoveryProcessorTests.cs b/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/TypeDiscoveryProcessorTests.cs deleted file mode 100644 index fb0c4c27..00000000 --- a/tests/MongoFramework.Tests/Infrastructure/Mapping/Processors/TypeDiscoveryProcessorTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MongoDB.Bson.Serialization; -using MongoFramework.Attributes; -using MongoFramework.Infrastructure.Mapping; -using MongoFramework.Infrastructure.Mapping.Processors; -using MongoFramework.Infrastructure.Serialization; - -namespace MongoFramework.Tests.Infrastructure.Mapping.Processors -{ - [TestClass] - public class TypeDiscoveryProcessorTests : MappingTestBase - { - public class NoTypeDiscoveryAttributeModel - { - - } - - [RuntimeTypeDiscovery] - public class TypeDiscoveryAttributeModel - { - - } - - [TestMethod] - public void TypeDiscoverySerializerWhenAttributeIsDefined() - { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); - EntityMapping.AddMappingProcessor(new PropertyMappingProcessor()); - - EntityMapping.RegisterType(typeof(TypeDiscoveryAttributeModel)); - - var serializer = BsonSerializer.LookupSerializer(); - Assert.AreEqual(typeof(TypeDiscoverySerializer<>), serializer.GetType().GetGenericTypeDefinition()); - } - - [TestMethod] - public void NotTypeDiscoverySerializerWhenAttributeNotDefined() - { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); - EntityMapping.AddMappingProcessor(new PropertyMappingProcessor()); - - EntityMapping.RegisterType(typeof(NoTypeDiscoveryAttributeModel)); - - var serializer = BsonSerializer.LookupSerializer(); - Assert.AreNotEqual(typeof(TypeDiscoverySerializer<>), serializer.GetType().GetGenericTypeDefinition()); - } - } -} diff --git a/tests/MongoFramework.Tests/Infrastructure/Mapping/PropertyTraversalExtensionTests.cs b/tests/MongoFramework.Tests/Infrastructure/Mapping/PropertyTraversalExtensionTests.cs new file mode 100644 index 00000000..6c601e1d --- /dev/null +++ b/tests/MongoFramework.Tests/Infrastructure/Mapping/PropertyTraversalExtensionTests.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MongoFramework.Infrastructure.Mapping; + +namespace MongoFramework.Tests.Infrastructure.Mapping; + +[TestClass] +public class PropertyTraversalExtensionTests : TestBase +{ + public class TraverseMappingModel + { + public string Id { get; set; } + public NestedTraverseMappingModel NestedModel { get; set; } + public NestedTraverseMappingModel RepeatedType { get; set; } + public TraverseMappingModel RecursionType { get; set; } + + public NestedTraverseMappingModel[] ArrayModel { get; set; } + public IEnumerable EnumerableModel { get; set; } + public List ListModel { get; set; } + + } + public class NestedTraverseMappingModel + { + public string PropertyOne { get; set; } + public int PropertyTwo { get; set; } + public InnerNestedTraverseMappingModel InnerModel { get; set; } + } + public class InnerNestedTraverseMappingModel + { + public string InnerMostProperty { get; set; } + public TraverseMappingModel NestedRecursionType { get; set; } + } + + [TestMethod] + public void TraverseProperties() + { + var definition = EntityMapping.RegisterType(typeof(TraverseMappingModel)); + var result = definition.TraverseProperties().ToArray(); + + Assert.AreEqual(32, result.Length); + + Assert.IsTrue(result.Any(m => m.GetPath() == "RecursionType" && m.Depth == 0)); + + Assert.IsTrue(result.Any(m => m.GetPath() == "NestedModel.PropertyOne" && m.Depth == 1)); + Assert.IsTrue(result.Any(m => m.GetPath() == "NestedModel.InnerModel" && m.Depth == 1)); + Assert.IsTrue(result.Any(m => m.GetPath() == "NestedModel.InnerModel.InnerMostProperty" && m.Depth == 2)); + Assert.IsTrue(result.Any(m => m.GetPath() == "NestedModel.InnerModel.NestedRecursionType" && m.Depth == 2)); + + Assert.IsTrue(result.Any(m => m.GetPath() == "RepeatedType.PropertyOne" && m.Depth == 1)); + Assert.IsTrue(result.Any(m => m.GetPath() == "RepeatedType.InnerModel" && m.Depth == 1)); + Assert.IsTrue(result.Any(m => m.GetPath() == "RepeatedType.InnerModel.InnerMostProperty" && m.Depth == 2)); + Assert.IsTrue(result.Any(m => m.GetPath() == "RepeatedType.InnerModel.NestedRecursionType" && m.Depth == 2)); + + Assert.IsTrue(result.Any(m => m.GetPath() == "ArrayModel" && m.Depth == 0)); + Assert.IsTrue(result.Any(m => m.GetPath() == "ArrayModel.InnerModel" && m.Depth == 1)); + Assert.IsTrue(result.Any(m => m.GetPath() == "ArrayModel.InnerModel.InnerMostProperty" && m.Depth == 2)); + Assert.IsTrue(result.Any(m => m.GetPath() == "ArrayModel.InnerModel.NestedRecursionType" && m.Depth == 2)); + + Assert.IsTrue(result.Any(m => m.GetPath() == "EnumerableModel" && m.Depth == 0)); + Assert.IsTrue(result.Any(m => m.GetPath() == "EnumerableModel.InnerModel" && m.Depth == 1)); + Assert.IsTrue(result.Any(m => m.GetPath() == "EnumerableModel.InnerModel.InnerMostProperty" && m.Depth == 2)); + Assert.IsTrue(result.Any(m => m.GetPath() == "EnumerableModel.InnerModel.NestedRecursionType" && m.Depth == 2)); + + Assert.IsTrue(result.Any(m => m.GetPath() == "ListModel" && m.Depth == 0)); + Assert.IsTrue(result.Any(m => m.GetPath() == "ListModel.InnerModel" && m.Depth == 1)); + Assert.IsTrue(result.Any(m => m.GetPath() == "ListModel.InnerModel.InnerMostProperty" && m.Depth == 2)); + Assert.IsTrue(result.Any(m => m.GetPath() == "ListModel.InnerModel.NestedRecursionType" && m.Depth == 2)); + } +} diff --git a/tests/MongoFramework.Tests/Infrastructure/Serialization/TypeDiscoverySerializationTests.cs b/tests/MongoFramework.Tests/Infrastructure/Serialization/TypeDiscoverySerializationTests.cs index e33b3d74..c368ccfc 100644 --- a/tests/MongoFramework.Tests/Infrastructure/Serialization/TypeDiscoverySerializationTests.cs +++ b/tests/MongoFramework.Tests/Infrastructure/Serialization/TypeDiscoverySerializationTests.cs @@ -46,10 +46,16 @@ public class UnknownDictionaryValueModel public IDictionary Dictionary { get; set; } } + public class NoTypeDiscovery_KnownBaseModel + { + } + public class NoTypeDiscovery_UnknownChildModel : NoTypeDiscovery_KnownBaseModel + { + } + [TestMethod] public void NullDeserialization() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(KnownBaseModel)); var serializer = TypeDiscoverySerializationProvider.Instance.GetSerializer(typeof(KnownBaseModel)); @@ -78,7 +84,6 @@ public void NullDeserialization() [TestMethod] public void DeserializeChildTypeDiscoveryForRootEntity() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(KnownBaseModel)); var document = new BsonDocument @@ -93,7 +98,6 @@ public void DeserializeChildTypeDiscoveryForRootEntity() [TestMethod] public void DeserializeGrandChildTypeDiscoveryForRootEntity() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(KnownBaseModel)); var document = new BsonDocument @@ -108,26 +112,21 @@ public void DeserializeGrandChildTypeDiscoveryForRootEntity() [TestMethod] public void DeserializeWithoutTypeDiscovery() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); - EntityMapping.RegisterType(typeof(KnownBaseModel)); - - TypeDiscoverySerializationProvider.Instance.Enabled = false; + //This test primarily confirms the behaviour of the driver without type discovery + EntityMapping.RegisterType(typeof(NoTypeDiscovery_KnownBaseModel)); var document = new BsonDocument { { "_t", "UnknownChildModel" } }; - var deserializedResult = BsonSerializer.Deserialize(document); - Assert.IsNotInstanceOfType(deserializedResult, typeof(UnknownChildModel)); - - TypeDiscoverySerializationProvider.Instance.Enabled = true; + var deserializedResult = BsonSerializer.Deserialize(document); + Assert.IsNotInstanceOfType(deserializedResult, typeof(NoTypeDiscovery_UnknownChildModel)); } [TestMethod] public void DeserializeCollection() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(CollectionBaseModel)); var document = new BsonDocument @@ -162,7 +161,6 @@ public void DeserializeCollection() [TestMethod] public void ReserializationWithoutDataLoss() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(CollectionBaseModel)); var initialEntity = new CollectionBaseModel @@ -193,7 +191,6 @@ public void ReserializationWithoutDataLoss() [TestMethod] public void DeserializeNullForUnknownPropertyType() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(UnknownPropertyTypeSerializationModel)); var document = new BsonDocument @@ -209,7 +206,6 @@ public void DeserializeNullForUnknownPropertyType() [TestMethod] public void DeserializeDictionaryForUnknownPropertyType() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(UnknownPropertyTypeSerializationModel)); var document = new BsonDocument @@ -235,7 +231,6 @@ public void DeserializeDictionaryForUnknownPropertyType() [TestMethod] public void DeserializeSpecifiedForUnknownPropertyType() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(UnknownPropertyTypeSerializationModel)); var document = new BsonDocument @@ -257,7 +252,6 @@ public void DeserializeSpecifiedForUnknownPropertyType() [TestMethod] public void DeserializeStringForUnknownPropertyType() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(UnknownPropertyTypeSerializationModel)); var document = new BsonDocument @@ -276,7 +270,6 @@ public void DeserializeStringForUnknownPropertyType() [TestMethod] public void DeserializeBooleanForUnknownPropertyType() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(UnknownPropertyTypeSerializationModel)); var document = new BsonDocument @@ -295,7 +288,6 @@ public void DeserializeBooleanForUnknownPropertyType() [TestMethod] public void DeserializeUnknownTypesInDictionary() { - EntityMapping.AddMappingProcessor(new TypeDiscoveryProcessor()); EntityMapping.RegisterType(typeof(UnknownDictionaryValueModel)); var document = new BsonDocument diff --git a/tests/MongoFramework.Tests/MongoDbDriverHelper.cs b/tests/MongoFramework.Tests/MongoDbDriverHelper.cs index 4b88677b..c542a143 100644 --- a/tests/MongoFramework.Tests/MongoDbDriverHelper.cs +++ b/tests/MongoFramework.Tests/MongoDbDriverHelper.cs @@ -38,14 +38,6 @@ public static void ResetDriver() { discriminators.Clear(); } - - var serializerRegistryField = typeof(BsonSerializer).GetField("__serializerRegistry", BindingFlags.NonPublic | BindingFlags.Static); - if (serializerRegistryField.GetValue(null) is BsonSerializerRegistry registry) - { - var cacheField = typeof(BsonSerializerRegistry).GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance); - var registryCache = cacheField.GetValue(registry) as ConcurrentDictionary; - registryCache.Clear(); - } } } }