diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..c545a099
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,28 @@
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+# C# files
+[*.cs]
+
+#### Suppress warnings ####
+
+# CA1034: Nested types should not be visible
+dotnet_diagnostic.CA1034.severity = none
+
+# CA1063: Implement IDisposable Correctly
+dotnet_diagnostic.CA1063.severity = none
+
+# CA1303: Do not pass literals as localized parameters
+dotnet_diagnostic.CA1303.severity = none # Don't need translated exceptions
+
+# CA1707: Identifiers should not contain underscores
+dotnet_diagnostic.CA1707.severity = none # I want to use underscore in constants
+
+# CA1812: Avoid uninstantiated internal classes
+dotnet_diagnostic.CA1812.severity = none # Doing extensive use of dependency injection
+
+# CA1816: Dispose methods should call SuppressFinalize
+dotnet_diagnostic.CA1816.severity = none
+
+# CA2225: Operator overloads have named alternates
+dotnet_diagnostic.CA2225.severity = none
diff --git a/.github/workflows/myget-feature-deploy.yml b/.github/workflows/myget-feature-deploy.yml
deleted file mode 100644
index d0b1879c..00000000
--- a/.github/workflows/myget-feature-deploy.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-name: Build and package feature branch. Deploy to MyGet
-
-on:
- push:
- branches:
- - 'feature/**'
-
-jobs:
- build-test-package:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@master
- with:
- fetch-depth: 0
-
- - name: Setup .NET Core SDK
- uses: actions/setup-dotnet@master
- with:
- dotnet-version: '3.1.x'
-
- - name: Build with dotnet
- run: dotnet build --configuration Release
-
- - name: Generate nuget package
- run: dotnet pack --configuration Release -o nupkg
-
- - name: Push packages
- run: dotnet nuget push './nupkg/*.nupkg' --api-key ${{secrets.MYGET_APIKEY}} --source https://www.myget.org/F/etherna/api/v3/index.json
diff --git a/.github/workflows/myget-stable-deploy.yml b/.github/workflows/myget-unstable-deploy.yml
similarity index 87%
rename from .github/workflows/myget-stable-deploy.yml
rename to .github/workflows/myget-unstable-deploy.yml
index a8346b09..fe2b5bde 100644
--- a/.github/workflows/myget-stable-deploy.yml
+++ b/.github/workflows/myget-unstable-deploy.yml
@@ -1,8 +1,10 @@
-name: Build test and package dev and master branch. Deploy to MyGet
+name: Unstable release deploy to MyGet
on:
push:
- branches: [ master, dev ]
+ branches:
+ - dev
+ - 'release/**'
jobs:
build-test-package:
diff --git a/.github/workflows/nuget-release-deploy.yml b/.github/workflows/nuget-stable-deploy.yml
similarity index 89%
rename from .github/workflows/nuget-release-deploy.yml
rename to .github/workflows/nuget-stable-deploy.yml
index c5fd3b0f..ef2dc40f 100644
--- a/.github/workflows/nuget-release-deploy.yml
+++ b/.github/workflows/nuget-stable-deploy.yml
@@ -1,8 +1,9 @@
-name: Build test and package master branch. Deploy to NuGet
+name: Stable release deploy to NuGet
on:
push:
- branches: master
+ branches:
+ - master
jobs:
build-test-package:
diff --git a/.gitignore b/.gitignore
index c395abfd..28f83ffa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,6 +160,7 @@ publish/
# NuGet Packages
*.nupkg
+*.snupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
diff --git a/MongODM.sln b/MongODM.sln
index e3a97b2e..cfd11552 100644
--- a/MongODM.sln
+++ b/MongODM.sln
@@ -21,16 +21,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MongODM.Core.Tests", "test\
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{03C64D98-FF9F-4760-AE82-203953FF4940}"
ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
.gitignore = .gitignore
LICENSE.txt = LICENSE.txt
MongODM.sln.licenseheader = MongODM.sln.licenseheader
+ README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{D4BB5972-5F7B-43ED-81C1-69ECF0E62D1E}"
ProjectSection(SolutionItems) = preProject
- .github\workflows\myget-feature-deploy.yml = .github\workflows\myget-feature-deploy.yml
- .github\workflows\myget-stable-deploy.yml = .github\workflows\myget-stable-deploy.yml
- .github\workflows\nuget-release-deploy.yml = .github\workflows\nuget-release-deploy.yml
+ .github\workflows\myget-unstable-deploy.yml = .github\workflows\myget-unstable-deploy.yml
+ .github\workflows\nuget-stable-deploy.yml = .github\workflows\nuget-stable-deploy.yml
EndProjectSection
EndProject
Global
diff --git a/README.md b/README.md
index 5a29733c..5e9eed82 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-MongODM
+MongODM
=========
## Overview
diff --git a/src/ExecutionContext/ExecutionContext.csproj b/src/ExecutionContext/ExecutionContext.csproj
index 98695310..d327b750 100644
--- a/src/ExecutionContext/ExecutionContext.csproj
+++ b/src/ExecutionContext/ExecutionContext.csproj
@@ -8,7 +8,12 @@
Execution context provider
8.0
enable
+
https://github.com/Etherna/mongodm
+ git
+ true
+ true
+ snupkg
LICENSE.txt
@@ -21,6 +26,10 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/src/MongODM.AspNetCore/MongODM.AspNetCore.csproj b/src/MongODM.AspNetCore/MongODM.AspNetCore.csproj
index 5fccab9a..516bd6e8 100644
--- a/src/MongODM.AspNetCore/MongODM.AspNetCore.csproj
+++ b/src/MongODM.AspNetCore/MongODM.AspNetCore.csproj
@@ -8,7 +8,12 @@
Asp.Net Core adapter for MongODM
8.0
enable
+
https://github.com/Etherna/mongodm
+ git
+ true
+ true
+ snupkg
LICENSE.txt
@@ -31,6 +36,10 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs
index 2ae9f8e4..595dfb61 100644
--- a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs
+++ b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs
@@ -16,6 +16,7 @@
using Etherna.ExecContext.AsyncLocal;
using Etherna.MongODM;
using Etherna.MongODM.AspNetCore;
+using Etherna.MongODM.Models;
using Etherna.MongODM.ProxyModels;
using Etherna.MongODM.Repositories;
using Etherna.MongODM.Serialization;
@@ -32,17 +33,21 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{
- public static MongODMConfiguration UseMongODM(
+ public static MongODMConfiguration UseMongODM(
this IServiceCollection services,
IEnumerable? executionContexts = null)
- where TTaskRunner : class, ITaskRunner =>
- UseMongODM(services, executionContexts);
+ where TTaskRunner : class, ITaskRunner
+ where TModelBase : class, IModel => //needed because of this https://jira.mongodb.org/browse/CSHARP-3154
+ UseMongODM(
+ services,
+ executionContexts);
- public static MongODMConfiguration UseMongODM(
+ public static MongODMConfiguration UseMongODM(
this IServiceCollection services,
IEnumerable? executionContexts = null)
- where TProxyGenerator: class, IProxyGenerator
+ where TProxyGenerator : class, IProxyGenerator
where TTaskRunner: class, ITaskRunner
+ where TModelBase: class, IModel //needed because of this https://jira.mongodb.org/browse/CSHARP-3154
{
services.TryAddSingleton();
@@ -70,18 +75,23 @@ public static MongODMConfiguration UseMongODM(
* the same dbContext different components could have different instances of the same component.
*/
services.TryAddTransient();
- services.TryAddTransient();
+ services.TryAddTransient();
services.TryAddTransient();
+ services.TryAddTransient();
services.TryAddTransient();
services.TryAddTransient();
services.TryAddSingleton();
//tasks
+ services.TryAddTransient();
services.TryAddTransient();
- //castle proxy generator.
+ //castle proxy generator
services.TryAddSingleton(new Castle.DynamicProxy.ProxyGenerator());
+ //static configurations
+ services.TryAddSingleton>();
+
return new MongODMConfiguration(services);
}
diff --git a/src/MongODM.AspNetCore/StaticConfigurationBuilder.cs b/src/MongODM.AspNetCore/StaticConfigurationBuilder.cs
new file mode 100644
index 00000000..39531480
--- /dev/null
+++ b/src/MongODM.AspNetCore/StaticConfigurationBuilder.cs
@@ -0,0 +1,41 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Etherna.MongODM.Conventions;
+using Etherna.MongODM.Models.Internal;
+using Etherna.MongODM.ProxyModels;
+using Etherna.MongODM.Utility;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Conventions;
+
+namespace Etherna.MongODM.AspNetCore
+{
+ public class StaticConfigurationBuilder : IStaticConfigurationBuilder
+ {
+ public StaticConfigurationBuilder(IProxyGenerator proxyGenerator)
+ {
+ // Register conventions.
+ ConventionRegistry.Register("Enum string", new ConventionPack
+ {
+ new EnumRepresentationConvention(BsonType.String)
+ }, c => true);
+
+ BsonSerializer.RegisterDiscriminatorConvention(typeof(TModelBase),
+ new HierarchicalProxyTolerantDiscriminatorConvention("_t", proxyGenerator));
+ BsonSerializer.RegisterDiscriminatorConvention(typeof(EntityModelBase),
+ new HierarchicalProxyTolerantDiscriminatorConvention("_t", proxyGenerator));
+ }
+ }
+}
diff --git a/src/MongODM.Core/Conventions/HierarchicalProxyTolerantDiscriminatorConvention.cs b/src/MongODM.Core/Conventions/HierarchicalProxyTolerantDiscriminatorConvention.cs
new file mode 100644
index 00000000..9fb64f3b
--- /dev/null
+++ b/src/MongODM.Core/Conventions/HierarchicalProxyTolerantDiscriminatorConvention.cs
@@ -0,0 +1,40 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Etherna.MongODM.ProxyModels;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Conventions;
+using System;
+
+namespace Etherna.MongODM.Conventions
+{
+ public class HierarchicalProxyTolerantDiscriminatorConvention : HierarchicalDiscriminatorConvention
+ {
+ // Fields.
+ private readonly IProxyGenerator proxyGenerator;
+
+ // Constructors.
+ public HierarchicalProxyTolerantDiscriminatorConvention(
+ string elementName,
+ IProxyGenerator proxyGenerator)
+ : base(elementName)
+ {
+ this.proxyGenerator = proxyGenerator ?? throw new ArgumentNullException(nameof(proxyGenerator));
+ }
+
+ // Methods.
+ public override BsonValue GetDiscriminator(Type nominalType, Type actualType) =>
+ base.GetDiscriminator(nominalType, proxyGenerator.PurgeProxyType(actualType));
+ }
+}
diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs
index a3ff89c1..7161a21a 100644
--- a/src/MongODM.Core/DbContext.cs
+++ b/src/MongODM.Core/DbContext.cs
@@ -14,17 +14,19 @@
using Etherna.MongODM.Migration;
using Etherna.MongODM.Models;
+using Etherna.MongODM.Models.Internal;
+using Etherna.MongODM.Models.Internal.ModelMaps;
using Etherna.MongODM.ProxyModels;
using Etherna.MongODM.Repositories;
using Etherna.MongODM.Serialization;
using Etherna.MongODM.Serialization.Modifiers;
using Etherna.MongODM.Utility;
-using MongoDB.Bson;
-using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Driver;
+using MongoDB.Driver.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -37,80 +39,84 @@ public abstract class DbContext : IDbContext
// Constructors and initialization.
public DbContext(
- IDbContextDependencies dependencies,
+ IDbDependencies dependencies,
DbContextOptions options)
{
- DBCache = dependencies.DbCache;
- DBMaintainer = dependencies.DbMaintainer;
+ if (dependencies is null)
+ throw new ArgumentNullException(nameof(dependencies));
+ if (options is null)
+ throw new ArgumentNullException(nameof(options));
+
+ ApplicationVersion = options.ApplicationVersion;
+ DbCache = dependencies.DbCache;
+ DbMaintainer = dependencies.DbMaintainer;
+ DbMigrationManager = dependencies.DbMigrationManager;
+ DbOperations = new CollectionRepository(options.DbOperationsCollectionName);
DocumentSchemaRegister = dependencies.DocumentSchemaRegister;
- DocumentVersion = options.DocumentVersion;
+ Identifier = options.Identifier ?? GetType().Name;
+ LibraryVersion = GetType()
+ .GetTypeInfo()
+ .Assembly
+ .GetCustomAttribute()
+ ?.InformationalVersion
+ ?.Split('+')[0] ?? "1.0.0";
ProxyGenerator = dependencies.ProxyGenerator;
RepositoryRegister = dependencies.RepositoryRegister;
SerializerModifierAccessor = dependencies.SerializerModifierAccessor;
// Initialize MongoDB driver.
Client = new MongoClient(options.ConnectionString);
- Database = Client.GetDatabase(options.DBName);
+ Database = Client.GetDatabase(options.DbName);
// Initialize internal dependencies.
+ DbMaintainer.Initialize(this);
+ DbMigrationManager.Initialize(this);
DocumentSchemaRegister.Initialize(this);
- DBMaintainer.Initialize(this);
RepositoryRegister.Initialize(this);
// Initialize repositories.
foreach (var repository in RepositoryRegister.ModelRepositoryMap.Values)
repository.Initialize(this);
- // Customize conventions.
- ConventionRegistry.Register("Enum string", new ConventionPack
- {
- new EnumRepresentationConvention(BsonType.String)
- }, c => true);
+ // Register model maps.
+ //internal maps
+ new DbMigrationOperationMap().Register(this);
+ new ModelBaseMap().Register(this);
+ new OperationBaseMap().Register(this);
+ new SeedOperationMap().Register(this);
- // Register serializers.
- foreach (var serializerCollector in SerializerCollectors)
- serializerCollector.Register(this);
+ //application maps
+ foreach (var maps in ModelMapsCollectors)
+ maps.Register(this);
// Build and freeze document schema register.
DocumentSchemaRegister.Freeze();
}
// Public properties.
+ public SemanticVersion ApplicationVersion { get; }
public IReadOnlyCollection ChangedModelsList =>
- DBCache.LoadedModels.Values
+ DbCache.LoadedModels.Values
.Where(model => (model as IAuditable)?.IsChanged == true)
.ToList();
public IMongoClient Client { get; }
public IMongoDatabase Database { get; }
- public IDbCache DBCache { get; }
- public IDbMaintainer DBMaintainer { get; }
+ public IDbCache DbCache { get; }
+ public IDbMaintainer DbMaintainer { get; }
+ public IDbMigrationManager DbMigrationManager { get; }
+ public ICollectionRepository DbOperations { get; }
+ public virtual IEnumerable DocumentMigrationList { get; } = Array.Empty();
public IDocumentSchemaRegister DocumentSchemaRegister { get; }
- public DocumentVersion DocumentVersion { get; }
- public bool IsMigrating { get; private set; }
+ public string Identifier { get; }
+ public SemanticVersion LibraryVersion { get; }
public IProxyGenerator ProxyGenerator { get; }
public IRepositoryRegister RepositoryRegister { get; }
public ISerializerModifierAccessor SerializerModifierAccessor { get; }
// Protected properties.
- protected virtual IEnumerable MigrationTaskList { get; } = Array.Empty();
- protected abstract IEnumerable SerializerCollectors { get; }
+ protected abstract IEnumerable ModelMapsCollectors { get; }
// Methods.
- public async Task MigrateRepositoriesAsync(CancellationToken cancellationToken = default)
- {
- IsMigrating = true;
-
- // Migrate collections.
- foreach (var migration in MigrationTaskList)
- await migration.MigrateAsync(cancellationToken);
-
- // Build indexes.
- foreach (var repository in RepositoryRegister.ModelCollectionRepositoryMap.Values)
- await repository.BuildIndexesAsync(DocumentSchemaRegister, cancellationToken);
-
- IsMigrating = false;
- }
-
public virtual async Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
/*
@@ -146,16 +152,46 @@ public virtual async Task SaveChangesAsync(CancellationToken cancellationToken =
// Commit updated models replacement.
foreach (var model in ChangedModelsList)
{
- var modelType = model.GetType().BaseType;
- if (RepositoryRegister.ModelCollectionRepositoryMap.ContainsKey(modelType)) //can't replace if is a file
+ var modelType = ProxyGenerator.PurgeProxyType(model.GetType());
+ while (modelType != typeof(object)) //try to find right collection. Can't replace model if it is stored on gridfs
{
- var repository = RepositoryRegister.ModelCollectionRepositoryMap[modelType];
- await repository.ReplaceAsync(model);
+ if (RepositoryRegister.ModelCollectionRepositoryMap.ContainsKey(modelType))
+ {
+ var repository = RepositoryRegister.ModelCollectionRepositoryMap[modelType];
+ await repository.ReplaceAsync(model).ConfigureAwait(false);
+ break;
+ }
+ else
+ {
+ modelType = modelType.BaseType;
+ }
}
}
}
+ public async Task SeedIfNeededAsync()
+ {
+ // Check if already seeded.
+ if (await DbOperations.QueryElementsAsync(elements =>
+ elements.OfType()
+ .AnyAsync(sop => sop.DbContextName == Identifier)).ConfigureAwait(false))
+ return false;
+
+ // Seed.
+ await SeedAsync().ConfigureAwait(false);
+
+ // Report operation.
+ var seedOperation = new SeedOperation(this);
+ await DbOperations.CreateAsync(seedOperation).ConfigureAwait(false);
+
+ return true;
+ }
+
public Task StartSessionAsync(CancellationToken cancellationToken = default) =>
Client.StartSessionAsync(cancellationToken: cancellationToken);
+
+ // Protected methods.
+ protected virtual Task SeedAsync() =>
+ Task.CompletedTask;
}
}
\ No newline at end of file
diff --git a/src/MongODM.Core/DbContextOptions.cs b/src/MongODM.Core/DbContextOptions.cs
index f1d4375a..91aaf9be 100644
--- a/src/MongODM.Core/DbContextOptions.cs
+++ b/src/MongODM.Core/DbContextOptions.cs
@@ -19,10 +19,12 @@ namespace Etherna.MongODM
{
public class DbContextOptions
{
+ public SemanticVersion ApplicationVersion { get; set; } = "1.0.0";
public string ConnectionString { get; set; } = "mongodb://localhost/localDb";
- public string DBName => ConnectionString.Split('?')[0]
+ public string DbName => ConnectionString.Split('?')[0]
.Split('/').Last();
- public DocumentVersion DocumentVersion { get; set; } = "1.0.0";
+ public string DbOperationsCollectionName { get; set; } = "_db_ops";
+ public string? Identifier { get; set; }
}
public class DbContextOptions : DbContextOptions
diff --git a/src/MongODM.Core/Exceptions/InvalidEntityTypeException.cs b/src/MongODM.Core/Exceptions/InvalidEntityTypeException.cs
index f57a0dce..94eea405 100644
--- a/src/MongODM.Core/Exceptions/InvalidEntityTypeException.cs
+++ b/src/MongODM.Core/Exceptions/InvalidEntityTypeException.cs
@@ -23,5 +23,8 @@ public InvalidEntityTypeException()
public InvalidEntityTypeException(string message) : base(message)
{ }
+
+ public InvalidEntityTypeException(string message, Exception innerException) : base(message, innerException)
+ { }
}
}
diff --git a/src/MongODM.Core/Extensions/ClassMapExtensions.cs b/src/MongODM.Core/Extensions/ClassMapExtensions.cs
index ab18f7a8..2d1501ac 100644
--- a/src/MongODM.Core/Extensions/ClassMapExtensions.cs
+++ b/src/MongODM.Core/Extensions/ClassMapExtensions.cs
@@ -28,6 +28,9 @@ public static BsonMemberMap SetMemberSerializer(
IBsonSerializer serializer)
where TMember : class
{
+ if (classMap is null)
+ throw new ArgumentNullException(nameof(classMap));
+
var member = classMap.GetMemberMap(memberLambda);
if (member == null)
member = classMap.MapMember(memberLambda);
@@ -41,6 +44,9 @@ public static BsonMemberMap SetMemberSerializer
{
+ if (serializer is null)
+ throw new ArgumentNullException(nameof(serializer));
+
if (typeof(TMember) == typeof(TSerializer))
return classMap.SetMemberSerializer(memberLambda, (IBsonSerializer)serializer);
else
diff --git a/src/MongODM.Core/IDbContext.cs b/src/MongODM.Core/IDbContext.cs
index f37de024..e0fce431 100644
--- a/src/MongODM.Core/IDbContext.cs
+++ b/src/MongODM.Core/IDbContext.cs
@@ -12,12 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+using Etherna.MongODM.Migration;
+using Etherna.MongODM.Models.Internal;
using Etherna.MongODM.ProxyModels;
using Etherna.MongODM.Repositories;
using Etherna.MongODM.Serialization;
using Etherna.MongODM.Serialization.Modifiers;
using Etherna.MongODM.Utility;
using MongoDB.Driver;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -28,6 +31,11 @@ namespace Etherna.MongODM
///
public interface IDbContext
{
+ ///
+ /// Current application version.
+ ///
+ SemanticVersion ApplicationVersion { get; }
+
// Properties.
///
/// Current MongoDB client.
@@ -42,28 +50,43 @@ public interface IDbContext
///
/// Database cache container.
///
- IDbCache DBCache { get; }
+ IDbCache DbCache { get; }
///
/// Database operator interested into maintenance tasks.
///
- IDbMaintainer DBMaintainer { get; }
-
+ IDbMaintainer DbMaintainer { get; }
+
+ ///
+ /// Manage migrations over database context
+ ///
+ IDbMigrationManager DbMigrationManager { get; }
+
+ ///
+ /// Internal collection for keep db operations execution log
+ ///
+ ICollectionRepository DbOperations { get; }
+
+ ///
+ /// List of registered migration tasks
+ ///
+ IEnumerable DocumentMigrationList { get; }
+
///
/// Container for model serialization and document schema information.
///
IDocumentSchemaRegister DocumentSchemaRegister { get; }
-
+
///
- /// Current operating document version.
+ /// DbContext unique identifier.
///
- DocumentVersion DocumentVersion { get; }
-
+ string Identifier { get; }
+
///
- /// Flag reporting eventual current migration operation.
+ /// Current MongODM library version
///
- bool IsMigrating { get; }
-
+ SemanticVersion LibraryVersion { get; }
+
///
/// Current model proxy generator.
///
@@ -81,16 +104,16 @@ public interface IDbContext
// Methods.
///
- /// Start a database migration process.
+ /// Save current model changes on db.
///
/// Cancellation token
- Task MigrateRepositoriesAsync(CancellationToken cancellationToken = default);
+ Task SaveChangesAsync(CancellationToken cancellationToken = default);
///
- /// Save current model changes on db.
+ /// Seed database context if still not seeded
///
- /// Cancellation token
- Task SaveChangesAsync(CancellationToken cancellationToken = default);
+ /// True if seed has been executed. False otherwise
+ Task SeedIfNeededAsync();
///
/// Start a new database transaction session.
diff --git a/src/MongODM.Core/Migration/MigrationResult.cs b/src/MongODM.Core/Migration/MigrationResult.cs
new file mode 100644
index 00000000..58192c0f
--- /dev/null
+++ b/src/MongODM.Core/Migration/MigrationResult.cs
@@ -0,0 +1,40 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace Etherna.MongODM.Migration
+{
+ public class MigrationResult
+ {
+ // Constructors.
+ private MigrationResult() { }
+
+ // Properties.
+ public bool Succeded { get; private set; }
+ public long MigratedDocuments { get; private set; }
+
+ // Methods.
+ public static MigrationResult Failed() =>
+ new MigrationResult
+ {
+ Succeded = false
+ };
+
+ public static MigrationResult Succeeded(long migratedDocuments) =>
+ new MigrationResult
+ {
+ Succeded = true,
+ MigratedDocuments = migratedDocuments
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/MongODM.Core/Migration/MongoCollectionMigration.cs b/src/MongODM.Core/Migration/MongoCollectionMigration.cs
index 9d550826..a081d53c 100644
--- a/src/MongODM.Core/Migration/MongoCollectionMigration.cs
+++ b/src/MongODM.Core/Migration/MongoCollectionMigration.cs
@@ -32,29 +32,61 @@ public class MongoCollectionMigration
where TModelDest : class, IEntityModel
{
+ // Fields.
private readonly Func converter;
private readonly Func discriminator;
private readonly IMongoCollection destinationCollection;
- private readonly IMongoCollection sourceCollection;
+ private readonly ICollectionRepository _sourceCollection;
+ // Constructor.
public MongoCollectionMigration(
ICollectionRepository sourceCollection,
ICollectionRepository destinationCollection,
Func converter,
- Func discriminator)
+ Func discriminator,
+ string id)
+ : base(id)
{
- this.sourceCollection = sourceCollection.Collection;
+ if (sourceCollection is null)
+ throw new ArgumentNullException(nameof(sourceCollection));
+ if (destinationCollection is null)
+ throw new ArgumentNullException(nameof(destinationCollection));
+
+ _sourceCollection = sourceCollection;
this.destinationCollection = destinationCollection.Collection;
this.converter = converter;
this.discriminator = discriminator;
}
- public override Task MigrateAsync(CancellationToken cancellationToken = default) =>
- sourceCollection.Find(Builders.Filter.Empty, new FindOptions { NoCursorTimeout = true })
- .ForEachAsync(obj =>
+ // Properties.
+ public override ICollectionRepository SourceCollection => _sourceCollection;
+
+ // Methods.
+ public override async Task MigrateAsync(
+ int callbackEveryDocuments = 0,
+ Func? callbackAsync = null,
+ CancellationToken cancellationToken = default)
+ {
+ if (callbackEveryDocuments < 0)
+ throw new ArgumentOutOfRangeException(nameof(callbackEveryDocuments), "Value can't be negative");
+
+ // Migrate documents.
+ var totMigratedDocuments = 0L;
+ await _sourceCollection.Collection.Find(Builders.Filter.Empty, new FindOptions { NoCursorTimeout = true })
+ .ForEachAsync(async model =>
{
- if (discriminator(obj))
- destinationCollection.InsertOneAsync(converter(obj));
- }, cancellationToken);
+ if (callbackEveryDocuments > 0 &&
+ totMigratedDocuments % callbackEveryDocuments == 0 &&
+ callbackAsync != null)
+ await callbackAsync.Invoke(totMigratedDocuments).ConfigureAwait(false);
+
+ if (discriminator(model))
+ await destinationCollection.InsertOneAsync(converter(model)).ConfigureAwait(false);
+
+ totMigratedDocuments++;
+ }, cancellationToken).ConfigureAwait(false);
+
+ return MigrationResult.Succeeded(totMigratedDocuments);
+ }
}
}
diff --git a/src/MongODM.Core/Migration/MongoDocumentMigration.cs b/src/MongODM.Core/Migration/MongoDocumentMigration.cs
index 9b3d854a..6a79adc8 100644
--- a/src/MongODM.Core/Migration/MongoDocumentMigration.cs
+++ b/src/MongODM.Core/Migration/MongoDocumentMigration.cs
@@ -17,6 +17,7 @@
using Etherna.MongODM.Serialization;
using MongoDB.Bson;
using MongoDB.Driver;
+using System;
using System.Threading;
using System.Threading.Tasks;
@@ -30,22 +31,37 @@ namespace Etherna.MongODM.Migration
public class MongoDocumentMigration : MongoMigrationBase
where TModel : class, IEntityModel
{
- private readonly DocumentVersion minimumDocumentVersion;
- private readonly IMongoCollection sourceCollection;
+ // Fields.
+ private readonly SemanticVersion minimumDocumentVersion;
+ private readonly ICollectionRepository _sourceCollection;
+ // Constructors.
public MongoDocumentMigration(
ICollectionRepository sourceCollection,
- DocumentVersion minimumDocumentVersion)
+ SemanticVersion minimumDocumentVersion,
+ string id)
+ : base(id)
{
- this.sourceCollection = sourceCollection.Collection;
+ if (sourceCollection is null)
+ throw new ArgumentNullException(nameof(sourceCollection));
+
+ _sourceCollection = sourceCollection;
this.minimumDocumentVersion = minimumDocumentVersion;
}
- ///
- /// Fix all documents prev of MinimumDocumentVersion
- ///
- public override async Task MigrateAsync(CancellationToken cancellationToken = default)
+ // Properties.
+ public override ICollectionRepository SourceCollection => _sourceCollection;
+
+ // Methods.
+ public override async Task MigrateAsync(
+ int callbackEveryDocuments = 0,
+ Func? callbackAsync = null,
+ CancellationToken cancellationToken = default)
{
+ if (callbackEveryDocuments < 0)
+ throw new ArgumentOutOfRangeException(nameof(callbackEveryDocuments), "Value can't be negative");
+
+ // Building filter.
var filterBuilder = Builders.Filter;
var filter = filterBuilder.Or(
// No version in document (very old).
@@ -70,9 +86,22 @@ public override async Task MigrateAsync(CancellationToken cancellationToken = de
filterBuilder.Eq($"{DbContext.DocumentVersionElementName}.1", minimumDocumentVersion.MinorRelease),
filterBuilder.Lt($"{DbContext.DocumentVersionElementName}.2", minimumDocumentVersion.PatchRelease)));
- // Replace documents.
- await sourceCollection.Find(filter, new FindOptions { NoCursorTimeout = true })
- .ForEachAsync(obj => sourceCollection.ReplaceOneAsync(Builders.Filter.Eq(m => m.Id, obj.Id), obj), cancellationToken);
+ // Migrate documents.
+ var totMigratedDocuments = 0L;
+ await _sourceCollection.Collection.Find(filter, new FindOptions { NoCursorTimeout = true })
+ .ForEachAsync(async model =>
+ {
+ if (callbackEveryDocuments > 0 &&
+ totMigratedDocuments % callbackEveryDocuments == 0 &&
+ callbackAsync != null)
+ await callbackAsync.Invoke(totMigratedDocuments).ConfigureAwait(false);
+
+ await _sourceCollection.Collection.ReplaceOneAsync(Builders.Filter.Eq(m => m.Id, model.Id), model).ConfigureAwait(false);
+
+ totMigratedDocuments++;
+ }, cancellationToken).ConfigureAwait(false);
+
+ return MigrationResult.Succeeded(totMigratedDocuments);
}
}
}
diff --git a/src/MongODM.Core/Migration/MongoMigrationBase.cs b/src/MongODM.Core/Migration/MongoMigrationBase.cs
index 30d84690..2a54bdcd 100644
--- a/src/MongODM.Core/Migration/MongoMigrationBase.cs
+++ b/src/MongODM.Core/Migration/MongoMigrationBase.cs
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+using Etherna.MongODM.Repositories;
+using System;
using System.Threading;
using System.Threading.Tasks;
@@ -19,6 +21,23 @@ namespace Etherna.MongODM.Migration
{
public abstract class MongoMigrationBase
{
- public abstract Task MigrateAsync(CancellationToken cancellationToken = default);
+ // Constructors.
+ public MongoMigrationBase(string id)
+ {
+ Id = id ?? throw new ArgumentNullException(nameof(id));
+ }
+
+ // Properties.
+ public string Id { get; }
+ public abstract ICollectionRepository SourceCollection { get; }
+
+ // Methods.
+ ///
+ /// Perform migration with optional updating callback
+ ///
+ /// Interval of processed documents between callback invokations. 0 if ignore callback
+ /// The async callback function. Parameter is number of processed documents
+ /// The migration result
+ public abstract Task MigrateAsync(int callbackEveryDocuments = 0, Func? callbackAsync = null, CancellationToken cancellationToken = default);
}
}
diff --git a/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs
new file mode 100644
index 00000000..5f18d842
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs
@@ -0,0 +1,38 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace Etherna.MongODM.Models.Internal.DbMigrationOpAgg
+{
+ public class DocumentMigrationLog : MigrationLogBase
+ {
+ // Constructors.
+ public DocumentMigrationLog(
+ string collectionName,
+ string documentMigrationId,
+ ExecutionState state,
+ long totMigratedDocs)
+ : base(state)
+ {
+ CollectionName = collectionName;
+ DocumentMigrationId = documentMigrationId;
+ TotMigratedDocs = totMigratedDocs;
+ }
+ protected DocumentMigrationLog() { }
+
+ // Properties.
+ public virtual string CollectionName { get; protected set; } = default!;
+ public virtual string DocumentMigrationId { get; protected set; } = default!;
+ public virtual long TotMigratedDocs { get; protected set; }
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/IndexMigrationLog.cs b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/IndexMigrationLog.cs
new file mode 100644
index 00000000..b6cea61d
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/IndexMigrationLog.cs
@@ -0,0 +1,32 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace Etherna.MongODM.Models.Internal.DbMigrationOpAgg
+{
+ public class IndexMigrationLog : MigrationLogBase
+ {
+ // Constructors.
+ public IndexMigrationLog(
+ string repository,
+ ExecutionState state)
+ : base(state)
+ {
+ Repository = repository;
+ }
+ protected IndexMigrationLog() { }
+
+ // Properties.
+ public virtual string Repository { get; protected set; } = default!;
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/MigrationLogBase.cs b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/MigrationLogBase.cs
new file mode 100644
index 00000000..05390bd9
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/MigrationLogBase.cs
@@ -0,0 +1,42 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+
+namespace Etherna.MongODM.Models.Internal.DbMigrationOpAgg
+{
+ public abstract class MigrationLogBase : ModelBase
+ {
+ // Enums.
+ public enum ExecutionState
+ {
+ Executing,
+ Succeded,
+ Skipped,
+ Failed
+ }
+
+ // Constructors.
+ public MigrationLogBase(ExecutionState state)
+ {
+ CreationDateTime = DateTime.Now;
+ State = state;
+ }
+ protected MigrationLogBase() { }
+
+ // Properties.
+ public virtual ExecutionState State { get; protected set; }
+ public virtual DateTime CreationDateTime { get; protected set; }
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/DbMigrationOperation.cs b/src/MongODM.Core/Models/Internal/DbMigrationOperation.cs
new file mode 100644
index 00000000..8e01b456
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/DbMigrationOperation.cs
@@ -0,0 +1,100 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Etherna.MongODM.Attributes;
+using Etherna.MongODM.Models.Internal.DbMigrationOpAgg;
+using System;
+using System.Collections.Generic;
+
+namespace Etherna.MongODM.Models.Internal
+{
+ public class DbMigrationOperation : OperationBase
+ {
+ // Enums.
+ public enum Status
+ {
+ New,
+ Running,
+ Completed,
+ Cancelled
+ }
+
+ // Fields.
+ private List _logs = new List();
+
+ // Constructors.
+ public DbMigrationOperation(IDbContext dbContext, string? author)
+ : base(dbContext)
+ {
+ Author = author;
+ CurrentStatus = Status.New;
+ }
+ protected DbMigrationOperation() { }
+
+ // Properties.
+ public virtual string? Author { get; protected set; }
+ public virtual DateTime? CompletedDateTime { get; protected set; }
+ public virtual Status CurrentStatus { get; protected set; }
+ public virtual IEnumerable Logs
+ {
+ get => _logs;
+ protected set => _logs = new List(value ?? Array.Empty());
+ }
+ public virtual string? TaskId { get; protected set; }
+
+ // Methods.
+ [PropertyAlterer(nameof(Logs))]
+ public virtual void AddLog(MigrationLogBase log)
+ {
+ if (log is null)
+ throw new ArgumentNullException(nameof(log));
+
+ _logs.Add(log);
+ }
+
+ [PropertyAlterer(nameof(CurrentStatus))]
+ public virtual void TaskCancelled()
+ {
+ if (CurrentStatus == Status.Completed)
+ throw new InvalidOperationException();
+
+ CurrentStatus = Status.Cancelled;
+ }
+
+ [PropertyAlterer(nameof(CompletedDateTime))]
+ [PropertyAlterer(nameof(CurrentStatus))]
+ public virtual void TaskCompleted()
+ {
+ if (CurrentStatus != Status.Running)
+ throw new InvalidOperationException();
+
+ CompletedDateTime = DateTime.Now;
+ CurrentStatus = Status.Completed;
+ }
+
+ [PropertyAlterer(nameof(CurrentStatus))]
+ [PropertyAlterer(nameof(TaskId))]
+ public virtual void TaskStarted(string taskId)
+ {
+ if (taskId is null)
+ throw new ArgumentNullException(nameof(taskId));
+
+ if (CurrentStatus != Status.New)
+ throw new InvalidOperationException();
+
+ CurrentStatus = Status.Running;
+ TaskId = taskId;
+ }
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/EntityModelBase.cs b/src/MongODM.Core/Models/Internal/EntityModelBase.cs
new file mode 100644
index 00000000..27a76f67
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/EntityModelBase.cs
@@ -0,0 +1,60 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.Collections.Generic;
+
+namespace Etherna.MongODM.Models.Internal
+{
+ public abstract class EntityModelBase : ModelBase, IEntityModel
+ {
+ private DateTime _creationDateTime;
+
+ // Constructors and dispose.
+ protected EntityModelBase()
+ {
+ _creationDateTime = DateTime.Now;
+ }
+
+ public virtual void DisposeForDelete() { }
+
+ // Properties.
+ public virtual DateTime CreationDateTime { get => _creationDateTime; protected set => _creationDateTime = value; }
+ }
+
+ public abstract class EntityModelBase : EntityModelBase, IEntityModel
+ {
+ // Properties.
+ public virtual TKey Id { get; protected set; } = default!;
+
+ // Methods.
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj is null) return false;
+ if (EqualityComparer.Default.Equals(Id, default!) ||
+ !(obj is IEntityModel) ||
+ EqualityComparer.Default.Equals((obj as IEntityModel)!.Id, default!)) return false;
+ return GetType() == obj.GetType() &&
+ EqualityComparer.Default.Equals(Id, (obj as IEntityModel)!.Id);
+ }
+
+ public override int GetHashCode()
+ {
+ if (EqualityComparer.Default.Equals(Id, default!))
+ return -1;
+ return Id!.GetHashCode();
+ }
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/ModelBase.cs b/src/MongODM.Core/Models/Internal/ModelBase.cs
new file mode 100644
index 00000000..e733815c
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/ModelBase.cs
@@ -0,0 +1,25 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Etherna.MongODM.Models.Internal
+{
+ public abstract class ModelBase : IModel
+ {
+ [SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Setter needed for deserialization scope")]
+ public virtual IDictionary? ExtraElements { get; protected set; }
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/ModelMaps/DbMigrationOperationMap.cs b/src/MongODM.Core/Models/Internal/ModelMaps/DbMigrationOperationMap.cs
new file mode 100644
index 00000000..049318ee
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/ModelMaps/DbMigrationOperationMap.cs
@@ -0,0 +1,37 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Etherna.MongODM.Models.Internal.DbMigrationOpAgg;
+using Etherna.MongODM.Serialization;
+
+namespace Etherna.MongODM.Models.Internal.ModelMaps
+{
+ class DbMigrationOperationMap : IModelMapsCollector
+ {
+ public void Register(IDbContext dbContext)
+ {
+ dbContext.DocumentSchemaRegister.RegisterModelSchema(
+ "0.20.0");
+
+ dbContext.DocumentSchemaRegister.RegisterModelSchema(
+ "0.20.0");
+
+ dbContext.DocumentSchemaRegister.RegisterModelSchema(
+ "0.20.0");
+
+ dbContext.DocumentSchemaRegister.RegisterModelSchema(
+ "0.20.0");
+ }
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/ModelMaps/ModelBaseMap.cs b/src/MongODM.Core/Models/Internal/ModelMaps/ModelBaseMap.cs
new file mode 100644
index 00000000..a946473f
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/ModelMaps/ModelBaseMap.cs
@@ -0,0 +1,40 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Etherna.MongODM.Serialization;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.IdGenerators;
+using MongoDB.Bson.Serialization.Serializers;
+
+namespace Etherna.MongODM.Models.Internal.ModelMaps
+{
+ class ModelBaseMap : IModelMapsCollector
+ {
+ public void Register(IDbContext dbContext)
+ {
+ // register class maps.
+ dbContext.DocumentSchemaRegister.RegisterModelSchema("0.20.0");
+
+ dbContext.DocumentSchemaRegister.RegisterModelSchema>("0.20.0",
+ cm =>
+ {
+ cm.AutoMap();
+
+ // Set Id representation.
+ cm.IdMemberMap.SetSerializer(new StringSerializer(BsonType.ObjectId))
+ .SetIdGenerator(new StringObjectIdGenerator());
+ });
+ }
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs b/src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs
new file mode 100644
index 00000000..8c41bb47
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs
@@ -0,0 +1,35 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Etherna.MongODM.Serialization;
+using Etherna.MongODM.Serialization.Serializers;
+
+namespace Etherna.MongODM.Models.Internal.ModelMaps
+{
+ class OperationBaseMap : IModelMapsCollector
+ {
+ public void Register(IDbContext dbContext)
+ {
+ dbContext.DocumentSchemaRegister.RegisterModelSchema(
+ "0.20.0", //mongodm library's version
+ cm => cm.AutoMap(),
+ initCustomSerializer: () =>
+ new ExtendedClassMapSerializer(
+ dbContext.DbCache,
+ dbContext.LibraryVersion,
+ dbContext.SerializerModifierAccessor)
+ { AddVersion = true });
+ }
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/ModelMaps/SeedOperationMap.cs b/src/MongODM.Core/Models/Internal/ModelMaps/SeedOperationMap.cs
new file mode 100644
index 00000000..18359a85
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/ModelMaps/SeedOperationMap.cs
@@ -0,0 +1,27 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Etherna.MongODM.Serialization;
+
+namespace Etherna.MongODM.Models.Internal.ModelMaps
+{
+ class SeedOperationMap : IModelMapsCollector
+ {
+ public void Register(IDbContext dbContext)
+ {
+ dbContext.DocumentSchemaRegister.RegisterModelSchema(
+ "0.20.0");
+ }
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/OperationBase.cs b/src/MongODM.Core/Models/Internal/OperationBase.cs
new file mode 100644
index 00000000..ff541b3b
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/OperationBase.cs
@@ -0,0 +1,35 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+
+namespace Etherna.MongODM.Models.Internal
+{
+ public abstract class OperationBase : EntityModelBase
+ {
+ // Constructors and dispose.
+ public OperationBase(IDbContext dbContext)
+ {
+ if (dbContext is null)
+ throw new ArgumentNullException(nameof(dbContext));
+
+ CreationDateTime = DateTime.Now;
+ DbContextName = dbContext.Identifier;
+ }
+ protected OperationBase() { }
+
+ // Properties.
+ public virtual string DbContextName { get; protected set; } = default!;
+ }
+}
diff --git a/src/MongODM.Core/Models/Internal/SeedOperation.cs b/src/MongODM.Core/Models/Internal/SeedOperation.cs
new file mode 100644
index 00000000..3a401134
--- /dev/null
+++ b/src/MongODM.Core/Models/Internal/SeedOperation.cs
@@ -0,0 +1,25 @@
+// Copyright 2020-present Etherna Sagl
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace Etherna.MongODM.Models.Internal
+{
+ public class SeedOperation : OperationBase
+ {
+ // Constructors.
+ public SeedOperation(IDbContext owner)
+ : base(owner)
+ { }
+ protected SeedOperation() { }
+ }
+}
diff --git a/src/MongODM.Core/MongODM.Core.csproj b/src/MongODM.Core/MongODM.Core.csproj
index 3fc6c30c..9c33e289 100644
--- a/src/MongODM.Core/MongODM.Core.csproj
+++ b/src/MongODM.Core/MongODM.Core.csproj
@@ -8,7 +8,12 @@
ODM framework for MongoDB
8.0
enable
+
https://github.com/Etherna/mongodm
+ git
+ true
+ true
+ snupkg
LICENSE.txt
@@ -18,6 +23,14 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/src/MongODM.Core/ProxyModels/AuditableInterceptor.cs b/src/MongODM.Core/ProxyModels/AuditableInterceptor.cs
index c505d01b..b7e088b6 100644
--- a/src/MongODM.Core/ProxyModels/AuditableInterceptor.cs
+++ b/src/MongODM.Core/ProxyModels/AuditableInterceptor.cs
@@ -35,6 +35,9 @@ public AuditableInterceptor(IEnumerable additionalInterfaces)
// Protected methods.
protected override bool InterceptInterface(IInvocation invocation)
{
+ if (invocation is null)
+ throw new ArgumentNullException(nameof(invocation));
+
// Intercept ISummarizable invocations
if (invocation.Method.DeclaringType == typeof(IAuditable))
{
@@ -61,10 +64,13 @@ protected override bool InterceptInterface(IInvocation invocation)
protected override void InterceptModel(IInvocation invocation)
{
+ if (invocation is null)
+ throw new ArgumentNullException(nameof(invocation));
+
// Filter sets.
if (isAuditingEnabled)
{
- if (invocation.Method.Name.StartsWith("set_"))
+ if (invocation.Method.Name.StartsWith("set_", StringComparison.InvariantCulture))
{
var propertyName = invocation.Method.Name.Substring(4);
var propertyInfo = typeof(TModel).GetMember(propertyName).Single();
@@ -72,7 +78,7 @@ protected override void InterceptModel(IInvocation invocation)
// Add property to edited set.
changedMembers.Add(propertyInfo);
}
- else if (invocation.Method.Name.StartsWith("get_"))
+ else if (invocation.Method.Name.StartsWith("get_", StringComparison.InvariantCulture))
{
//ignore get
}
diff --git a/src/MongODM.Core/ProxyModels/IProxyGenerator.cs b/src/MongODM.Core/ProxyModels/IProxyGenerator.cs
index f6b208fd..e7c39c45 100644
--- a/src/MongODM.Core/ProxyModels/IProxyGenerator.cs
+++ b/src/MongODM.Core/ProxyModels/IProxyGenerator.cs
@@ -21,5 +21,6 @@ public interface IProxyGenerator
object CreateInstance(IDbContext dbContext, Type type, params object[] constructorArguments);
TModel CreateInstance(IDbContext dbContext, params object[] constructorArguments);
bool IsProxyType(Type type);
+ Type PurgeProxyType(Type type);
}
}
\ No newline at end of file
diff --git a/src/MongODM.Core/ProxyModels/ModelInterceptorBase.cs b/src/MongODM.Core/ProxyModels/ModelInterceptorBase.cs
index dca32894..20d4365b 100644
--- a/src/MongODM.Core/ProxyModels/ModelInterceptorBase.cs
+++ b/src/MongODM.Core/ProxyModels/ModelInterceptorBase.cs
@@ -30,6 +30,9 @@ public ModelInterceptorBase(IEnumerable additionalInterfaces)
public void Intercept(IInvocation invocation)
{
+ if (invocation is null)
+ throw new ArgumentNullException(nameof(invocation));
+
if (additionalInterfaces.Contains(invocation.Method.DeclaringType))
{
var handled = InterceptInterface(invocation);
@@ -66,6 +69,9 @@ protected virtual bool InterceptInterface(IInvocation invocation)
/// Current invocation
protected virtual void InterceptModel(IInvocation invocation)
{
+ if (invocation is null)
+ throw new ArgumentNullException(nameof(invocation));
+
invocation.Proceed();
}
}
diff --git a/src/MongODM.Core/ProxyModels/ProxyGenerator.cs b/src/MongODM.Core/ProxyModels/ProxyGenerator.cs
index dca8690c..f7ae3110 100644
--- a/src/MongODM.Core/ProxyModels/ProxyGenerator.cs
+++ b/src/MongODM.Core/ProxyModels/ProxyGenerator.cs
@@ -21,7 +21,7 @@
namespace Etherna.MongODM.ProxyModels
{
- public class ProxyGenerator : IProxyGenerator
+ public class ProxyGenerator : IProxyGenerator, IDisposable
{
// Fields.
private readonly Castle.DynamicProxy.IProxyGenerator proxyGeneratorCore;
@@ -47,6 +47,11 @@ public object CreateInstance(
Type type,
params object[] constructorArguments)
{
+ if (dbContext is null)
+ throw new ArgumentNullException(nameof(dbContext));
+ if (type is null)
+ throw new ArgumentNullException(nameof(type));
+
// Get configuration.
(Type[] AdditionalInterfaces, Func InterceptorInstancerSelector) configuration = (null!, null!);
modelConfigurationDictionaryLock.EnterReadLock();
@@ -128,6 +133,12 @@ public object CreateInstance(
public TModel CreateInstance(IDbContext dbContext, params object[] constructorArguments) =>
(TModel)CreateInstance(dbContext, typeof(TModel), constructorArguments);
+ public void Dispose()
+ {
+ modelConfigurationDictionaryLock.Dispose();
+ proxyTypeDictionaryLock.Dispose();
+ }
+
public bool IsProxyType(Type type)
{
proxyTypeDictionaryLock.EnterReadLock();
@@ -141,6 +152,16 @@ public bool IsProxyType(Type type)
}
}
+ public Type PurgeProxyType(Type type)
+ {
+ if (type is null)
+ throw new ArgumentNullException(nameof(type));
+
+ return IsProxyType(type) ?
+ type.BaseType :
+ type;
+ }
+
// Protected virtual methods.
protected virtual IEnumerable GetCustomAdditionalInterfaces(Type modelType) =>
Array.Empty();
diff --git a/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs b/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs
index 1c343485..cfa7079b 100644
--- a/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs
+++ b/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs
@@ -30,7 +30,7 @@ public class ReferenceableInterceptor : ModelInterceptorBase settedMemberNames = new Dictionary(); //
- private readonly IRepository repository;
+ private readonly IRepository repository;
// Constructors.
public ReferenceableInterceptor(
@@ -38,12 +38,26 @@ public ReferenceableInterceptor(
IDbContext dbContext)
: base(additionalInterfaces)
{
- repository = (IRepository)dbContext.RepositoryRegister.ModelRepositoryMap[typeof(TModel)];
+ if (dbContext is null)
+ throw new ArgumentNullException(nameof(dbContext));
+
+ var repositoryModelType = typeof(TModel);
+ while (!dbContext.RepositoryRegister.ModelRepositoryMap.ContainsKey(repositoryModelType))
+ {
+ if (repositoryModelType == typeof(object))
+ throw new InvalidOperationException($"Cant find valid repository for model type {typeof(TModel)}");
+ repositoryModelType = repositoryModelType.BaseType;
+ }
+
+ repository = dbContext.RepositoryRegister.ModelRepositoryMap[repositoryModelType];
}
// Protected methods.
protected override bool InterceptInterface(IInvocation invocation)
{
+ if (invocation is null)
+ throw new ArgumentNullException(nameof(invocation));
+
// Intercept ISummarizable invocations
if (invocation.Method.DeclaringType == typeof(IReferenceable))
{
@@ -83,8 +97,11 @@ protected override bool InterceptInterface(IInvocation invocation)
protected override void InterceptModel(IInvocation invocation)
{
+ if (invocation is null)
+ throw new ArgumentNullException(nameof(invocation));
+
// Filter gets.
- if (invocation.Method.Name.StartsWith("get_") && isSummary)
+ if (invocation.Method.Name.StartsWith("get_", StringComparison.InvariantCulture) && isSummary)
{
var propertyName = invocation.Method.Name.Substring(4);
@@ -97,7 +114,7 @@ protected override void InterceptModel(IInvocation invocation)
}
// Filter sets.
- else if (invocation.Method.Name.StartsWith("set_"))
+ else if (invocation.Method.Name.StartsWith("set_", StringComparison.InvariantCulture))
{
var propertyName = invocation.Method.Name.Substring(4);
@@ -108,7 +125,7 @@ protected override void InterceptModel(IInvocation invocation)
// Filter normal methods.
else
{
- var attributes = invocation.Method.GetCustomAttributes(true) ?? new PropertyAltererAttribute[0];
+ var attributes = invocation.Method.GetCustomAttributes(true) ?? Array.Empty();
foreach (var propertyName in from attribute in attributes
select attribute.PropertyName)
{
@@ -137,10 +154,13 @@ protected override void InterceptModel(IInvocation invocation)
// Helpers.
private async Task FullLoadAsync(TModel model)
{
+ if (model.Id is null)
+ throw new InvalidOperationException("model or id can't be null");
+
if (isSummary)
{
// Merge full object to current.
- var fullModel = await repository.TryFindOneAsync(model.Id);
+ var fullModel = (await repository.TryFindOneAsync(model.Id).ConfigureAwait(false)) as TModel;
MergeFullModel(model, fullModel);
}
}
diff --git a/src/MongODM.Core/ReflectionHelper.cs b/src/MongODM.Core/ReflectionHelper.cs
index f6b38b3c..091ef24f 100644
--- a/src/MongODM.Core/ReflectionHelper.cs
+++ b/src/MongODM.Core/ReflectionHelper.cs
@@ -26,58 +26,20 @@ public static class ReflectionHelper
private static readonly Dictionary> propertyRegister = new Dictionary>();
private static readonly ReaderWriterLockSlim propertyRegisterLock = new ReaderWriterLockSlim();
- public static TClass CloneModel(TClass srcObj, params Expression>[] memberLambdas)
- where TClass : new()
- {
- var destObj = new TClass();
- CloneModel(srcObj, destObj, memberLambdas);
- return destObj;
- }
-
- public static void CloneModel(TClass srcObj, TClass destObj, params Expression>[] memberLambdas)
- {
- if (srcObj is null)
- throw new ArgumentNullException(nameof(srcObj));
- if (destObj is null)
- throw new ArgumentNullException(nameof(destObj));
-
- IEnumerable membersToClone;
-
- if (memberLambdas.Any())
- {
- membersToClone = memberLambdas.Select(l => GetMemberInfoFromLambda(l, typeof(TClass)));
- }
- else // clone full object
- {
- membersToClone = GetWritableInstanceProperties(typeof(TClass));
- }
-
- foreach (var member in membersToClone)
- {
- SetValue(destObj, member, GetValue(srcObj, member));
- }
- }
-
- public static void CloneModel(object srcObj, object destObj, Type actualType) =>
- CloneModel(srcObj, destObj, GetWritableInstanceProperties(actualType));
-
- public static void CloneModel(object srcObj, object destObj, IEnumerable members)
- {
- foreach (var member in members)
- {
- SetValue(destObj, member, GetValue(srcObj, member));
- }
- }
-
public static MemberInfo FindProperty(LambdaExpression lambdaExpression)
{
+ if (lambdaExpression is null)
+ throw new ArgumentNullException(nameof(lambdaExpression));
+
Expression expressionToCheck = lambdaExpression;
bool done = false;
while (!done)
{
+#pragma warning disable CA1062 // Validate arguments of public methods. Suppressing for an issue in Microsoft.CodeAnalysis.FxCopAnalyzers v3.0.0
switch (expressionToCheck.NodeType)
+#pragma warning restore CA1062 // Validate arguments of public methods
{
case ExpressionType.Convert:
expressionToCheck = ((UnaryExpression)expressionToCheck).Operand;
@@ -86,7 +48,7 @@ public static MemberInfo FindProperty(LambdaExpression lambdaExpression)
expressionToCheck = ((LambdaExpression)expressionToCheck).Body;
break;
case ExpressionType.MemberAccess:
- var memberExpression = ((MemberExpression)expressionToCheck);
+ var memberExpression = (MemberExpression)expressionToCheck;
if (memberExpression.Expression.NodeType != ExpressionType.Parameter &&
memberExpression.Expression.NodeType != ExpressionType.Convert)
@@ -110,6 +72,11 @@ public static MemberInfo FindProperty(LambdaExpression lambdaExpression)
public static PropertyInfo FindPropertyImplementation(PropertyInfo interfacePropertyInfo, Type actualType)
{
+ if (interfacePropertyInfo is null)
+ throw new ArgumentNullException(nameof(interfacePropertyInfo));
+ if (actualType is null)
+ throw new ArgumentNullException(nameof(actualType));
+
var interfaceType = interfacePropertyInfo.DeclaringType;
// An interface map must be used because because there is no
@@ -137,19 +104,13 @@ public static PropertyInfo FindPropertyImplementation(PropertyInfo interfaceProp
});
}
- public static object? GetDefaultValue(Type type)
- {
- if (type.IsValueType)
- {
- return Activator.CreateInstance(type);
- }
- return null;
- }
-
public static MemberInfo GetMemberInfoFromLambda(
Expression> memberLambda,
Type? actualType = null)
{
+ if (memberLambda is null)
+ throw new ArgumentNullException(nameof(memberLambda));
+
var body = memberLambda.Body;
MemberExpression memberExpression;
switch (body.NodeType)
@@ -214,6 +175,9 @@ public static TMember GetValueFromLambda(TModel source, Express
/// The list of properties
public static IEnumerable GetWritableInstanceProperties(Type objectType)
{
+ if (objectType is null)
+ throw new ArgumentNullException(nameof(objectType));
+
propertyRegisterLock.EnterReadLock();
try
{
diff --git a/src/MongODM.Core/Repositories/CollectionRepository.cs b/src/MongODM.Core/Repositories/CollectionRepository.cs
index 9c0909c2..3031dd4d 100644
--- a/src/MongODM.Core/Repositories/CollectionRepository.cs
+++ b/src/MongODM.Core/Repositories/CollectionRepository.cs
@@ -49,6 +49,7 @@ public CollectionRepository(CollectionRepositoryOptions options)
// Properties.
public IMongoCollection Collection => _collection ??= DbContext.Database.GetCollection(options.Name);
+ public override string Name => options.Name;
// Public methods.
public override async Task BuildIndexesAsync(IDocumentSchemaRegister schemaRegister, CancellationToken cancellationToken = default)
@@ -95,7 +96,7 @@ public override async Task BuildIndexesAsync(IDocumentSchemaRegister schemaRegis
// Get current indexes.
var currentIndexes = new List();
- using (var indexList = await Collection.Indexes.ListAsync(cancellationToken))
+ using (var indexList = await Collection.Indexes.ListAsync(cancellationToken).ConfigureAwait(false))
while (indexList.MoveNext())
currentIndexes.AddRange(indexList.Current);
@@ -106,11 +107,11 @@ public override async Task BuildIndexesAsync(IDocumentSchemaRegister schemaRegis
where !newIndexes.Any(newIndex => newIndex.name == indexName)
select index)
{
- await Collection.Indexes.DropOneAsync(oldIndex.GetElement("name").Value.ToString(), cancellationToken);
+ await Collection.Indexes.DropOneAsync(oldIndex.GetElement("name").Value.ToString(), cancellationToken).ConfigureAwait(false);
}
// Build new indexes.
- await Collection.Indexes.CreateManyAsync(newIndexes.Select(i => i.createIndex), cancellationToken);
+ await Collection.Indexes.CreateManyAsync(newIndexes.Select(i => i.createIndex), cancellationToken).ConfigureAwait(false);
}
public virtual Task> FindAsync(
@@ -126,8 +127,13 @@ public Task FindOneAsync(
public virtual Task QueryElementsAsync(
Func, Task> query,
- AggregateOptions? aggregateOptions = null) =>
- query(Collection.AsQueryable(aggregateOptions));
+ AggregateOptions? aggregateOptions = null)
+ {
+ if (query is null)
+ throw new ArgumentNullException(nameof(query));
+
+ return query(Collection.AsQueryable(aggregateOptions));
+ }
public virtual Task ReplaceAsync(
object model,
@@ -162,7 +168,7 @@ public virtual Task ReplaceAsync(
try
{
- return await FindOneAsync(predicate, cancellationToken);
+ return await FindOneAsync(predicate, cancellationToken).ConfigureAwait(false);
}
catch (EntityNotFoundException)
{
@@ -177,10 +183,15 @@ protected override Task CreateOnDBAsync(IEnumerable models, Cancellation
protected override Task CreateOnDBAsync(TModel model, CancellationToken cancellationToken) =>
Collection.InsertOneAsync(model, null, cancellationToken);
- protected override Task DeleteOnDBAsync(TModel model, CancellationToken cancellationToken) =>
- Collection.DeleteOneAsync(
+ protected override Task DeleteOnDBAsync(TModel model, CancellationToken cancellationToken)
+ {
+ if (model is null)
+ throw new ArgumentNullException(nameof(model));
+
+ return Collection.DeleteOneAsync(
Builders.Filter.Eq(m => m.Id, model.Id),
cancellationToken);
+ }
protected override async Task FindOneOnDBAsync(TKey id, CancellationToken cancellationToken = default)
{
@@ -189,7 +200,7 @@ protected override async Task FindOneOnDBAsync(TKey id, CancellationToke
try
{
- return await FindOneOnDBAsync(m => m.Id!.Equals(id), cancellationToken: cancellationToken);
+ return await FindOneOnDBAsync(m => m.Id!.Equals(id), cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch (EntityNotFoundException)
{
@@ -207,7 +218,7 @@ private async Task FindOneOnDBAsync(
var element = await Collection.AsQueryable()
.Where(predicate)
- .SingleOrDefaultAsync(cancellationToken);
+ .SingleOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (element == default(TModel))
throw new EntityNotFoundException("Can't find element");
@@ -229,7 +240,7 @@ private async Task ReplaceHelperAsync(
await Collection.ReplaceOneAsync(
Builders.Filter.Eq(m => m.Id, model.Id),
model,
- cancellationToken: cancellationToken);
+ cancellationToken: cancellationToken).ConfigureAwait(false);
}
else
{
@@ -237,12 +248,12 @@ await Collection.ReplaceOneAsync(
session,
Builders.Filter.Eq(m => m.Id, model.Id),
model,
- cancellationToken: cancellationToken);
+ cancellationToken: cancellationToken).ConfigureAwait(false);
}
// Update dependent documents.
if (updateDependentDocuments)
- DbContext.DBMaintainer.OnUpdatedModel((IAuditable)model, model.Id);
+ DbContext.DbMaintainer.OnUpdatedModel((IAuditable)model, model.Id);
// Reset changed members.
(model as IAuditable)?.ResetChangedMembers();
diff --git a/src/MongODM.Core/Repositories/GridFSRepository.cs b/src/MongODM.Core/Repositories/GridFSRepository.cs
index 3aa65a33..f45e2d04 100644
--- a/src/MongODM.Core/Repositories/GridFSRepository.cs
+++ b/src/MongODM.Core/Repositories/GridFSRepository.cs
@@ -48,6 +48,7 @@ public GridFSRepository(GridFSRepositoryOptions options)
// Properties.
public IGridFSBucket GridFSBucket =>
_gridFSBucket ??= new GridFSBucket(DbContext.Database, new GridFSBucketOptions { BucketName = options.Name });
+ public override string Name => options.Name;
// Methods.
public override Task BuildIndexesAsync(IDocumentSchemaRegister schemaRegister, CancellationToken cancellationToken = default) => Task.CompletedTask;
@@ -56,13 +57,16 @@ public virtual Task DownloadAsBytesAsync(string id, CancellationToken ca
GridFSBucket.DownloadAsBytesAsync(ObjectId.Parse(id), null, cancellationToken);
public virtual async Task DownloadAsStreamAsync(string id, CancellationToken cancellationToken = default) =>
- await GridFSBucket.OpenDownloadStreamAsync(ObjectId.Parse(id), null, cancellationToken);
+ await GridFSBucket.OpenDownloadStreamAsync(ObjectId.Parse(id), null, cancellationToken).ConfigureAwait(false);
// Protected methods.
protected override async Task CreateOnDBAsync(IEnumerable models, CancellationToken cancellationToken)
{
+ if (models is null)
+ throw new ArgumentNullException(nameof(models));
+
foreach (var model in models)
- await CreateOnDBAsync(model, cancellationToken);
+ await CreateOnDBAsync(model, cancellationToken).ConfigureAwait(false);
}
protected override async Task CreateOnDBAsync(TModel model, CancellationToken cancellationToken)
@@ -75,12 +79,17 @@ protected override async Task CreateOnDBAsync(TModel model, CancellationToken ca
var id = await GridFSBucket.UploadFromStreamAsync(model.Name, model.Stream, new GridFSUploadOptions
{
Metadata = options.MetadataSerializer?.Invoke(model)
- });
+ }).ConfigureAwait(false);
ReflectionHelper.SetValue(model, m => m.Id, id.ToString());
}
- protected override Task DeleteOnDBAsync(TModel model, CancellationToken cancellationToken) =>
- GridFSBucket.DeleteAsync(ObjectId.Parse(model.Id), cancellationToken);
+ protected override Task DeleteOnDBAsync(TModel model, CancellationToken cancellationToken)
+ {
+ if (model is null)
+ throw new ArgumentNullException(nameof(model));
+
+ return GridFSBucket.DeleteAsync(ObjectId.Parse(model.Id), cancellationToken);
+ }
protected override async Task FindOneOnDBAsync(string id, CancellationToken cancellationToken = default)
{
@@ -88,7 +97,7 @@ protected override async Task FindOneOnDBAsync(string id, CancellationTo
throw new ArgumentNullException(nameof(id));
var filter = Builders.Filter.Eq("_id", ObjectId.Parse(id));
- var mongoFile = await GridFSBucket.Find(filter).SingleOrDefaultAsync(cancellationToken);
+ var mongoFile = await GridFSBucket.Find(filter).SingleOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (mongoFile == null)
throw new EntityNotFoundException($"Can't find key {id}");
diff --git a/src/MongODM.Core/Repositories/IRepository.cs b/src/MongODM.Core/Repositories/IRepository.cs
index 7937552c..f389b9d4 100644
--- a/src/MongODM.Core/Repositories/IRepository.cs
+++ b/src/MongODM.Core/Repositories/IRepository.cs
@@ -26,6 +26,7 @@ public interface IRepository : IDbContextInitializable
IDbContext DbContext { get; }
Type GetKeyType { get; }
Type GetModelType { get; }
+ string Name { get; }
Task BuildIndexesAsync(
IDocumentSchemaRegister schemaRegister,
@@ -34,6 +35,20 @@ Task BuildIndexesAsync(
Task DeleteAsync(
IEntityModel model,
CancellationToken cancellationToken = default);
+
+ Task