From 0db6a38b51786f556f1c2954be37b375077e8517 Mon Sep 17 00:00:00 2001 From: glucaci Date: Tue, 9 Jul 2024 15:14:37 +0200 Subject: [PATCH 1/7] Add bson class map by type --- .../MongoCollectionBuilderTests.cs | 16 ++++++++++++ src/Context/IMongoCollectionBuilder.cs | 4 +++ .../Internal/MongoCollectionBuilder.cs | 25 +++++++++++++++++++ 3 files changed, 45 insertions(+) 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/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 { From 2204d79bc6f36f2a79ad12d81a225d257d6c6b0b Mon Sep 17 00:00:00 2001 From: glucaci Date: Wed, 10 Jul 2024 16:24:37 +0200 Subject: [PATCH 2/7] Support abstract record --- .../ImmutableConventionWithRecordsTests.cs | 23 +++++++++++++++++++ src/Context/ImmutableConvention.cs | 15 +++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) 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/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) From f3d37a7a58d0df5410ef72e78e875669c4471a57 Mon Sep 17 00:00:00 2001 From: glucaci Date: Wed, 10 Jul 2024 21:36:35 +0200 Subject: [PATCH 3/7] Update packages --- Directory.Packages.props | 4 ++-- src/Context.Tests/Internal/DependencyTypesResolverTests.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 42793f4..26001a7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,8 +22,8 @@ - - + + diff --git a/src/Context.Tests/Internal/DependencyTypesResolverTests.cs b/src/Context.Tests/Internal/DependencyTypesResolverTests.cs index 357d227..bb02e90 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 From 0047ff00477b6f6830274caa7714fe221bbc4fad Mon Sep 17 00:00:00 2001 From: glucaci Date: Wed, 10 Jul 2024 21:41:14 +0200 Subject: [PATCH 4/7] Update --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 26001a7..6507565 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,7 +23,7 @@ - + From 28c757c21bf4ab3ec68022a1b7c948e8f6a06ecf Mon Sep 17 00:00:00 2001 From: glucaci Date: Wed, 29 Jan 2025 11:17:54 +0100 Subject: [PATCH 5/7] Update .NET SDK version and add support for multiple versions in workflow --- .github/workflows/pull-request.yml | 5 ++++- global.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f170849..820acca 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -10,11 +10,14 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: | - 8 + - 8 + - 6 + - name: Dotnet Test run: dotnet test src 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" } } From e469422282d06de764c9acc254a8b82a638f4c04 Mon Sep 17 00:00:00 2001 From: glucaci Date: Wed, 29 Jan 2025 11:19:38 +0100 Subject: [PATCH 6/7] Update GitHub Actions to use latest versions of checkout and setup-dotnet --- .github/workflows/pull-request.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 820acca..ab081b6 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -9,14 +9,14 @@ 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 + 6.x.x + 8.x.x - name: Dotnet Test run: dotnet test src From 40d08e6816b0dd79c782518d96c2d5d217d2cae1 Mon Sep 17 00:00:00 2001 From: glucaci Date: Wed, 29 Jan 2025 11:28:42 +0100 Subject: [PATCH 7/7] Refactor TypeObjectSerializer to use Dictionary for allowed types and add thread safety with locks --- src/Context/Internal/TypeObjectSerializer.cs | 53 +++++++++++++------- 1 file changed, 36 insertions(+), 17 deletions(-) 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; }