Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ public class AddToBucketCommand<TGroup, TSubEntity> : IWriteCommand<EntityBucket
{
private TGroup Group { get; }
private TSubEntity SubEntity { get; }
private IEntityProperty EntityTimeProperty { get; }
private IEntityPropertyDefinition EntityTimeProperty { get; }
private int BucketSize { get; }

public Type EntityType => typeof(EntityBucket<TGroup, TSubEntity>);

public AddToBucketCommand(TGroup group, TSubEntity subEntity, IEntityProperty entityTimeProperty, int bucketSize)
public AddToBucketCommand(TGroup group, TSubEntity subEntity, IEntityPropertyDefinition entityTimeProperty, int bucketSize)
{
Group = group;
SubEntity = subEntity;
Expand All @@ -42,7 +42,7 @@ public IEnumerable<WriteModel<EntityBucket<TGroup, TSubEntity>>> 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<EntityBucket<TGroup, TSubEntity>>(filter, updateDefinition)
{
Expand Down
38 changes: 38 additions & 0 deletions src/MongoFramework/Infrastructure/DriverAbstractionRules.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Provides a single entry point to configure common areas of the driver for MongoFramework
/// </summary>
internal static class DriverAbstractionRules
{
public static void ApplyRules()
{
RegisterSerializer<decimal>(new DecimalSerializer(BsonType.Decimal128));
RegisterSerializer<decimal?>(new NullableSerializer<decimal>(new DecimalSerializer(BsonType.Decimal128)));

BsonSerializer.RegisterSerializationProvider(TypeDiscoverySerializationProvider.Instance);
}

private static void RegisterSerializer<TTarget>(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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,23 @@ public static IEnumerable<CreateIndexModel<TEntity>> BuildModel()
}
}

private static CreateIndexModel<TEntity> CreateIndexModel(IEntityIndex indexDefinition)
private static CreateIndexModel<TEntity> CreateIndexModel(IEntityIndexDefinition indexDefinition)
{
var builder = Builders<TEntity>.IndexKeys;
IndexKeysDefinition<TEntity> 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)))
Expand Down
44 changes: 27 additions & 17 deletions src/MongoFramework/Infrastructure/Internal/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<Type> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ public static class DefaultProcessors
new EntityIdProcessor(),
new NestedTypeProcessor(),
new ExtraElementsProcessor(),
new DecimalSerializationProcessor(),
new TypeDiscoveryProcessor(),
new BsonKnownTypesProcessor(),
new IndexProcessor(),
new MappingAdapterProcessor()
Expand Down
64 changes: 64 additions & 0 deletions src/MongoFramework/Infrastructure/Mapping/DriverMappingInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using MongoDB.Bson.Serialization;

namespace MongoFramework.Infrastructure.Mapping;

/// <summary>
/// Maps the MongoFramework definition into something the MongoDB Driver will understand
/// </summary>
internal static class DriverMappingInterop
{
/// <summary>
/// Registers the <paramref name="definition"/> as a <see cref="BsonClassMap"/> with all appropriate properties configured.
/// </summary>
/// <param name="definition"></param>
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);
}
}
106 changes: 99 additions & 7 deletions src/MongoFramework/Infrastructure/Mapping/EntityDefinition.cs
Original file line number Diff line number Diff line change
@@ -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<IEntityPropertyDefinition> Properties { get; set; }
public IEnumerable<IEntityIndexDefinition> 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<IEntityPropertyDefinition> 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<IEntityPropertyDefinition> Properties { get; set; } = Enumerable.Empty<IEntityPropertyDefinition>();
public IEnumerable<IEntityIndexDefinition> Indexes { get; set; } = Enumerable.Empty<IEntityIndexDefinition>();
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<IEntityProperty> Properties { get; set; } = Enumerable.Empty<IEntityProperty>();
public IEnumerable<IEntityIndex> Indexes { get; set; } = Enumerable.Empty<IEntityIndex>();
PropertyInfo.SetValue(entity, value);
}
}

public class EntityIndexDefinition : IEntityIndexDefinition
{
public IReadOnlyCollection<IEntityPropertyDefinition> 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; }
}
Loading