Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register BsonClassMap by type and support abstract Record #95

Merged
merged 8 commits into from
Jan 29, 2025
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
9 changes: 6 additions & 3 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<ItemGroup>
<PackageVersion Include="MongoDB.Driver" Version="3.0.0" />
<PackageVersion Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.0.0" />
<PackageVersion Include="System.Text.Json" Version="7.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
Expand Down Expand Up @@ -49,4 +49,4 @@
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="2.2.0" />
</ItemGroup>
</Project>
</Project>
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.301",
"version": "8.0.402",
"rollForward": "latestFeature"
}
}
23 changes: 23 additions & 0 deletions src/Context.Tests/ImmutableConventionWithRecordsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MongoResource>
{
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<T>(MongoDbContextData context, T input) where T : class
Expand Down
2 changes: 1 addition & 1 deletion src/Context.Tests/Internal/DependencyTypesResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public void GetAllowedTypesByDependencies_All_Successful()

// Act
IEnumerable<string> knownNamespaces = DependencyTypesResolver
.GetAllowedTypesByDependencies(new[] { "Coverlet" })
.GetAllowedTypesByDependencies(new[] { "Coverlet", "Castle" })
.OrderBy(x => x);

// Assert
Expand Down
16 changes: 16 additions & 0 deletions src/Context.Tests/MongoCollectionBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Order>(_mongoDatabase);

// Act
mongoCollectionBuilder.AddBsonClassMap(typeof(Order));
IMongoCollection<Order> result = mongoCollectionBuilder.Build();

// Assert
Assert.NotNull(result);
Assert.True(BsonClassMap.IsClassMapRegistered(typeof(Order)));
}

private class ItemClassMapNotRegistered
{
public int Id { get; set; }
Expand Down
4 changes: 4 additions & 0 deletions src/Context/IMongoCollectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ IMongoCollectionBuilder<TDocument> AddBsonClassMap<TMapDocument>(
Action<BsonClassMap<TMapDocument>> bsonClassMapAction)
where TMapDocument : class;

IMongoCollectionBuilder<TDocument> AddBsonClassMap(
Type type,
Action<BsonClassMap>? bsonClassMapAction = default);

IMongoCollectionBuilder<TDocument> WithCollectionSettings(
Action<MongoCollectionSettings> collectionSettings);

Expand Down
15 changes: 12 additions & 3 deletions src/Context/ImmutableConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand All @@ -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(
Expand All @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions src/Context/Internal/MongoCollectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ public IMongoCollectionBuilder<TDocument> AddBsonClassMap<TMapDocument>()
return this;
}

public IMongoCollectionBuilder<TDocument> AddBsonClassMap(
Type type,
Action<BsonClassMap>? bsonClassMapAction = default)
{
_classMapActions.Add(() =>
RegisterClassMapSync(type, bsonClassMapAction));

return this;
}

public IMongoCollectionBuilder<TDocument> WithCollectionSettings(
Action<MongoCollectionSettings> collectionSettings)
{
Expand Down Expand Up @@ -103,6 +113,21 @@ private void RegisterClassMapSync<TMapDocument>(
}
}

private void RegisterClassMapSync(
Type type,
Action<BsonClassMap>? bsonClassMapAction)
{
lock (_lockObject)
{
if (!BsonClassMap.IsClassMapRegistered(type))
{
var classMap = new BsonClassMap(type);
bsonClassMapAction?.Invoke(classMap);
BsonClassMap.RegisterClassMap(classMap);
}
}
}

private void RegisterClassMapSync<TMapDocument>()
where TMapDocument : class
{
Expand Down
53 changes: 36 additions & 17 deletions src/Context/Internal/TypeObjectSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ namespace MongoDB.Extensions.Context.Internal;
internal class TypeObjectSerializer : ClassSerializerBase<object>, IHasDiscriminatorConvention
{
private readonly ObjectSerializer _objectSerializer;
private static readonly ConcurrentDictionary<Type, bool> _allowedTypes = new();
private static readonly Dictionary<Type, bool> _allowedTypes = new();
private static readonly HashSet<string> _allowedTypesByNamespaces = new();
private static readonly HashSet<string> _allowedTypesByDependencies = new();
private static readonly object _lock = new();

public TypeObjectSerializer()
{
Expand All @@ -34,44 +35,62 @@ public static IReadOnlyCollection<string> 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<T>()
{
_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)
Expand All @@ -85,7 +104,7 @@ private static bool IsInAllowedNamespaces(Type type)

if (isInAllowedNamespaces)
{
_allowedTypes.TryAdd(type, true);
_allowedTypes.Add(type, true);
}

return isInAllowedNamespaces;
Expand Down Expand Up @@ -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;
}
Expand Down
Loading