Skip to content

Commit 143974e

Browse files
authored
Register BsonClassMap by type and support abstract Record (#95)
1 parent 1ab6e19 commit 143974e

10 files changed

+126
-27
lines changed

.github/workflows/pull-request.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ jobs:
99
runs-on: ubuntu-latest
1010
steps:
1111
- name: Checkout code
12-
uses: actions/checkout@v2
12+
uses: actions/checkout@v4
13+
1314
- name: Setup .NET
14-
uses: actions/setup-dotnet@v3
15+
uses: actions/setup-dotnet@v4
1516
with:
1617
dotnet-version: |
17-
8
18+
6.x.x
19+
8.x.x
20+
1821
- name: Dotnet Test
1922
run: dotnet test src
2023

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<ItemGroup>
2020
<PackageVersion Include="MongoDB.Driver" Version="3.0.0" />
2121
<PackageVersion Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.0.0" />
22-
<PackageVersion Include="System.Text.Json" Version="7.0.0" />
22+
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
2323
</ItemGroup>
2424
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0'">
2525
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
@@ -49,4 +49,4 @@
4949
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
5050
<PackageVersion Include="Microsoft.Extensions.Logging" Version="2.2.0" />
5151
</ItemGroup>
52-
</Project>
52+
</Project>

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"version": "8.0.301",
3+
"version": "8.0.402",
44
"rollForward": "latestFeature"
55
}
66
}

src/Context.Tests/ImmutableConventionWithRecordsTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,30 @@ public async Task ApplyConvention_SerializeSuccessful()
111111
public record A(B Foo, string BarFoo);
112112

113113
public record B(string Bar);
114+
}
115+
116+
public class AbstractRecordCase : IClassFixture<MongoResource>
117+
{
118+
private readonly MongoDbContextData _context;
119+
120+
public AbstractRecordCase(MongoResource mongoResource)
121+
{
122+
_context = CreateContext(mongoResource);
123+
}
124+
125+
[Fact]
126+
public async Task ApplyConvention_SerializeSuccessful()
127+
{
128+
// Arrange, Act and Assert
129+
await InsertAndFind(_context, new B("foo"));
130+
}
131+
132+
public abstract record A(string Foo)
133+
{
134+
public string? Bar { get; set; }
135+
}
114136

137+
public record B(string Foo) : A(Foo);
115138
}
116139

117140
private static async Task InsertAndFind<T>(MongoDbContextData context, T input) where T : class

src/Context.Tests/Internal/DependencyTypesResolverTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public void GetAllowedTypesByDependencies_All_Successful()
1414

1515
// Act
1616
IEnumerable<string> knownNamespaces = DependencyTypesResolver
17-
.GetAllowedTypesByDependencies(new[] { "Coverlet" })
17+
.GetAllowedTypesByDependencies(new[] { "Coverlet", "Castle" })
1818
.OrderBy(x => x);
1919

2020
// Assert

src/Context.Tests/MongoCollectionBuilderTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,22 @@ public void AddBsonClassMap_AddNewBsonClassMapWithoutParameter_BsonClassMapIsReg
138138
Assert.True(BsonClassMap.IsClassMapRegistered(typeof(ItemWithoutSpecificClassMap)));
139139
}
140140

