diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f170849..ab081b6 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -9,12 +9,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 + - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | - 8 + 6.x.x + 8.x.x + - name: Dotnet Test run: dotnet test src diff --git a/Directory.Packages.props b/Directory.Packages.props index 97ba0d4..cff2b59 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,7 +19,7 @@ - + @@ -49,4 +49,4 @@ - \ No newline at end of file + diff --git a/global.json b/global.json index dea430a..f154fa3 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.301", + "version": "8.0.402", "rollForward": "latestFeature" } } diff --git a/src/Context.Tests/ImmutableConventionWithRecordsTests.cs b/src/Context.Tests/ImmutableConventionWithRecordsTests.cs index c9fc80f..7b9c695 100644 --- a/src/Context.Tests/ImmutableConventionWithRecordsTests.cs +++ b/src/Context.Tests/ImmutableConventionWithRecordsTests.cs @@ -111,7 +111,30 @@ public async Task ApplyConvention_SerializeSuccessful() public record A(B Foo, string BarFoo); public record B(string Bar); + } + + public class AbstractRecordCase : IClassFixture + { + private readonly MongoDbContextData _context; + + public AbstractRecordCase(MongoResource mongoResource) + { + _context = CreateContext(mongoResource); + } + + [Fact] + public async Task ApplyConvention_SerializeSuccessful() + { + // Arrange, Act and Assert + await InsertAndFind(_context, new B("foo")); + } + + public abstract record A(string Foo) + { + public string? Bar { get; set; } + } + public record B(string Foo) : A(Foo); } private static async Task InsertAndFind(MongoDbContextData context, T input) where T : class diff --git a/src/Context.Tests/Internal/DependencyTypesResolverTests.cs b/src/Context.Tests/Internal/DependencyTypesResolverTests.cs index 404b51f..149b9d2 100644 --- a/src/Context.Tests/Internal/DependencyTypesResolverTests.cs +++ b/src/Context.Tests/Internal/DependencyTypesResolverTests.cs @@ -14,7 +14,7 @@ public void GetAllowedTypesByDependencies_All_Successful() // Act IEnumerable knownNamespaces = DependencyTypesResolver - .GetAllowedTypesByDependencies(new[] { "Coverlet" }) + .GetAllowedTypesByDependencies(new[] { "Coverlet", "Castle" }) .OrderBy(x => x); // Assert diff --git a/src/Context.Tests/MongoCollectionBuilderTests.cs b/src/Context.Tests/MongoCollectionBuilderTests.cs index 1291f5a..bae075b 100644 --- a/src/Context.Tests/MongoCollectionBuilderTests.cs +++ b/src/Context.Tests/MongoCollectionBuilderTests.cs @@ -138,6 +138,22 @@ public void AddBsonClassMap_AddNewBsonClassMapWithoutParameter_BsonClassMapIsReg Assert.True(BsonClassMap.IsClassMapRegistered(typeof(ItemWithoutSpecificClassMap))); } + [Fact] + public void AddBsonClassMapByType_AddNewBsonClassMap_BsonClassMapIsRegistered() + { + // Arrange + var mongoCollectionBuilder = + new MongoCollectionBuilder(_mongoDatabase); + + // Act + mongoCollectionBuilder.AddBsonClassMap(typeof(Order)); + IMongoCollection result = mongoCollectionBuilder.Build(); + + // Assert + Assert.NotNull(result); + Assert.True(BsonClassMap.IsClassMapRegistered(typeof(Order))); + } + private class ItemClassMapNotRegistered { public int Id { get; set; } diff --git a/src/Context/IMongoCollectionBuilder.cs b/src/Context/IMongoCollectionBuilder.cs index 1152a5e..c81f702 100644 --- a/src/Context/IMongoCollectionBuilder.cs +++ b/src/Context/IMongoCollectionBuilder.cs @@ -15,6 +15,10 @@ IMongoCollectionBuilder AddBsonClassMap( Action> bsonClassMapAction) where TMapDocument : class; + IMongoCollectionBuilder AddBsonClassMap( + Type type, + Action? bsonClassMapAction = default); + IMongoCollectionBuilder WithCollectionSettings( Action collectionSettings); diff --git a/src/Context/ImmutableConvention.cs b/src/Context/ImmutableConvention.cs index c63951b..290f3d6 100644 --- a/src/Context/ImmutableConvention.cs +++ b/src/Context/ImmutableConvention.cs @@ -36,7 +36,7 @@ public void Apply(BsonClassMap classMap) .ToList(); var mappingProperties = properties - .Where(p => IsReadOnlyProperty(classMap, p) || IsInitOnlyProperty(p)) + .Where(p => IsReadOnlyProperty(classMap, p) || IsInitOnlyProperty(classMap, p)) .ToList(); foreach (PropertyInfo property in mappingProperties) @@ -140,7 +140,9 @@ private static bool IsReadOnlyProperty( return true; } - private bool IsInitOnlyProperty(PropertyInfo property) + private bool IsInitOnlyProperty( + BsonClassMap classMap, + PropertyInfo property) { if (!property.CanWrite) { @@ -153,7 +155,7 @@ private bool IsInitOnlyProperty(PropertyInfo property) var containsInit = setModifiers?.Any(m => m.FullName == _externalInitTypeName); - return containsInit ?? false; + return containsInit.GetValueOrDefault(false) && !IsBaseTypeProperty(classMap, property); } private static bool IsBaseTypeProperty( @@ -163,6 +165,13 @@ private static bool IsBaseTypeProperty( return getMethodInfo.GetBaseDefinition().DeclaringType != classMap.ClassType; } + private static bool IsBaseTypeProperty( + BsonClassMap classMap, + PropertyInfo propertyInfo) + { + return propertyInfo.DeclaringType != classMap.ClassType; + } + private static bool IsOverrideProperty( BsonClassMap classMap, MethodInfo getMethodInfo) diff --git a/src/Context/Internal/MongoCollectionBuilder.cs b/src/Context/Internal/MongoCollectionBuilder.cs index 2a9a334..0f47170 100644 --- a/src/Context/Internal/MongoCollectionBuilder.cs +++ b/src/Context/Internal/MongoCollectionBuilder.cs @@ -53,6 +53,16 @@ public IMongoCollectionBuilder AddBsonClassMap() return this; } + public IMongoCollectionBuilder AddBsonClassMap( + Type type, + Action? bsonClassMapAction = default) + { + _classMapActions.Add(() => + RegisterClassMapSync(type, bsonClassMapAction)); + + return this; + } + public IMongoCollectionBuilder WithCollectionSettings( Action collectionSettings) { @@ -103,6 +113,21 @@ private void RegisterClassMapSync( } } + private void RegisterClassMapSync( + Type type, + Action? bsonClassMapAction) + { + lock (_lockObject) + { + if (!BsonClassMap.IsClassMapRegistered(type)) + { + var classMap = new BsonClassMap(type); + bsonClassMapAction?.Invoke(classMap); + BsonClassMap.RegisterClassMap(classMap); + } + } + } + private void RegisterClassMapSync() where TMapDocument : class { diff --git a/src/Context/Internal/TypeObjectSerializer.cs b/src/Context/Internal/TypeObjectSerializer.cs index bdb913e..1badd95 100644 --- a/src/Context/Internal/TypeObjectSerializer.cs +++ b/src/Context/Internal/TypeObjectSerializer.cs @@ -13,9 +13,10 @@ namespace MongoDB.Extensions.Context.Internal; internal class TypeObjectSerializer : ClassSerializerBase, IHasDiscriminatorConvention { private readonly ObjectSerializer _objectSerializer; - private static readonly ConcurrentDictionary _allowedTypes = new(); + private static readonly Dictionary _allowedTypes = new(); private static readonly HashSet _allowedTypesByNamespaces = new(); private static readonly HashSet _allowedTypesByDependencies = new(); + private static readonly object _lock = new(); public TypeObjectSerializer() { @@ -34,44 +35,62 @@ public static IReadOnlyCollection AllowedTypesByDependencies public static bool IsTypeAllowed(Type type) { - return ObjectSerializer.DefaultAllowedTypes.Invoke(type) || - _allowedTypes.ContainsKey(type) || - IsInAllowedNamespaces(type) || - IsInAllowedDependencyTypes(type); + lock (_lock) + { + return ObjectSerializer.DefaultAllowedTypes.Invoke(type) || + _allowedTypes.ContainsKey(type) || + IsInAllowedNamespaces(type) || + IsInAllowedDependencyTypes(type); + } } public static void AddAllowedType() { - _allowedTypes.TryAdd(typeof(T), true); + lock (_lock) + { + _allowedTypes.Add(typeof(T), true); + } } public static void AddAllowedTypes(params Type[] allowedTypes) { - foreach (Type allowedType in allowedTypes) + lock (_lock) { - _allowedTypes.TryAdd(allowedType, true); + foreach (Type allowedType in allowedTypes) + { + _allowedTypes.Add(allowedType, true); + } } } public static void AddAllowedTypes(params string[] allowedNamespaces) { - foreach (string allowedNamespace in allowedNamespaces) + lock (_lock) { - _allowedTypesByNamespaces.Add(allowedNamespace); + foreach (string allowedNamespace in allowedNamespaces) + { + _allowedTypesByNamespaces.Add(allowedNamespace); + } } } public static void AddAllowedTypesOfAllDependencies(params string[] excludeNamespaces) { - _allowedTypesByDependencies - .UnionWith(DependencyTypesResolver.GetAllowedTypesByDependencies(excludeNamespaces)); + lock (_lock) + { + _allowedTypesByDependencies + .UnionWith(DependencyTypesResolver.GetAllowedTypesByDependencies(excludeNamespaces)); + } } internal static void Clear() { - _allowedTypes.Clear(); - _allowedTypesByNamespaces.Clear(); - _allowedTypesByDependencies.Clear(); + lock (_lock) + { + _allowedTypes.Clear(); + _allowedTypesByNamespaces.Clear(); + _allowedTypesByDependencies.Clear(); + } } private static bool IsInAllowedNamespaces(Type type) @@ -85,7 +104,7 @@ private static bool IsInAllowedNamespaces(Type type) if (isInAllowedNamespaces) { - _allowedTypes.TryAdd(type, true); + _allowedTypes.Add(type, true); } return isInAllowedNamespaces; @@ -115,7 +134,7 @@ private static bool IsInAllowedDependencyTypes(Type type) bool isInDependencyTypes = _allowedTypesByDependencies .Contains(type.GetRootNamespace()); - _allowedTypes.TryAdd(type, isInDependencyTypes); + _allowedTypes.Add(type, isInDependencyTypes); return isInDependencyTypes; }