141+
[Fact]
142+
public void AddBsonClassMapByType_AddNewBsonClassMap_BsonClassMapIsRegistered()
143+
{
144+
// Arrange
145+
var mongoCollectionBuilder =
146+
new MongoCollectionBuilder<Order>(_mongoDatabase);
147+
148+
// Act
149+
mongoCollectionBuilder.AddBsonClassMap(typeof(Order));
150+
IMongoCollection<Order> result = mongoCollectionBuilder.Build();
151+
152+
// Assert
153+
Assert.NotNull(result);
154+
Assert.True(BsonClassMap.IsClassMapRegistered(typeof(Order)));
155+
}
156+
141157
private class ItemClassMapNotRegistered
142158
{
143159
public int Id { get; set; }

src/Context/IMongoCollectionBuilder.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ IMongoCollectionBuilder<TDocument> AddBsonClassMap<TMapDocument>(
1515
Action<BsonClassMap<TMapDocument>> bsonClassMapAction)
1616
where TMapDocument : class;
1717

18+
IMongoCollectionBuilder<TDocument> AddBsonClassMap(
19+
Type type,
20+
Action<BsonClassMap>? bsonClassMapAction = default);
21+
1822
IMongoCollectionBuilder<TDocument> WithCollectionSettings(
1923
Action<MongoCollectionSettings> collectionSettings);
2024

src/Context/ImmutableConvention.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void Apply(BsonClassMap classMap)
3636
.ToList();
3737

3838
var mappingProperties = properties
39-
.Where(p => IsReadOnlyProperty(classMap, p) || IsInitOnlyProperty(p))
39+
.Where(p => IsReadOnlyProperty(classMap, p) || IsInitOnlyProperty(classMap, p))
4040
.ToList();
4141

4242
foreach (PropertyInfo property in mappingProperties)
@@ -140,7 +140,9 @@ private static bool IsReadOnlyProperty(
140140
return true;
141141
}
142142

143-
private bool IsInitOnlyProperty(PropertyInfo property)
143+
private bool IsInitOnlyProperty(
144+
BsonClassMap classMap,
145+
PropertyInfo property)
144146
{
145147
if (!property.CanWrite)
146148
{
@@ -153,7 +155,7 @@ private bool IsInitOnlyProperty(PropertyInfo property)
153155
var containsInit = setModifiers?.Any(m =>
154156
m.FullName == _externalInitTypeName);
155157

156-
return containsInit ?? false;
158+
return containsInit.GetValueOrDefault(false) && !IsBaseTypeProperty(classMap, property);
157159
}
158160

159161
private static bool IsBaseTypeProperty(
@@ -163,6 +165,13 @@ private static bool IsBaseTypeProperty(
163165
return getMethodInfo.GetBaseDefinition().DeclaringType != classMap.ClassType;
164166
}
165167

168+
private static bool IsBaseTypeProperty(
169+
BsonClassMap classMap,
170+
PropertyInfo propertyInfo)
171+
{
172+
return propertyInfo.DeclaringType != classMap.ClassType;
173+
}
174+
166175
private static bool IsOverrideProperty(
167176
BsonClassMap classMap,
168177
MethodInfo getMethodInfo)

src/Context/Internal/MongoCollectionBuilder.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ public IMongoCollectionBuilder<TDocument> AddBsonClassMap<TMapDocument>()
5353
return this;
5454
}
5555

56+
public IMongoCollectionBuilder<TDocument> AddBsonClassMap(
57+
Type type,
58+
Action<BsonClassMap>? bsonClassMapAction = default)
59+
{
60+
_classMapActions.Add(() =>
61+
RegisterClassMapSync(type, bsonClassMapAction));
62+
63+
return this;
64+
}
65+
5666
public IMongoCollectionBuilder<TDocument> WithCollectionSettings(
5767
Action<MongoCollectionSettings> collectionSettings)
5868
{
@@ -103,6 +113,21 @@ private void RegisterClassMapSync<TMapDocument>(
103113
}
104114
}
105115

116+
private void RegisterClassMapSync(
117+
Type type,
118+
Action<BsonClassMap>? bsonClassMapAction)
119+
{
120+
lock (_lockObject)
121+
{
122+
if (!BsonClassMap.IsClassMapRegistered(type))
123+
{
124+
var classMap = new BsonClassMap(type);
125+
bsonClassMapAction?.Invoke(classMap);
126+
BsonClassMap.RegisterClassMap(classMap);
127+
}
128+
}
129+
}
130+
106131
private void RegisterClassMapSync<TMapDocument>()
107132
where TMapDocument : class
108133
{

src/Context/Internal/TypeObjectSerializer.cs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ namespace MongoDB.Extensions.Context.Internal;
1313
internal class TypeObjectSerializer : ClassSerializerBase<object>, IHasDiscriminatorConvention
1414
{
1515
private readonly ObjectSerializer _objectSerializer;
16-
private static readonly ConcurrentDictionary<Type, bool> _allowedTypes = new();
16+
private static readonly Dictionary<Type, bool> _allowedTypes = new();
1717
private static readonly HashSet<string> _allowedTypesByNamespaces = new();
1818
private static readonly HashSet<string> _allowedTypesByDependencies = new();
19+
private static readonly object _lock = new();
1920

2021
public TypeObjectSerializer()
2122
{
@@ -34,44 +35,62 @@ public static IReadOnlyCollection<string> AllowedTypesByDependencies
3435

3536
public static bool IsTypeAllowed(Type type)
3637
{
37-
return ObjectSerializer.DefaultAllowedTypes.Invoke(type) ||
38-
_allowedTypes.ContainsKey(type) ||
39-
IsInAllowedNamespaces(type) ||
40-
IsInAllowedDependencyTypes(type);
38+
lock (_lock)
39+
{
40+
return ObjectSerializer.DefaultAllowedTypes.Invoke(type) ||
41+
_allowedTypes.ContainsKey(type) ||
42+
IsInAllowedNamespaces(type) ||
43+
IsInAllowedDependencyTypes(type);
44+
}
4145
}
4246

4347
public static void AddAllowedType<T>()
4448
{
45-
_allowedTypes.TryAdd(typeof(T), true);
49+
lock (_lock)
50+
{
51+
_allowedTypes.Add(typeof(T), true);
52+
}
4653
}
4754

4855
public static void AddAllowedTypes(params Type[] allowedTypes)
4956
{
50-
foreach (Type allowedType in allowedTypes)
57+
lock (_lock)
5158
{
52-
_allowedTypes.TryAdd(allowedType, true);
59+
foreach (Type allowedType in allowedTypes)
60+
{
61+
_allowedTypes.Add(allowedType, true);
62+
}
5363
}
5464
}
5565

5666
public static void AddAllowedTypes(params string[] allowedNamespaces)
5767
{
58-
foreach (string allowedNamespace in allowedNamespaces)
68+
lock (_lock)
5969
{
60-
_allowedTypesByNamespaces.Add(allowedNamespace);
70+
foreach (string allowedNamespace in allowedNamespaces)
71+
{
72+
_allowedTypesByNamespaces.Add(allowedNamespace);
73+
}
6174
}
6275
}
6376

6477
public static void AddAllowedTypesOfAllDependencies(params string[] excludeNamespaces)
6578
{
66-
_allowedTypesByDependencies
67-
.UnionWith(DependencyTypesResolver.GetAllowedTypesByDependencies(excludeNamespaces));
79+
lock (_lock)
80+
{
81+
_allowedTypesByDependencies
82+
.UnionWith(DependencyTypesResolver.GetAllowedTypesByDependencies(excludeNamespaces));
83+
}
6884
}
6985

7086
internal static void Clear()
7187
{
72-
_allowedTypes.Clear();
73-
_allowedTypesByNamespaces.Clear();
74-
_allowedTypesByDependencies.Clear();
88+
lock (_lock)
89+
{
90+
_allowedTypes.Clear();
91+
_allowedTypesByNamespaces.Clear();
92+
_allowedTypesByDependencies.Clear();
93+
}
7594
}
7695

7796
private static bool IsInAllowedNamespaces(Type type)
@@ -85,7 +104,7 @@ private static bool IsInAllowedNamespaces(Type type)
85104

86105
if (isInAllowedNamespaces)
87106
{
88-
_allowedTypes.TryAdd(type, true);
107+
_allowedTypes.Add(type, true);
89108
}
90109

91110
return isInAllowedNamespaces;
@@ -115,7 +134,7 @@ private static bool IsInAllowedDependencyTypes(Type type)
115134
bool isInDependencyTypes = _allowedTypesByDependencies
116135
.Contains(type.GetRootNamespace());
117136

118-
_allowedTypes.TryAdd(type, isInDependencyTypes);
137+
_allowedTypes.Add(type, isInDependencyTypes);
119138

120139
return isInDependencyTypes;
121140
}

0 commit comments

Comments
 (0)