From 9c5730c2b523a2bd7ffdb1a8ba63bb31ff26ec0b Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 5 Jul 2020 16:58:12 +0200 Subject: [PATCH 01/27] Names refactoring --- src/MongODM.Core/DbContext.cs | 20 +++++++++---------- src/MongODM.Core/DbContextOptions.cs | 4 ++-- src/MongODM.Core/IDbContext.cs | 6 +++--- .../Migration/MongoDocumentMigration.cs | 4 ++-- .../Repositories/CollectionRepository.cs | 2 +- .../Repositories/RepositoryBase.cs | 8 ++++---- .../Serialization/DocumentSchema.cs | 4 ++-- .../Serialization/DocumentSchemaMemberMap.cs | 4 ++-- .../Serialization/DocumentSchemaRegister.cs | 16 +++++++-------- .../ExtendedBsonDocumentReader.cs | 2 +- .../Serialization/IDocumentSchemaRegister.cs | 12 +++++------ ...zerCollector.cs => IModelMapsCollector.cs} | 2 +- ...{DocumentVersion.cs => SemanticVersion.cs} | 20 +++++++++---------- .../Serializers/ExtendedClassMapSerializer.cs | 16 +++++++-------- .../Serializers/ReferenceSerializer.cs | 2 +- .../Serializers/ReferenceSerializerSwitch.cs | 2 +- .../ExtendedClassMapSerializerTest.cs | 2 +- 17 files changed, 63 insertions(+), 63 deletions(-) rename src/MongODM.Core/Serialization/{IModelSerializerCollector.cs => IModelMapsCollector.cs} (72%) rename src/MongODM.Core/Serialization/{DocumentVersion.cs => SemanticVersion.cs} (86%) diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index 770c1bb9..0e1397b7 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -26,8 +26,8 @@ public DbContext( IDbContextDependencies dependencies, DbContextOptions options) { - DBCache = dependencies.DbCache; - DBMaintainer = dependencies.DbMaintainer; + DbCache = dependencies.DbCache; + DbMaintainer = dependencies.DbMaintainer; DocumentSchemaRegister = dependencies.DocumentSchemaRegister; DocumentVersion = options.DocumentVersion; ProxyGenerator = dependencies.ProxyGenerator; @@ -36,11 +36,11 @@ public DbContext( // Initialize MongoDB driver. Client = new MongoClient(options.ConnectionString); - Database = Client.GetDatabase(options.DBName); + Database = Client.GetDatabase(options.DbName); // Initialize internal dependencies. DocumentSchemaRegister.Initialize(this); - DBMaintainer.Initialize(this); + DbMaintainer.Initialize(this); RepositoryRegister.Initialize(this); // Initialize repositories. @@ -54,7 +54,7 @@ public DbContext( }, c => true); // Register serializers. - foreach (var serializerCollector in SerializerCollectors) + foreach (var serializerCollector in ModelMapsCollectors) serializerCollector.Register(this); // Build and freeze document schema register. @@ -63,15 +63,15 @@ public DbContext( // Public properties. 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 IDocumentSchemaRegister DocumentSchemaRegister { get; } - public DocumentVersion DocumentVersion { get; } + public SemanticVersion DocumentVersion { get; } public bool IsMigrating { get; private set; } public IProxyGenerator ProxyGenerator { get; } public IRepositoryRegister RepositoryRegister { get; } @@ -79,7 +79,7 @@ public DbContext( // 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) diff --git a/src/MongODM.Core/DbContextOptions.cs b/src/MongODM.Core/DbContextOptions.cs index eaa49bf8..2612c0b7 100644 --- a/src/MongODM.Core/DbContextOptions.cs +++ b/src/MongODM.Core/DbContextOptions.cs @@ -6,9 +6,9 @@ namespace Etherna.MongODM public class DbContextOptions { 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 SemanticVersion DocumentVersion { get; set; } = "1.0.0"; } public class DbContextOptions : DbContextOptions diff --git a/src/MongODM.Core/IDbContext.cs b/src/MongODM.Core/IDbContext.cs index 2ffc85df..3af62421 100644 --- a/src/MongODM.Core/IDbContext.cs +++ b/src/MongODM.Core/IDbContext.cs @@ -28,12 +28,12 @@ public interface IDbContext /// /// Database cache container. /// - IDbCache DBCache { get; } + IDbCache DbCache { get; } /// /// Database operator interested into maintenance tasks. /// - IDbMaintainer DBMaintainer { get; } + IDbMaintainer DbMaintainer { get; } /// /// Container for model serialization and document schema information. @@ -43,7 +43,7 @@ public interface IDbContext /// /// Current operating document version. /// - DocumentVersion DocumentVersion { get; } + SemanticVersion DocumentVersion { get; } /// /// Flag reporting eventual current migration operation. diff --git a/src/MongODM.Core/Migration/MongoDocumentMigration.cs b/src/MongODM.Core/Migration/MongoDocumentMigration.cs index 5ddbb4da..3a7d9862 100644 --- a/src/MongODM.Core/Migration/MongoDocumentMigration.cs +++ b/src/MongODM.Core/Migration/MongoDocumentMigration.cs @@ -16,12 +16,12 @@ namespace Etherna.MongODM.Migration public class MongoDocumentMigration : MongoMigrationBase where TModel : class, IEntityModel { - private readonly DocumentVersion minimumDocumentVersion; + private readonly SemanticVersion minimumDocumentVersion; private readonly IMongoCollection sourceCollection; public MongoDocumentMigration( ICollectionRepository sourceCollection, - DocumentVersion minimumDocumentVersion) + SemanticVersion minimumDocumentVersion) { this.sourceCollection = sourceCollection.Collection; this.minimumDocumentVersion = minimumDocumentVersion; diff --git a/src/MongODM.Core/Repositories/CollectionRepository.cs b/src/MongODM.Core/Repositories/CollectionRepository.cs index 04e4a460..1fbb575a 100644 --- a/src/MongODM.Core/Repositories/CollectionRepository.cs +++ b/src/MongODM.Core/Repositories/CollectionRepository.cs @@ -228,7 +228,7 @@ await Collection.ReplaceOneAsync( // 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/RepositoryBase.cs b/src/MongODM.Core/Repositories/RepositoryBase.cs index 3d98ee07..1aaf46a9 100644 --- a/src/MongODM.Core/Repositories/RepositoryBase.cs +++ b/src/MongODM.Core/Repositories/RepositoryBase.cs @@ -73,8 +73,8 @@ public virtual async Task DeleteAsync(TModel model, CancellationToken cancellati await DeleteOnDBAsync(model, cancellationToken); // Remove from cache. - if (DbContext.DBCache.LoadedModels.ContainsKey(model.Id!)) - DbContext.DBCache.RemoveModel(model.Id!); + if (DbContext.DbCache.LoadedModels.ContainsKey(model.Id!)) + DbContext.DbCache.RemoveModel(model.Id!); } public async Task DeleteAsync(IEntityModel model, CancellationToken cancellationToken = default) @@ -88,9 +88,9 @@ public virtual async Task FindOneAsync( TKey id, CancellationToken cancellationToken = default) { - if (DbContext.DBCache.LoadedModels.ContainsKey(id!)) + if (DbContext.DbCache.LoadedModels.ContainsKey(id!)) { - var cachedModel = DbContext.DBCache.LoadedModels[id!] as TModel; + var cachedModel = DbContext.DbCache.LoadedModels[id!] as TModel; if ((cachedModel as IReferenceable)?.IsSummary == false) return cachedModel!; } diff --git a/src/MongODM.Core/Serialization/DocumentSchema.cs b/src/MongODM.Core/Serialization/DocumentSchema.cs index 6c597760..0244d193 100644 --- a/src/MongODM.Core/Serialization/DocumentSchema.cs +++ b/src/MongODM.Core/Serialization/DocumentSchema.cs @@ -6,7 +6,7 @@ namespace Etherna.MongODM.Serialization public class DocumentSchema { // Constructors. - public DocumentSchema(BsonClassMap classMap, Type modelType, IBsonSerializer? serializer, DocumentVersion version) + public DocumentSchema(BsonClassMap classMap, Type modelType, IBsonSerializer? serializer, SemanticVersion version) { ClassMap = classMap; ModelType = modelType; @@ -18,6 +18,6 @@ public DocumentSchema(BsonClassMap classMap, Type modelType, IBsonSerializer? se public BsonClassMap ClassMap { get; } public Type ModelType { get; } public IBsonSerializer? Serializer { get; } - public DocumentVersion Version { get; } + public SemanticVersion Version { get; } } } diff --git a/src/MongODM.Core/Serialization/DocumentSchemaMemberMap.cs b/src/MongODM.Core/Serialization/DocumentSchemaMemberMap.cs index cc4729d2..465f5f37 100644 --- a/src/MongODM.Core/Serialization/DocumentSchemaMemberMap.cs +++ b/src/MongODM.Core/Serialization/DocumentSchemaMemberMap.cs @@ -12,7 +12,7 @@ public class DocumentSchemaMemberMap public DocumentSchemaMemberMap( Type rootModelType, IEnumerable memberPath, - DocumentVersion version, + SemanticVersion version, bool? useCascadeDelete) { MemberPath = memberPath; @@ -59,7 +59,7 @@ public IEnumerable MemberPathToId } public Type RootModelType { get; } public bool? UseCascadeDelete { get; } - public DocumentVersion Version { get; } + public SemanticVersion Version { get; } // Methods. public string MemberPathToString() => diff --git a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs index 22eb9520..8ce721c0 100644 --- a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs +++ b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs @@ -129,9 +129,9 @@ public IEnumerable GetModelEntityReferencesIds(Type mod } public void RegisterModelSchema( - DocumentVersion fromVersion, + SemanticVersion fromVersion, Func>? initCustomSerializer = null, - Func>? modelMigrationAsync = null) + Func>? modelMigrationAsync = null) where TModel : class => RegisterModelSchema( fromVersion, @@ -140,10 +140,10 @@ public void RegisterModelSchema( modelMigrationAsync); public void RegisterModelSchema( - DocumentVersion fromVersion, + SemanticVersion fromVersion, Action> classMapInitializer, Func>? initCustomSerializer = null, - Func>? modelMigrationAsync = null) + Func>? modelMigrationAsync = null) where TModel : class => RegisterModelSchema( fromVersion, @@ -152,10 +152,10 @@ public void RegisterModelSchema( modelMigrationAsync); public void RegisterModelSchema( - DocumentVersion fromVersion, + SemanticVersion fromVersion, BsonClassMap classMap, Func>? initCustomSerializer = null, - Func>? modelMigrationAsync = null) + Func>? modelMigrationAsync = null) where TModel : class { configLock.EnterWriteLock(); @@ -173,7 +173,7 @@ public void RegisterModelSchema( else if (!typeof(TModel).IsAbstract) //else if can deserialize, set default serializer serializer = new ExtendedClassMapSerializer( - dbContext.DBCache, + dbContext.DbCache, dbContext.DocumentVersion, serializerModifierAccessor, (m, v) => modelMigrationAsync?.Invoke(m, v) ?? Task.FromResult(m)) @@ -193,7 +193,7 @@ private void CompileDependencyRegisters( Type modelType, IEnumerable memberPath, BsonClassMap currentClassMap, - DocumentVersion version, + SemanticVersion version, bool? useCascadeDeleteSetting = null) { // Ignore class maps of abstract types. (child classes will map all their members) diff --git a/src/MongODM.Core/Serialization/ExtendedBsonDocumentReader.cs b/src/MongODM.Core/Serialization/ExtendedBsonDocumentReader.cs index c26927e0..afe3e442 100644 --- a/src/MongODM.Core/Serialization/ExtendedBsonDocumentReader.cs +++ b/src/MongODM.Core/Serialization/ExtendedBsonDocumentReader.cs @@ -9,6 +9,6 @@ public ExtendedBsonDocumentReader(BsonDocument document) : base(document) { } - public DocumentVersion? DocumentVersion { get; set; } + public SemanticVersion? DocumentVersion { get; set; } } } diff --git a/src/MongODM.Core/Serialization/IDocumentSchemaRegister.cs b/src/MongODM.Core/Serialization/IDocumentSchemaRegister.cs index 149b6bc2..e7741262 100644 --- a/src/MongODM.Core/Serialization/IDocumentSchemaRegister.cs +++ b/src/MongODM.Core/Serialization/IDocumentSchemaRegister.cs @@ -34,9 +34,9 @@ public interface IDocumentSchemaRegister : IDbContextInitializable /// Custom serializer initializer /// Model migration method void RegisterModelSchema( - DocumentVersion fromVersion, + SemanticVersion fromVersion, Func>? initCustomSerializer = null, - Func>? modelMigrationAsync = null) + Func>? modelMigrationAsync = null) where TModel : class; /// @@ -48,10 +48,10 @@ void RegisterModelSchema( /// Custom serializer initializer /// Model migration method void RegisterModelSchema( - DocumentVersion fromVersion, + SemanticVersion fromVersion, Action> classMapInitializer, Func>? initCustomSerializer = null, - Func>? modelMigrationAsync = null) + Func>? modelMigrationAsync = null) where TModel : class; /// @@ -63,10 +63,10 @@ void RegisterModelSchema( /// Custom serializer initializer /// Model migration method void RegisterModelSchema( - DocumentVersion fromVersion, + SemanticVersion fromVersion, BsonClassMap classMap, Func>? initCustomSerializer = null, - Func>? modelMigrationAsync = null) + Func>? modelMigrationAsync = null) where TModel : class; } } \ No newline at end of file diff --git a/src/MongODM.Core/Serialization/IModelSerializerCollector.cs b/src/MongODM.Core/Serialization/IModelMapsCollector.cs similarity index 72% rename from src/MongODM.Core/Serialization/IModelSerializerCollector.cs rename to src/MongODM.Core/Serialization/IModelMapsCollector.cs index 6a59c0d5..1c50a20e 100644 --- a/src/MongODM.Core/Serialization/IModelSerializerCollector.cs +++ b/src/MongODM.Core/Serialization/IModelMapsCollector.cs @@ -1,6 +1,6 @@ namespace Etherna.MongODM.Serialization { - public interface IModelSerializerCollector + public interface IModelMapsCollector { // Methods. void Register(IDbContext dbContext); diff --git a/src/MongODM.Core/Serialization/DocumentVersion.cs b/src/MongODM.Core/Serialization/SemanticVersion.cs similarity index 86% rename from src/MongODM.Core/Serialization/DocumentVersion.cs rename to src/MongODM.Core/Serialization/SemanticVersion.cs index b31d4082..23e22f79 100644 --- a/src/MongODM.Core/Serialization/DocumentVersion.cs +++ b/src/MongODM.Core/Serialization/SemanticVersion.cs @@ -4,14 +4,14 @@ namespace Etherna.MongODM.Serialization { - public class DocumentVersion : IComparable + public class SemanticVersion : IComparable { // Constructors. /// /// Construct from string version /// /// The version as string (ex. 3.1.4-alpha1) - public DocumentVersion(string version) + public SemanticVersion(string version) { // Accepted formats for version: // * 3 @@ -44,7 +44,7 @@ public DocumentVersion(string version) /// Minor version /// Patch version /// Additional label - public DocumentVersion( + public SemanticVersion( int major, int minor, int patch, @@ -63,7 +63,7 @@ public DocumentVersion( public string? LabelRelease { get; private set; } // Overrides. - public int CompareTo(DocumentVersion? other) + public int CompareTo(SemanticVersion? other) { // If other is not a valid object reference, this instance is greater. if (other is null) return 1; @@ -73,7 +73,7 @@ public int CompareTo(DocumentVersion? other) else return -1; } - public override bool Equals(object obj) => this == (obj as DocumentVersion); + public override bool Equals(object obj) => this == (obj as SemanticVersion); public override int GetHashCode() { @@ -98,7 +98,7 @@ public override string ToString() } // Operators. - public static bool operator < (DocumentVersion? x, DocumentVersion? y) + public static bool operator < (SemanticVersion? x, SemanticVersion? y) { // Check if null. if (y == null) @@ -121,9 +121,9 @@ public override string ToString() return false; } - public static bool operator > (DocumentVersion? x, DocumentVersion? y) => y < x; + public static bool operator > (SemanticVersion? x, SemanticVersion? y) => y < x; - public static bool operator == (DocumentVersion? x, DocumentVersion? y) + public static bool operator == (SemanticVersion? x, SemanticVersion? y) { if (ReferenceEquals(x, y)) return true; if (x is null || y is null) return false; @@ -134,8 +134,8 @@ public override string ToString() x.LabelRelease == y.LabelRelease; } - public static bool operator != (DocumentVersion x, DocumentVersion y) => !(x == y); + public static bool operator != (SemanticVersion x, SemanticVersion y) => !(x == y); - public static implicit operator DocumentVersion(string version) => new DocumentVersion(version); + public static implicit operator SemanticVersion(string version) => new SemanticVersion(version); } } diff --git a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs index a8266eca..ae637f1d 100644 --- a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs @@ -31,15 +31,15 @@ private struct ExtraElementCondition private readonly IDbCache dbCache; private readonly ISerializerModifierAccessor serializerModifierAccessor; private readonly ICollection extraElements; - private readonly Func> fixDeserializedModelAsync; + private readonly Func> fixDeserializedModelAsync; private BsonClassMapSerializer _serializer = default!; // Constructor. public ExtendedClassMapSerializer( IDbCache dbCache, - DocumentVersion documentVersion, + SemanticVersion documentVersion, ISerializerModifierAccessor serializerModifierAccessor, - Func>? fixDeserializedModelAsync = null) + Func>? fixDeserializedModelAsync = null) { this.dbCache = dbCache; this.serializerModifierAccessor = serializerModifierAccessor; @@ -92,7 +92,7 @@ public override TModel Deserialize(BsonDeserializationContext context, BsonDeser var bsonDocument = BsonDocumentSerializer.Instance.Deserialize(context, args); // Get version. - DocumentVersion? documentVersion = null; + SemanticVersion? documentVersion = null; if (bsonDocument.TryGetElement(DbContext.DocumentVersionElementName, out BsonElement versionElement)) documentVersion = BsonValueToDocumentVersion(versionElement.Value); @@ -204,12 +204,12 @@ public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializati Serializer.TryGetMemberSerializationInfo(memberName, out serializationInfo); // Helpers. - private static DocumentVersion? BsonValueToDocumentVersion(BsonValue bsonValue) => + private static SemanticVersion? BsonValueToDocumentVersion(BsonValue bsonValue) => bsonValue switch { BsonNull _ => null, - BsonString bsonString => new DocumentVersion(bsonString.AsString), - BsonArray bsonArray => new DocumentVersion( + BsonString bsonString => new SemanticVersion(bsonString.AsString), + BsonArray bsonArray => new SemanticVersion( bsonArray[0].AsInt32, bsonArray[1].AsInt32, bsonArray[2].AsInt32, @@ -217,7 +217,7 @@ public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializati _ => throw new NotSupportedException(), }; - private static BsonArray DocumentVersionToBsonArray(DocumentVersion documentVersion) + private static BsonArray DocumentVersionToBsonArray(SemanticVersion documentVersion) { var bsonArray = new BsonArray(new[] { diff --git a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs index eecdcf27..658cb0c0 100644 --- a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs @@ -38,7 +38,7 @@ public ReferenceSerializer( IDbContext dbContext, bool useCascadeDelete) { - this.dbCache = dbContext.DBCache; + this.dbCache = dbContext.DbCache; this.proxyGenerator = dbContext.ProxyGenerator; this.serializerModifierAccessor = dbContext.SerializerModifierAccessor; this.dbContext = dbContext; diff --git a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializerSwitch.cs b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializerSwitch.cs index 06213407..76c2716f 100644 --- a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializerSwitch.cs +++ b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializerSwitch.cs @@ -13,7 +13,7 @@ public class ReferenceSerializerSwitch : // Nested classes. public class CaseContext { - public DocumentVersion? DocumentVersion { get; set; } + public SemanticVersion? DocumentVersion { get; set; } } // Fields. diff --git a/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs b/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs index ffff574b..e1a3408b 100644 --- a/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs +++ b/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs @@ -66,7 +66,7 @@ public SerializationTestElement( // Fields. private readonly Mock dbCacheMock; - private readonly DocumentVersion documentVersion = new DocumentVersion("1.0.0"); + private readonly SemanticVersion documentVersion = new SemanticVersion("1.0.0"); private readonly Mock serializerModifierAccessorMock; // Constructor. From f1a3c721be3be8f9671288c8f4883b77fb0e7d93 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 5 Jul 2020 17:43:00 +0200 Subject: [PATCH 02/27] Updated workflow for trigger at any push First implementation of operations on db --- .github/workflows/build-test-package.yml | 4 +-- src/MongODM.Core/DbContext.cs | 22 ++++++++++-- src/MongODM.Core/DbContextOptions.cs | 1 + .../Operations/ModelMaps/OperationBaseMap.cs | 34 +++++++++++++++++++ src/MongODM.Core/Operations/OperationBase.cs | 31 +++++++++++++++++ 5 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs create mode 100644 src/MongODM.Core/Operations/OperationBase.cs diff --git a/.github/workflows/build-test-package.yml b/.github/workflows/build-test-package.yml index 55bab03c..afd6320c 100644 --- a/.github/workflows/build-test-package.yml +++ b/.github/workflows/build-test-package.yml @@ -1,8 +1,6 @@ name: Build test and package .Net Core library - MongODM -on: - push: - branches: [ master, dev ] +on: push jobs: build-test-package: diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index 0e1397b7..cdc67cd8 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -1,5 +1,7 @@ using Etherna.MongODM.Migration; using Etherna.MongODM.Models; +using Etherna.MongODM.Operations; +using Etherna.MongODM.Operations.ModelMaps; using Etherna.MongODM.ProxyModels; using Etherna.MongODM.Repositories; using Etherna.MongODM.Serialization; @@ -11,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -28,8 +31,15 @@ public DbContext( { DbCache = dependencies.DbCache; DbMaintainer = dependencies.DbMaintainer; + DbOperations = new CollectionRepository(options.DbOperationsCollectionName); DocumentSchemaRegister = dependencies.DocumentSchemaRegister; DocumentVersion = options.DocumentVersion; + LibraryVersion = GetType() + .GetTypeInfo() + .Assembly + .GetCustomAttribute() + ?.InformationalVersion + ?.Split('+')[0] ?? "1.0.0"; ProxyGenerator = dependencies.ProxyGenerator; RepositoryRegister = dependencies.RepositoryRegister; SerializerModifierAccessor = dependencies.SerializerModifierAccessor; @@ -53,9 +63,13 @@ public DbContext( new EnumRepresentationConvention(BsonType.String) }, c => true); - // Register serializers. - foreach (var serializerCollector in ModelMapsCollectors) - serializerCollector.Register(this); + // Register model maps. + //system maps + new OperationBaseMap().Register(this); + + //user maps + foreach (var maps in ModelMapsCollectors) + maps.Register(this); // Build and freeze document schema register. DocumentSchemaRegister.Freeze(); @@ -70,9 +84,11 @@ public DbContext( public IMongoDatabase Database { get; } public IDbCache DbCache { get; } public IDbMaintainer DbMaintainer { get; } + public ICollectionRepository DbOperations { get; } public IDocumentSchemaRegister DocumentSchemaRegister { get; } public SemanticVersion DocumentVersion { get; } public bool IsMigrating { get; private set; } + public SemanticVersion LibraryVersion { get; } public IProxyGenerator ProxyGenerator { get; } public IRepositoryRegister RepositoryRegister { get; } public ISerializerModifierAccessor SerializerModifierAccessor { get; } diff --git a/src/MongODM.Core/DbContextOptions.cs b/src/MongODM.Core/DbContextOptions.cs index 2612c0b7..3f5273b6 100644 --- a/src/MongODM.Core/DbContextOptions.cs +++ b/src/MongODM.Core/DbContextOptions.cs @@ -8,6 +8,7 @@ public class DbContextOptions public string ConnectionString { get; set; } = "mongodb://localhost/localDb"; public string DbName => ConnectionString.Split('?')[0] .Split('/').Last(); + public string DbOperationsCollectionName { get; set; } = "_dbOperations"; public SemanticVersion DocumentVersion { get; set; } = "1.0.0"; } diff --git a/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs b/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs new file mode 100644 index 00000000..5b330ab1 --- /dev/null +++ b/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs @@ -0,0 +1,34 @@ +using Etherna.MongODM.Serialization; +using Etherna.MongODM.Serialization.Serializers; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.IdGenerators; +using MongoDB.Bson.Serialization.Serializers; + +namespace Etherna.MongODM.Operations.ModelMaps +{ + class OperationBaseMap : IModelMapsCollector + { + public void Register(IDbContext dbContext) + { + dbContext.DocumentSchemaRegister.RegisterModelSchema( + "0.20.0", //mongodm library's version + cm => + { + cm.AutoMap(); + + // Set creator. + cm.SetCreator(() => dbContext.ProxyGenerator.CreateInstance(dbContext)); + + // Set Id representation. + cm.IdMemberMap.SetSerializer(new StringSerializer(BsonType.ObjectId)) + .SetIdGenerator(new StringObjectIdGenerator()); + }, + initCustomSerializer: () => + new ExtendedClassMapSerializer( + dbContext.DbCache, + dbContext.DocumentVersion, + dbContext.SerializerModifierAccessor) + { AddVersion = true }); + } + } +} diff --git a/src/MongODM.Core/Operations/OperationBase.cs b/src/MongODM.Core/Operations/OperationBase.cs new file mode 100644 index 00000000..ceadf211 --- /dev/null +++ b/src/MongODM.Core/Operations/OperationBase.cs @@ -0,0 +1,31 @@ +using Etherna.MongODM.Models; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Etherna.MongODM.Operations +{ + public abstract class OperationBase : IEntityModel + { + // Constructors and dispose. + public OperationBase( + string? authorIdentifier, + IDbContext owner) + { + AuthorIdentifier = authorIdentifier; + CreationDateTime = DateTime.Now; + DbContextName = owner.GetType().Name; + } + protected OperationBase() { } + public void DisposeForDelete() { } + + // Properties. + public virtual string Id { get; protected set; } = default!; + public virtual string? AuthorIdentifier { get; protected set; } + public virtual DateTime CreationDateTime { get; protected set; } + public virtual string DbContextName { get; protected set; } = default!; + + [SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Setter needed for deserialization scope")] + public virtual IDictionary? ExtraElements { get; protected set; } + } +} From a3fc301e5ab644be954fa1c9bafe2fe52f47c6a6 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 5 Jul 2020 19:17:35 +0200 Subject: [PATCH 03/27] Workflows update --- .github/workflows/myget-feature-deploy.yml | 4 ++-- .../{myget-stable-deploy.yml => myget-unstable-deploy.yml} | 4 ++-- .../{nuget-release-deploy.yml => nuget-stable-deploy.yml} | 2 +- MongODM.sln | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename .github/workflows/{myget-stable-deploy.yml => myget-unstable-deploy.yml} (89%) rename .github/workflows/{nuget-release-deploy.yml => nuget-stable-deploy.yml} (92%) diff --git a/.github/workflows/myget-feature-deploy.yml b/.github/workflows/myget-feature-deploy.yml index dabba0c9..f8d1fb58 100644 --- a/.github/workflows/myget-feature-deploy.yml +++ b/.github/workflows/myget-feature-deploy.yml @@ -1,8 +1,8 @@ -name: Build and package feature branch. Deploy to MyGet +name: Feature deploy to MyGet on: push: - branches: + branches: - 'feature/**' jobs: diff --git a/.github/workflows/myget-stable-deploy.yml b/.github/workflows/myget-unstable-deploy.yml similarity index 89% rename from .github/workflows/myget-stable-deploy.yml rename to .github/workflows/myget-unstable-deploy.yml index ee11be36..fe2b5bde 100644 --- a/.github/workflows/myget-stable-deploy.yml +++ b/.github/workflows/myget-unstable-deploy.yml @@ -1,10 +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 + - 'release/**' jobs: build-test-package: diff --git a/.github/workflows/nuget-release-deploy.yml b/.github/workflows/nuget-stable-deploy.yml similarity index 92% rename from .github/workflows/nuget-release-deploy.yml rename to .github/workflows/nuget-stable-deploy.yml index ad6afcd1..ef2dc40f 100644 --- a/.github/workflows/nuget-release-deploy.yml +++ b/.github/workflows/nuget-stable-deploy.yml @@ -1,4 +1,4 @@ -name: Build test and package master branch. Deploy to NuGet +name: Stable release deploy to NuGet on: push: diff --git a/MongODM.sln b/MongODM.sln index 1bec69e7..da88ead2 100644 --- a/MongODM.sln +++ b/MongODM.sln @@ -27,8 +27,8 @@ 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 From ac7960bcda0b9d89a4b70f4f82dacb3d389c91a5 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 5 Jul 2020 20:02:58 +0200 Subject: [PATCH 04/27] Added gitversion configuration for feature branches --- GitVersion.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 GitVersion.yml diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000..613e72ef --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,6 @@ +branches: + feature: + mode: ContinuousDeployment +ignore: + sha: [] +merge-message-formats: {} From 708ae2bf4f675699076f50863be5d5fc054bcf6f Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 5 Jul 2020 22:13:58 +0200 Subject: [PATCH 05/27] Updated feature publish workflow for debug Minor fix --- .github/workflows/myget-feature-deploy.yml | 4 ++-- src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/myget-feature-deploy.yml b/.github/workflows/myget-feature-deploy.yml index f8d1fb58..d7f0abb2 100644 --- a/.github/workflows/myget-feature-deploy.yml +++ b/.github/workflows/myget-feature-deploy.yml @@ -20,10 +20,10 @@ jobs: dotnet-version: '3.1.x' - name: Build with dotnet - run: dotnet build --configuration Release + run: dotnet build --configuration Debug - name: Generate nuget package - run: dotnet pack --configuration Release -o nupkg + run: dotnet pack --configuration Debug -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/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs b/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs index 5b330ab1..f6b9e2aa 100644 --- a/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs +++ b/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs @@ -10,15 +10,12 @@ class OperationBaseMap : IModelMapsCollector { public void Register(IDbContext dbContext) { - dbContext.DocumentSchemaRegister.RegisterModelSchema( + dbContext.DocumentSchemaRegister.RegisterModelSchema( "0.20.0", //mongodm library's version cm => { cm.AutoMap(); - // Set creator. - cm.SetCreator(() => dbContext.ProxyGenerator.CreateInstance(dbContext)); - // Set Id representation. cm.IdMemberMap.SetSerializer(new StringSerializer(BsonType.ObjectId)) .SetIdGenerator(new StringObjectIdGenerator()); From b71258de91dfcbfea2ea64a6b0b4d61353b48551 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 5 Jul 2020 22:41:20 +0200 Subject: [PATCH 06/27] Added SourceLink --- .gitignore | 1 + src/ExecutionContext/ExecutionContext.csproj | 9 +++++++++ src/MongODM.AspNetCore/MongODM.AspNetCore.csproj | 9 +++++++++ src/MongODM.Core/MongODM.Core.csproj | 9 +++++++++ src/MongODM.Hangfire/MongODM.Hangfire.csproj | 9 +++++++++ 5 files changed, 37 insertions(+) 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/src/ExecutionContext/ExecutionContext.csproj b/src/ExecutionContext/ExecutionContext.csproj index ae5c181f..ade3d190 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 + true + true + true + snupkg @@ -20,6 +25,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 daf416c1..b7215d1f 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 + true + true + true + snupkg @@ -30,6 +35,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/MongODM.Core/MongODM.Core.csproj b/src/MongODM.Core/MongODM.Core.csproj index 674ffeee..8002f7f4 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 + true + true + true + snupkg @@ -17,6 +22,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/MongODM.Hangfire/MongODM.Hangfire.csproj b/src/MongODM.Hangfire/MongODM.Hangfire.csproj index 137577e9..3a68ca1d 100644 --- a/src/MongODM.Hangfire/MongODM.Hangfire.csproj +++ b/src/MongODM.Hangfire/MongODM.Hangfire.csproj @@ -8,7 +8,12 @@ Linker for use MongoDB with Hangfire 8.0 enable + https://github.com/Etherna/mongodm + true + true + true + snupkg @@ -21,6 +26,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 6349ebc16efdef9e860b09d14d243c6396005085 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 5 Jul 2020 23:11:23 +0200 Subject: [PATCH 07/27] Updated projects --- .github/workflows/myget-feature-deploy.yml | 2 +- src/ExecutionContext/ExecutionContext.csproj | 2 +- src/MongODM.AspNetCore/MongODM.AspNetCore.csproj | 2 +- src/MongODM.Core/MongODM.Core.csproj | 2 +- src/MongODM.Hangfire/MongODM.Hangfire.csproj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/myget-feature-deploy.yml b/.github/workflows/myget-feature-deploy.yml index d7f0abb2..d326709f 100644 --- a/.github/workflows/myget-feature-deploy.yml +++ b/.github/workflows/myget-feature-deploy.yml @@ -6,7 +6,7 @@ on: - 'feature/**' jobs: - build-test-package: + build-package: runs-on: ubuntu-latest steps: diff --git a/src/ExecutionContext/ExecutionContext.csproj b/src/ExecutionContext/ExecutionContext.csproj index ade3d190..0bc9820b 100644 --- a/src/ExecutionContext/ExecutionContext.csproj +++ b/src/ExecutionContext/ExecutionContext.csproj @@ -10,8 +10,8 @@ enable https://github.com/Etherna/mongodm + git true - true true snupkg diff --git a/src/MongODM.AspNetCore/MongODM.AspNetCore.csproj b/src/MongODM.AspNetCore/MongODM.AspNetCore.csproj index b7215d1f..38504295 100644 --- a/src/MongODM.AspNetCore/MongODM.AspNetCore.csproj +++ b/src/MongODM.AspNetCore/MongODM.AspNetCore.csproj @@ -10,8 +10,8 @@ enable https://github.com/Etherna/mongodm + git true - true true snupkg diff --git a/src/MongODM.Core/MongODM.Core.csproj b/src/MongODM.Core/MongODM.Core.csproj index 8002f7f4..37160e43 100644 --- a/src/MongODM.Core/MongODM.Core.csproj +++ b/src/MongODM.Core/MongODM.Core.csproj @@ -10,8 +10,8 @@ enable https://github.com/Etherna/mongodm + git true - true true snupkg diff --git a/src/MongODM.Hangfire/MongODM.Hangfire.csproj b/src/MongODM.Hangfire/MongODM.Hangfire.csproj index 3a68ca1d..5bfc3876 100644 --- a/src/MongODM.Hangfire/MongODM.Hangfire.csproj +++ b/src/MongODM.Hangfire/MongODM.Hangfire.csproj @@ -10,8 +10,8 @@ enable https://github.com/Etherna/mongodm + git true - true true snupkg From 7f028c23b2d534720ae29b8519e88e852ea9d10d Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Mon, 6 Jul 2020 23:14:45 +0200 Subject: [PATCH 08/27] Implementing bugfix solution --- .../ServiceCollectionExtensions.cs | 18 ++++--- ...calProxyTolerantDiscriminatorConvention.cs | 26 ++++++++++ .../ProxyModels/IProxyGenerator.cs | 1 + .../ProxyModels/ProxyGenerator.cs | 5 ++ src/MongODM.Core/ReflectionHelper.cs | 52 ------------------- .../Serializers/ExtendedClassMapSerializer.cs | 10 ---- .../ProxyTolerantBsonClassMapSerializer.cs | 50 ++++++++++++++++++ .../Serializers/ReferenceSerializer.cs | 14 +---- 8 files changed, 95 insertions(+), 81 deletions(-) create mode 100644 src/MongODM.Core/Conventions/HierarchicalProxyTolerantDiscriminatorConvention.cs create mode 100644 src/MongODM.Core/Serialization/Serializers/ProxyTolerantBsonClassMapSerializer.cs diff --git a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs index 910067e1..aa75c484 100644 --- a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ using Etherna.ExecContext.AsyncLocal; using Etherna.MongODM; using Etherna.MongODM.AspNetCore; +using Etherna.MongODM.Conventions; using Etherna.MongODM.ProxyModels; using Etherna.MongODM.Repositories; using Etherna.MongODM.Serialization; @@ -10,6 +11,7 @@ using Etherna.MongODM.Utility; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection.Extensions; +using MongoDB.Bson.Serialization; using System; using System.Collections.Generic; using System.Linq; @@ -22,12 +24,15 @@ public static MongODMConfiguration UseMongODM( this IServiceCollection services, IEnumerable? executionContexts = null) where TTaskRunner : class, ITaskRunner => - UseMongODM(services, executionContexts); + UseMongODM( + services, + new ProxyGenerator(new Castle.DynamicProxy.ProxyGenerator()), + executionContexts); - public static MongODMConfiguration UseMongODM( + public static MongODMConfiguration UseMongODM( this IServiceCollection services, + IProxyGenerator proxyGenerator, IEnumerable? executionContexts = null) - where TProxyGenerator: class, IProxyGenerator where TTaskRunner: class, ITaskRunner { services.TryAddSingleton(); @@ -45,7 +50,7 @@ public static MongODMConfiguration UseMongODM( executionContexts.First() : new ExecutionContextSelector(executionContexts); }); - services.TryAddSingleton(); + services.TryAddSingleton(proxyGenerator); services.TryAddSingleton(); // DbContext internal. @@ -65,8 +70,9 @@ public static MongODMConfiguration UseMongODM( //tasks services.TryAddTransient(); - //castle proxy generator. - services.TryAddSingleton(new Castle.DynamicProxy.ProxyGenerator()); + // Register conventions. + BsonSerializer.RegisterDiscriminatorConvention(typeof(object), + new HierarchicalProxyTolerantDiscriminatorConvention("_t", proxyGenerator)); return new MongODMConfiguration(services); } diff --git a/src/MongODM.Core/Conventions/HierarchicalProxyTolerantDiscriminatorConvention.cs b/src/MongODM.Core/Conventions/HierarchicalProxyTolerantDiscriminatorConvention.cs new file mode 100644 index 00000000..83f91f0d --- /dev/null +++ b/src/MongODM.Core/Conventions/HierarchicalProxyTolerantDiscriminatorConvention.cs @@ -0,0 +1,26 @@ +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/ProxyModels/IProxyGenerator.cs b/src/MongODM.Core/ProxyModels/IProxyGenerator.cs index 10902b19..f414859e 100644 --- a/src/MongODM.Core/ProxyModels/IProxyGenerator.cs +++ b/src/MongODM.Core/ProxyModels/IProxyGenerator.cs @@ -7,5 +7,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/ProxyGenerator.cs b/src/MongODM.Core/ProxyModels/ProxyGenerator.cs index 21190269..bb9006b0 100644 --- a/src/MongODM.Core/ProxyModels/ProxyGenerator.cs +++ b/src/MongODM.Core/ProxyModels/ProxyGenerator.cs @@ -127,6 +127,11 @@ public bool IsProxyType(Type type) } } + public Type PurgeProxyType(Type type) => + IsProxyType(type) ? + type.BaseType : + type; + // Protected virtual methods. protected virtual IEnumerable GetCustomAdditionalInterfaces(Type modelType) => Array.Empty(); diff --git a/src/MongODM.Core/ReflectionHelper.cs b/src/MongODM.Core/ReflectionHelper.cs index 887881bd..d170aacb 100644 --- a/src/MongODM.Core/ReflectionHelper.cs +++ b/src/MongODM.Core/ReflectionHelper.cs @@ -12,49 +12,6 @@ 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) { Expression expressionToCheck = lambdaExpression; @@ -123,15 +80,6 @@ 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) diff --git a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs index ae637f1d..3949b636 100644 --- a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs @@ -162,16 +162,6 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati bsonWriter, builder => builder.IsDynamicType = context.IsDynamicType); - // Purify model from proxy class. - if (value.GetType() != typeof(TModel)) - { - var constructor = typeof(TModel).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null) ?? - typeof(TModel).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null); - var newModel = (TModel)constructor.Invoke(new object[0]); - ReflectionHelper.CloneModel(value, newModel); - value = newModel; - } - // Serialize. Serializer.Serialize(localContext, args, value); diff --git a/src/MongODM.Core/Serialization/Serializers/ProxyTolerantBsonClassMapSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ProxyTolerantBsonClassMapSerializer.cs new file mode 100644 index 00000000..21c6073d --- /dev/null +++ b/src/MongODM.Core/Serialization/Serializers/ProxyTolerantBsonClassMapSerializer.cs @@ -0,0 +1,50 @@ +using Etherna.MongODM.ProxyModels; +using MongoDB.Bson.Serialization; + +namespace Etherna.MongODM.Serialization.Serializers +{ + public class ProxyTolerantBsonClassMapSerializer : BsonClassMapSerializer + { + // Fields. + private readonly IProxyGenerator proxyGenerator; + + // Constructors. + public ProxyTolerantBsonClassMapSerializer( + BsonClassMap classMap, + IProxyGenerator proxyGenerator) + : base(classMap) + { + this.proxyGenerator = proxyGenerator; + } + + // Methods. + /// + /// Serializes a value. + /// + /// The serialization context. + /// The serialization args. + /// The object. + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TClass value) + { + var bsonWriter = context.Writer; + + if (value == null) + { + bsonWriter.WriteNull(); + } + else + { + var actualType = value.GetType(); + if (proxyGenerator.PurgeProxyType(actualType) == typeof(TClass)) + { + SerializeClass(context, args, value); + } + else + { + var serializer = BsonSerializer.LookupSerializer(actualType); + serializer.Serialize(context, args, value); + } + } + } + } +} diff --git a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs index 658cb0c0..2ab39c63 100644 --- a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs @@ -220,7 +220,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati } // Identify class map. - bool useProxyClass = IsProxyClassType(value, out Type valueType); + IsProxyClassType(value, out Type valueType); BsonClassMap classMap; configLockClassMaps.EnterReadLock(); @@ -237,18 +237,6 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati configLockClassMaps.ExitReadLock(); } - // Remove proxy class. - if (useProxyClass) - { - var constructor = valueType.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null) ?? - valueType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null); - var newModel = (TModelBase)constructor.Invoke(new object[0]); - ReflectionHelper.CloneModel(value, newModel, from mMap in classMap.AllMemberMaps - where mMap != classMap.ExtraElementsMemberMap - select mMap.MemberInfo as PropertyInfo); - value = newModel; - } - // Clear extra elements. (value as IModel)?.ExtraElements?.Clear(); From 23e0953a710542c71cb6a2428ab37bcb47bfac7f Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Tue, 7 Jul 2020 18:35:20 +0200 Subject: [PATCH 09/27] Moved SetCreator inside DocumentSchemaRegister --- .../Serialization/DocumentSchemaRegister.cs | 7 +++ .../ProxyTolerantBsonClassMapSerializer.cs | 50 ------------------- 2 files changed, 7 insertions(+), 50 deletions(-) delete mode 100644 src/MongODM.Core/Serialization/Serializers/ProxyTolerantBsonClassMapSerializer.cs diff --git a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs index 8ce721c0..6375fb72 100644 --- a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs +++ b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs @@ -164,6 +164,13 @@ public void RegisterModelSchema( if (IsFrozen) throw new InvalidOperationException("Register is frozen"); + // If not abstract, adjustments for use proxygenerator. + if (!typeof(TModel).IsAbstract) + { + //set creator + classMap.SetCreator(() => dbContext.ProxyGenerator.CreateInstance(dbContext)); + } + // Generate model serializer. IBsonSerializer? serializer = null; diff --git a/src/MongODM.Core/Serialization/Serializers/ProxyTolerantBsonClassMapSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ProxyTolerantBsonClassMapSerializer.cs deleted file mode 100644 index 21c6073d..00000000 --- a/src/MongODM.Core/Serialization/Serializers/ProxyTolerantBsonClassMapSerializer.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Etherna.MongODM.ProxyModels; -using MongoDB.Bson.Serialization; - -namespace Etherna.MongODM.Serialization.Serializers -{ - public class ProxyTolerantBsonClassMapSerializer : BsonClassMapSerializer - { - // Fields. - private readonly IProxyGenerator proxyGenerator; - - // Constructors. - public ProxyTolerantBsonClassMapSerializer( - BsonClassMap classMap, - IProxyGenerator proxyGenerator) - : base(classMap) - { - this.proxyGenerator = proxyGenerator; - } - - // Methods. - /// - /// Serializes a value. - /// - /// The serialization context. - /// The serialization args. - /// The object. - public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TClass value) - { - var bsonWriter = context.Writer; - - if (value == null) - { - bsonWriter.WriteNull(); - } - else - { - var actualType = value.GetType(); - if (proxyGenerator.PurgeProxyType(actualType) == typeof(TClass)) - { - SerializeClass(context, args, value); - } - else - { - var serializer = BsonSerializer.LookupSerializer(actualType); - serializer.Serialize(context, args, value); - } - } - } - } -} From 669f50e662ae26ecd6e104ba6446ba383094b62a Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Wed, 8 Jul 2020 23:31:22 +0200 Subject: [PATCH 10/27] Fixed serialization of proxy models --- .../ServiceCollectionExtensions.cs | 13 ++++++++----- .../Serialization/DocumentSchema.cs | 9 ++++++++- .../Serialization/DocumentSchemaRegister.cs | 17 ++++++++++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs index aa75c484..bd6e02a5 100644 --- a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using Etherna.MongODM; using Etherna.MongODM.AspNetCore; using Etherna.MongODM.Conventions; +using Etherna.MongODM.Models; using Etherna.MongODM.ProxyModels; using Etherna.MongODM.Repositories; using Etherna.MongODM.Serialization; @@ -20,20 +21,22 @@ 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( + where TTaskRunner : class, ITaskRunner + where TModelBase : class, IModel => //needed because of this https://jira.mongodb.org/browse/CSHARP-3154 + UseMongODM( services, new ProxyGenerator(new Castle.DynamicProxy.ProxyGenerator()), executionContexts); - public static MongODMConfiguration UseMongODM( + public static MongODMConfiguration UseMongODM( this IServiceCollection services, IProxyGenerator proxyGenerator, IEnumerable? executionContexts = null) where TTaskRunner: class, ITaskRunner + where TModelBase: class, IModel //needed because of this https://jira.mongodb.org/browse/CSHARP-3154 { services.TryAddSingleton(); @@ -71,7 +74,7 @@ public static MongODMConfiguration UseMongODM( services.TryAddTransient(); // Register conventions. - BsonSerializer.RegisterDiscriminatorConvention(typeof(object), + BsonSerializer.RegisterDiscriminatorConvention(typeof(TModelBase), new HierarchicalProxyTolerantDiscriminatorConvention("_t", proxyGenerator)); return new MongODMConfiguration(services); diff --git a/src/MongODM.Core/Serialization/DocumentSchema.cs b/src/MongODM.Core/Serialization/DocumentSchema.cs index 0244d193..15b08b63 100644 --- a/src/MongODM.Core/Serialization/DocumentSchema.cs +++ b/src/MongODM.Core/Serialization/DocumentSchema.cs @@ -6,10 +6,16 @@ namespace Etherna.MongODM.Serialization public class DocumentSchema { // Constructors. - public DocumentSchema(BsonClassMap classMap, Type modelType, IBsonSerializer? serializer, SemanticVersion version) + public DocumentSchema( + BsonClassMap classMap, + Type modelType, + BsonClassMap? proxyClassMap, + IBsonSerializer? serializer, + SemanticVersion version) { ClassMap = classMap; ModelType = modelType; + ProxyClassMap = proxyClassMap; Serializer = serializer; Version = version; } @@ -17,6 +23,7 @@ public DocumentSchema(BsonClassMap classMap, Type modelType, IBsonSerializer? se // Properties. public BsonClassMap ClassMap { get; } public Type ModelType { get; } + public BsonClassMap? ProxyClassMap { get; } public IBsonSerializer? Serializer { get; } public SemanticVersion Version { get; } } diff --git a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs index 6375fb72..bd47fec2 100644 --- a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs +++ b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs @@ -71,12 +71,18 @@ public void Freeze() foreach (var schemaGroup in schemas.GroupBy(s => s.ModelType) .Select(group => group.OrderByDescending(s => s.Version).First())) { - // Register class map. + // Register regular model. + //register class map BsonClassMap.RegisterClassMap(schemaGroup.ClassMap); - // Register serializer. + //register serializer if (schemaGroup.Serializer != null) BsonSerializer.RegisterSerializer(schemaGroup.ModelType, schemaGroup.Serializer); + + // Register proxy model. + //register proxy class map + if (schemaGroup.ProxyClassMap != null) + BsonClassMap.RegisterClassMap(schemaGroup.ProxyClassMap); } // Compile dependency registers. @@ -165,10 +171,15 @@ public void RegisterModelSchema( throw new InvalidOperationException("Register is frozen"); // If not abstract, adjustments for use proxygenerator. + BsonClassMap? proxyClassMap = null; if (!typeof(TModel).IsAbstract) { //set creator classMap.SetCreator(() => dbContext.ProxyGenerator.CreateInstance(dbContext)); + + //generate proxy classmap + proxyClassMap = new BsonClassMap( + dbContext.ProxyGenerator.CreateInstance(dbContext).GetType()); } // Generate model serializer. @@ -187,7 +198,7 @@ public void RegisterModelSchema( { AddVersion = typeof(IEntityModel).IsAssignableFrom(typeof(TModel)) }; //true only for entity models // Register schema. - schemas.Add(new DocumentSchema(classMap, typeof(TModel), serializer, fromVersion)); + schemas.Add(new DocumentSchema(classMap, typeof(TModel), proxyClassMap, serializer, fromVersion)); } finally { From acb01d7c7c5eb52f19ec711a3270ef6e952e488a Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Thu, 9 Jul 2020 00:41:07 +0200 Subject: [PATCH 11/27] Updated ReferenceableInterceptor for handle derived types --- .../ProxyModels/ReferenceableInterceptor.cs | 17 ++++++++++++++--- src/MongODM.Core/Repositories/IRepository.cs | 14 ++++++++++++++ src/MongODM.Core/Repositories/RepositoryBase.cs | 6 ++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs b/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs index 3cf8425e..cd4650c5 100644 --- a/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs +++ b/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs @@ -16,7 +16,7 @@ public class ReferenceableInterceptor : ModelInterceptorBase settedMemberNames = new Dictionary(); // - private readonly IRepository repository; + private readonly IRepository repository; // Constructors. public ReferenceableInterceptor( @@ -24,7 +24,15 @@ public ReferenceableInterceptor( IDbContext dbContext) : base(additionalInterfaces) { - repository = (IRepository)dbContext.RepositoryRegister.ModelRepositoryMap[typeof(TModel)]; + 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. @@ -123,10 +131,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)) as TModel; MergeFullModel(model, fullModel); } } diff --git a/src/MongODM.Core/Repositories/IRepository.cs b/src/MongODM.Core/Repositories/IRepository.cs index 73fa5760..636faffe 100644 --- a/src/MongODM.Core/Repositories/IRepository.cs +++ b/src/MongODM.Core/Repositories/IRepository.cs @@ -20,6 +20,20 @@ Task BuildIndexesAsync( Task DeleteAsync( IEntityModel model, CancellationToken cancellationToken = default); + + Task FindOneAsync( + object id, + CancellationToken cancellationToken = default); + + /// + /// Try to find a model and don't throw exception if it is not found + /// + /// Model's Id + /// The cancellation token + /// The model, null if it doesn't exist + Task TryFindOneAsync( + object id, + CancellationToken cancellationToken = default); } public interface IRepository : IRepository diff --git a/src/MongODM.Core/Repositories/RepositoryBase.cs b/src/MongODM.Core/Repositories/RepositoryBase.cs index 1aaf46a9..cc6d7f25 100644 --- a/src/MongODM.Core/Repositories/RepositoryBase.cs +++ b/src/MongODM.Core/Repositories/RepositoryBase.cs @@ -84,6 +84,9 @@ public async Task DeleteAsync(IEntityModel model, CancellationToken cancellation await DeleteAsync(castedModel, cancellationToken); } + public async Task FindOneAsync(object id, CancellationToken cancellationToken = default) => + await FindOneAsync((TKey)id, cancellationToken); + public virtual async Task FindOneAsync( TKey id, CancellationToken cancellationToken = default) @@ -98,6 +101,9 @@ public virtual async Task FindOneAsync( return await FindOneOnDBAsync(id, cancellationToken); } + public async Task TryFindOneAsync(object id, CancellationToken cancellationToken = default) => + await TryFindOneAsync((TKey)id, cancellationToken); + public async Task TryFindOneAsync( TKey id, CancellationToken cancellationToken = default) From 3a0bb4f42fcc9b179e8beddd38992e2994288bd9 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Thu, 9 Jul 2020 00:56:45 +0200 Subject: [PATCH 12/27] Updated ReferenceSerializer with new proxyGenerator.PurgeProxyType implementation --- .../Serializers/ReferenceSerializer.cs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs index 2ab39c63..017d88a9 100644 --- a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs @@ -180,7 +180,7 @@ public IBsonSerializer GetAdapter() public bool GetDocumentId(object document, out object id, out Type idNominalType, out IIdGenerator idGenerator) { - IsProxyClassType(document, out Type documentType); + var documentType = proxyGenerator.PurgeProxyType(document.GetType()); var serializer = (IBsonIdProvider)GetSerializer(documentType); return serializer.GetDocumentId(document, out id, out idNominalType, out idGenerator); } @@ -220,7 +220,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati } // Identify class map. - IsProxyClassType(value, out Type valueType); + var valueType = proxyGenerator.PurgeProxyType(value.GetType()); BsonClassMap classMap; configLockClassMaps.EnterReadLock(); @@ -247,7 +247,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati public void SetDocumentId(object document, object id) { - IsProxyClassType(document, out Type documentType); + var documentType = proxyGenerator.PurgeProxyType(document.GetType()); var serializer = (IBsonIdProvider)GetSerializer(documentType); serializer.SetDocumentId(document, id); } @@ -340,16 +340,5 @@ private IBsonSerializer GetSerializer(Type actualType) configLockSerializers.ExitWriteLock(); } } - - private bool IsProxyClassType(TModel value, out Type modelType) - { - modelType = value!.GetType(); - if (proxyGenerator.IsProxyType(modelType)) - { - modelType = modelType.BaseType; - return true; - } - return false; - } } } From bfbbec1ad582a398f1a686c42d22fde8db7e7ebc Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Thu, 9 Jul 2020 01:14:34 +0200 Subject: [PATCH 13/27] fixed tests --- .../ExtendedClassMapSerializerTest.cs | 28 ------------------- .../Models/FakeModelProxy.cs | 6 ---- .../ReferenceableInterceptorTest.cs | 14 ++++++---- 3 files changed, 9 insertions(+), 39 deletions(-) delete mode 100644 test/MongODM.Core.Tests/Models/FakeModelProxy.cs diff --git a/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs b/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs index e1a3408b..5e8ab713 100644 --- a/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs +++ b/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs @@ -306,34 +306,6 @@ public static IEnumerable SerializationTests new BsonElement("StringProp", BsonNull.Value) } as IEnumerable), condition: _ => false), - - // With a proxy class. - new SerializationTestElement( - new FakeModelProxy() - { - Id = "idVal", - IntegerProp = 42, - ObjectProp = new FakeModel(), - StringProp = "yes" - }, - new BsonDocument(new BsonElement[] - { - new BsonElement("_id", new BsonString("idVal")), - new BsonElement("CreationDateTime", new BsonDateTime(new DateTime())), - new BsonElement("EnumerableProp", BsonNull.Value), - new BsonElement("IntegerProp", new BsonInt32(42)), - new BsonElement("ObjectProp", new BsonDocument(new BsonElement[] - { - new BsonElement("_id", BsonNull.Value), - new BsonElement("CreationDateTime", new BsonDateTime(new DateTime())), - new BsonElement("EnumerableProp", BsonNull.Value), - new BsonElement("IntegerProp", new BsonInt32(0)), - new BsonElement("ObjectProp", BsonNull.Value), - new BsonElement("StringProp", BsonNull.Value) - } as IEnumerable)), - new BsonElement("StringProp", new BsonString("yes")) - } as IEnumerable), - condition: _ => false) }; return tests.Select(t => new object[] { t }); diff --git a/test/MongODM.Core.Tests/Models/FakeModelProxy.cs b/test/MongODM.Core.Tests/Models/FakeModelProxy.cs deleted file mode 100644 index 5d7e3fa0..00000000 --- a/test/MongODM.Core.Tests/Models/FakeModelProxy.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Etherna.MongODM.Models -{ - public class FakeModelProxy : FakeModel - { - } -} diff --git a/test/MongODM.Core.Tests/ReferenceableInterceptorTest.cs b/test/MongODM.Core.Tests/ReferenceableInterceptorTest.cs index 473ba8ae..50220fe8 100644 --- a/test/MongODM.Core.Tests/ReferenceableInterceptorTest.cs +++ b/test/MongODM.Core.Tests/ReferenceableInterceptorTest.cs @@ -3,6 +3,7 @@ using Etherna.MongODM.ProxyModels; using Etherna.MongODM.Repositories; using Moq; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -24,8 +25,11 @@ public ReferenceableInterceptorTest() repositoryMock = new Mock>(); dbContextMock = new Mock(); - dbContextMock.Setup(c => c.RepositoryRegister.ModelRepositoryMap[typeof(FakeModel)]) - .Returns(() => repositoryMock.Object); + dbContextMock.Setup(c => c.RepositoryRegister.ModelRepositoryMap) + .Returns(() => new Dictionary + { + [typeof(FakeModel)] = repositoryMock.Object + }); interceptor = new ReferenceableInterceptor( new[] { typeof(IReferenceable) }, @@ -122,8 +126,8 @@ public void GetNotLoadedMember() IntegerProp = 42 }; - repositoryMock.Setup(r => r.TryFindOneAsync(modelId, It.IsAny())) - .Returns(Task.FromResult(new FakeModel + repositoryMock.Setup(r => r.TryFindOneAsync((object)modelId, It.IsAny())) + .Returns(Task.FromResult(new FakeModel { Id = modelId, IntegerProp = 7, @@ -148,7 +152,7 @@ public void GetNotLoadedMember() // Assert. getPropertyInvocationMock.Verify(i => i.Proceed(), Times.Once()); - repositoryMock.Verify(r => r.TryFindOneAsync(modelId, It.IsAny()), Times.Once); + repositoryMock.Verify(r => r.TryFindOneAsync((object)modelId, It.IsAny()), Times.Once); interceptor.Intercept(getIsSummaryInvocationMock.Object); interceptor.Intercept(getLoadedMembersInvocationMock.Object); From 43a05a68484b45bfd8ad8087859a7d2a61540f59 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Thu, 9 Jul 2020 01:19:05 +0200 Subject: [PATCH 14/27] Removed feature deploy action --- .github/workflows/myget-feature-deploy.yml | 29 ---------------------- MongODM.sln | 1 - 2 files changed, 30 deletions(-) delete mode 100644 .github/workflows/myget-feature-deploy.yml diff --git a/.github/workflows/myget-feature-deploy.yml b/.github/workflows/myget-feature-deploy.yml deleted file mode 100644 index d326709f..00000000 --- a/.github/workflows/myget-feature-deploy.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Feature deploy to MyGet - -on: - push: - branches: - - 'feature/**' - -jobs: - build-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 Debug - - - name: Generate nuget package - run: dotnet pack --configuration Debug -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/MongODM.sln b/MongODM.sln index da88ead2..ae71d5ad 100644 --- a/MongODM.sln +++ b/MongODM.sln @@ -26,7 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 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-unstable-deploy.yml = .github\workflows\myget-unstable-deploy.yml .github\workflows\nuget-stable-deploy.yml = .github\workflows\nuget-stable-deploy.yml EndProjectSection From 18eae977fad0255b95d8d39883b41a925589d924 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Thu, 9 Jul 2020 19:24:07 +0200 Subject: [PATCH 15/27] Implemented db operation collection --- .../ServiceCollectionExtensions.cs | 9 +++++++++ src/MongODM.Core/DbContext.cs | 14 ++++---------- src/MongODM.Core/DbContextOptions.cs | 4 ++-- src/MongODM.Core/IDbContext.cs | 17 +++++++++++------ .../Operations/ModelMaps/OperationBaseMap.cs | 2 +- .../Serialization/DocumentSchemaRegister.cs | 2 +- 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs index bd6e02a5..f6f1b86b 100644 --- a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using Etherna.MongODM.AspNetCore; using Etherna.MongODM.Conventions; using Etherna.MongODM.Models; +using Etherna.MongODM.Operations; using Etherna.MongODM.ProxyModels; using Etherna.MongODM.Repositories; using Etherna.MongODM.Serialization; @@ -12,7 +13,9 @@ using Etherna.MongODM.Utility; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection.Extensions; +using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Conventions; using System; using System.Collections.Generic; using System.Linq; @@ -74,8 +77,14 @@ public static MongODMConfiguration UseMongODM( services.TryAddTransient(); // 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(OperationBase), + new HierarchicalProxyTolerantDiscriminatorConvention("_t", proxyGenerator)); return new MongODMConfiguration(services); } diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index cdc67cd8..218af3eb 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -33,7 +33,7 @@ public DbContext( DbMaintainer = dependencies.DbMaintainer; DbOperations = new CollectionRepository(options.DbOperationsCollectionName); DocumentSchemaRegister = dependencies.DocumentSchemaRegister; - DocumentVersion = options.DocumentVersion; + ApplicationVersion = options.ApplicationVersion; LibraryVersion = GetType() .GetTypeInfo() .Assembly @@ -57,17 +57,11 @@ public DbContext( 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. - //system maps + //internal maps new OperationBaseMap().Register(this); - //user maps + //application maps foreach (var maps in ModelMapsCollectors) maps.Register(this); @@ -86,7 +80,7 @@ public DbContext( public IDbMaintainer DbMaintainer { get; } public ICollectionRepository DbOperations { get; } public IDocumentSchemaRegister DocumentSchemaRegister { get; } - public SemanticVersion DocumentVersion { get; } + public SemanticVersion ApplicationVersion { get; } public bool IsMigrating { get; private set; } public SemanticVersion LibraryVersion { get; } public IProxyGenerator ProxyGenerator { get; } diff --git a/src/MongODM.Core/DbContextOptions.cs b/src/MongODM.Core/DbContextOptions.cs index 3f5273b6..a81a7113 100644 --- a/src/MongODM.Core/DbContextOptions.cs +++ b/src/MongODM.Core/DbContextOptions.cs @@ -5,11 +5,11 @@ 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] .Split('/').Last(); - public string DbOperationsCollectionName { get; set; } = "_dbOperations"; - public SemanticVersion DocumentVersion { get; set; } = "1.0.0"; + public string DbOperationsCollectionName { get; set; } = "_db_ops"; } public class DbContextOptions : DbContextOptions diff --git a/src/MongODM.Core/IDbContext.cs b/src/MongODM.Core/IDbContext.cs index 3af62421..48577522 100644 --- a/src/MongODM.Core/IDbContext.cs +++ b/src/MongODM.Core/IDbContext.cs @@ -14,6 +14,11 @@ namespace Etherna.MongODM /// public interface IDbContext { + /// + /// Current application version. + /// + SemanticVersion ApplicationVersion { get; } + // Properties. /// /// Current MongoDB client. @@ -40,16 +45,16 @@ public interface IDbContext /// IDocumentSchemaRegister DocumentSchemaRegister { get; } - /// - /// Current operating document version. - /// - SemanticVersion DocumentVersion { get; } - /// /// Flag reporting eventual current migration operation. /// bool IsMigrating { get; } - + + /// + /// Current MongODM library version + /// + SemanticVersion LibraryVersion { get; } + /// /// Current model proxy generator. /// diff --git a/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs b/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs index f6b9e2aa..479276ea 100644 --- a/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs +++ b/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs @@ -23,7 +23,7 @@ public void Register(IDbContext dbContext) initCustomSerializer: () => new ExtendedClassMapSerializer( dbContext.DbCache, - dbContext.DocumentVersion, + dbContext.LibraryVersion, dbContext.SerializerModifierAccessor) { AddVersion = true }); } diff --git a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs index bd47fec2..23b9b416 100644 --- a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs +++ b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs @@ -192,7 +192,7 @@ public void RegisterModelSchema( serializer = new ExtendedClassMapSerializer( dbContext.DbCache, - dbContext.DocumentVersion, + dbContext.ApplicationVersion, serializerModifierAccessor, (m, v) => modelMigrationAsync?.Invoke(m, v) ?? Task.FromResult(m)) { AddVersion = typeof(IEntityModel).IsAssignableFrom(typeof(TModel)) }; //true only for entity models From 9b94dd49b0a5a440d94eefad8eddf215c8d533f1 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Thu, 9 Jul 2020 19:26:47 +0200 Subject: [PATCH 16/27] Removed GitVersion.yml because actually unuseful --- GitVersion.yml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 GitVersion.yml diff --git a/GitVersion.yml b/GitVersion.yml deleted file mode 100644 index 613e72ef..00000000 --- a/GitVersion.yml +++ /dev/null @@ -1,6 +0,0 @@ -branches: - feature: - mode: ContinuousDeployment -ignore: - sha: [] -merge-message-formats: {} From 75fd0524c76f91dd2eb86b73becf7c558e08d23a Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Fri, 10 Jul 2020 02:54:36 +0200 Subject: [PATCH 17/27] Added StaticConfigurationBuilder --- .../ServiceCollectionExtensions.cs | 28 ++++++------------- .../StaticConfigurationBuilder.cs | 27 ++++++++++++++++++ .../Utility/DbContextDependencies.cs | 5 +++- .../Utility/IStaticConfigurationBuilder.cs | 12 ++++++++ 4 files changed, 52 insertions(+), 20 deletions(-) create mode 100644 src/MongODM.AspNetCore/StaticConfigurationBuilder.cs create mode 100644 src/MongODM.Core/Utility/IStaticConfigurationBuilder.cs diff --git a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs index f6f1b86b..dae9c683 100644 --- a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs @@ -2,9 +2,7 @@ using Etherna.ExecContext.AsyncLocal; using Etherna.MongODM; using Etherna.MongODM.AspNetCore; -using Etherna.MongODM.Conventions; using Etherna.MongODM.Models; -using Etherna.MongODM.Operations; using Etherna.MongODM.ProxyModels; using Etherna.MongODM.Repositories; using Etherna.MongODM.Serialization; @@ -13,9 +11,6 @@ using Etherna.MongODM.Utility; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection.Extensions; -using MongoDB.Bson; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Conventions; using System; using System.Collections.Generic; using System.Linq; @@ -29,15 +24,14 @@ public static MongODMConfiguration UseMongODM( IEnumerable? executionContexts = null) where TTaskRunner : class, ITaskRunner where TModelBase : class, IModel => //needed because of this https://jira.mongodb.org/browse/CSHARP-3154 - UseMongODM( + UseMongODM( services, - new ProxyGenerator(new Castle.DynamicProxy.ProxyGenerator()), executionContexts); - public static MongODMConfiguration UseMongODM( + public static MongODMConfiguration UseMongODM( this IServiceCollection services, - IProxyGenerator proxyGenerator, IEnumerable? executionContexts = null) + where TProxyGenerator : class, IProxyGenerator where TTaskRunner: class, ITaskRunner where TModelBase: class, IModel //needed because of this https://jira.mongodb.org/browse/CSHARP-3154 { @@ -56,7 +50,7 @@ public static MongODMConfiguration UseMongODM( executionContexts.First() : new ExecutionContextSelector(executionContexts); }); - services.TryAddSingleton(proxyGenerator); + services.TryAddSingleton(); services.TryAddSingleton(); // DbContext internal. @@ -76,15 +70,11 @@ public static MongODMConfiguration UseMongODM( //tasks services.TryAddTransient(); - // 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(OperationBase), - new HierarchicalProxyTolerantDiscriminatorConvention("_t", proxyGenerator)); + //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..f0389ab6 --- /dev/null +++ b/src/MongODM.AspNetCore/StaticConfigurationBuilder.cs @@ -0,0 +1,27 @@ +using Etherna.MongODM.Conventions; +using Etherna.MongODM.Operations; +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(OperationBase), + new HierarchicalProxyTolerantDiscriminatorConvention("_t", proxyGenerator)); + } + } +} diff --git a/src/MongODM.Core/Utility/DbContextDependencies.cs b/src/MongODM.Core/Utility/DbContextDependencies.cs index 3eb308f3..371cf960 100644 --- a/src/MongODM.Core/Utility/DbContextDependencies.cs +++ b/src/MongODM.Core/Utility/DbContextDependencies.cs @@ -13,7 +13,10 @@ public DbContextDependencies( IDocumentSchemaRegister documentSchemaRegister, IProxyGenerator proxyGenerator, IRepositoryRegister repositoryRegister, - ISerializerModifierAccessor serializerModifierAccessor) + ISerializerModifierAccessor serializerModifierAccessor, +#pragma warning disable IDE0060 // Remove unused parameter. It's needed for run static configurations + IStaticConfigurationBuilder staticConfigurationBuilder) +#pragma warning restore IDE0060 // Remove unused parameter { DbCache = dbCache; DbMaintainer = dbMaintainer; diff --git a/src/MongODM.Core/Utility/IStaticConfigurationBuilder.cs b/src/MongODM.Core/Utility/IStaticConfigurationBuilder.cs new file mode 100644 index 00000000..92e55ab4 --- /dev/null +++ b/src/MongODM.Core/Utility/IStaticConfigurationBuilder.cs @@ -0,0 +1,12 @@ +namespace Etherna.MongODM.Utility +{ + /// + /// This interface has the scope to inizialize only one time static configurations, when IoC system + /// has been configured, dependencies can be resolved, and before that any dbcontext starts to operate. + /// For a proper use, implements it in a class where configuration is invoked by constructor. + /// So configure it as a singleton on IoC system, and injectit as a dependency for DbContext. + /// + public interface IStaticConfigurationBuilder + { + } +} \ No newline at end of file From 76f9c2f39073f4a605d906f1314ef2445acc2dcc Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Fri, 10 Jul 2020 23:51:01 +0200 Subject: [PATCH 18/27] Fixed ReferenceSerializer --- .../Serializers/ReferenceSerializer.cs | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs index 017d88a9..b62a6d84 100644 --- a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs @@ -184,7 +184,32 @@ public bool GetDocumentId(object document, out object id, out Type idNominalType var serializer = (IBsonIdProvider)GetSerializer(documentType); return serializer.GetDocumentId(document, out id, out idNominalType, out idGenerator); } - + + public ReferenceSerializer RegisterProxyType() + { + var proxyType = proxyGenerator.CreateInstance(dbContext)!.GetType(); + + // Initialize class map. + var createBsonClassMapInfo = GetType().GetMethod(nameof(CreateBsonClassMap), BindingFlags.Instance | BindingFlags.NonPublic); + var createBsonClassMap = createBsonClassMapInfo.MakeGenericMethod(proxyType); + + var classMap = (BsonClassMap)createBsonClassMap.Invoke(this, new object[] { null! }); + + // Add info to dictionary of registered types. + configLockClassMaps.EnterWriteLock(); + try + { + registeredClassMaps.Add(proxyType, classMap); + } + finally + { + configLockClassMaps.ExitWriteLock(); + } + + // Return this for cascade use. + return this; + } + public ReferenceSerializer RegisterType(Action>? classMapInitializer = null) where TModel : class { @@ -219,29 +244,11 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati return; } - // Identify class map. - var valueType = proxyGenerator.PurgeProxyType(value.GetType()); - - BsonClassMap classMap; - configLockClassMaps.EnterReadLock(); - try - { - if (!registeredClassMaps.ContainsKey(valueType)) - { - throw new InvalidOperationException("Can't identify right class map"); - } - classMap = registeredClassMaps[valueType]; - } - finally - { - configLockClassMaps.ExitReadLock(); - } - // Clear extra elements. - (value as IModel)?.ExtraElements?.Clear(); + value.ExtraElements?.Clear(); // Serialize object. - var serializer = GetSerializer(valueType); + var serializer = GetSerializer(value.GetType()); serializer.Serialize(context, args, value); } @@ -271,8 +278,16 @@ where pair.Value.GetMemberMap(memberName) != null } // Helpers. - private BsonClassMap CreateBsonClassMap(Action> classMapInitializer) + /// + /// Create a new BsonClassMap for type TModel, and link its baseClassMap if already registered + /// + /// The destination model type of class map + /// The class map inizializer. Empty initilization if null + /// The new created class map + private BsonClassMap CreateBsonClassMap(Action>? classMapInitializer = null) { + classMapInitializer ??= cm => { }; + BsonClassMap classMap = new BsonClassMap(classMapInitializer); var baseType = typeof(TModel).BaseType; configLockClassMaps.EnterReadLock(); From 112eb4626d81e677e3f72825587ff91360be5868 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sat, 11 Jul 2020 16:56:21 +0200 Subject: [PATCH 19/27] First implementation of seeding Added property Identifier to dbcontext --- src/MongODM.Core/DbContext.cs | 41 ++++++++++++++++++-- src/MongODM.Core/DbContextOptions.cs | 1 + src/MongODM.Core/IDbContext.cs | 7 +++- src/MongODM.Core/Operations/OperationBase.cs | 8 +--- src/MongODM.Core/Operations/SeedOperation.cs | 11 ++++++ 5 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 src/MongODM.Core/Operations/SeedOperation.cs diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index 218af3eb..322c78fa 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -7,9 +7,8 @@ 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; @@ -29,11 +28,12 @@ public DbContext( IDbContextDependencies dependencies, DbContextOptions options) { + ApplicationVersion = options.ApplicationVersion; DbCache = dependencies.DbCache; DbMaintainer = dependencies.DbMaintainer; DbOperations = new CollectionRepository(options.DbOperationsCollectionName); DocumentSchemaRegister = dependencies.DocumentSchemaRegister; - ApplicationVersion = options.ApplicationVersion; + Identifier = options.Identifier ?? GetType().Name; LibraryVersion = GetType() .GetTypeInfo() .Assembly @@ -67,9 +67,13 @@ public DbContext( // Build and freeze document schema register. DocumentSchemaRegister.Freeze(); + + // Check for seeding. + SeedIfNeeded(); } // Public properties. + public SemanticVersion ApplicationVersion { get; } public IReadOnlyCollection ChangedModelsList => DbCache.LoadedModels.Values .Where(model => (model as IAuditable)?.IsChanged == true) @@ -80,7 +84,7 @@ public DbContext( public IDbMaintainer DbMaintainer { get; } public ICollectionRepository DbOperations { get; } public IDocumentSchemaRegister DocumentSchemaRegister { get; } - public SemanticVersion ApplicationVersion { get; } + public string Identifier { get; } public bool IsMigrating { get; private set; } public SemanticVersion LibraryVersion { get; } public IProxyGenerator ProxyGenerator { get; } @@ -153,5 +157,34 @@ public virtual async Task SaveChangesAsync(CancellationToken cancellationToken = public Task StartSessionAsync(CancellationToken cancellationToken = default) => Client.StartSessionAsync(cancellationToken: cancellationToken); + + // Protected methods. + protected virtual Task Seed() => + Task.CompletedTask; + + // Helpers. + private void SeedIfNeeded() + { + // Check if already seeded. + var queryTask = DbOperations.QueryElementsAsync(elements => + elements.OfType().AnyAsync(sop => sop.DbContextName == Identifier)); + queryTask.ConfigureAwait(false); + queryTask.Wait(); + + //skip if already seeded + if (queryTask.Result) + return; + + // Seed. + var seedTask = Seed(); + seedTask.ConfigureAwait(false); + seedTask.Wait(); + + // Report operation. + var seedOperation = new SeedOperation(this); + var createTask = DbOperations.CreateAsync(seedOperation); + createTask.ConfigureAwait(false); + createTask.Wait(); + } } } \ No newline at end of file diff --git a/src/MongODM.Core/DbContextOptions.cs b/src/MongODM.Core/DbContextOptions.cs index a81a7113..63386df3 100644 --- a/src/MongODM.Core/DbContextOptions.cs +++ b/src/MongODM.Core/DbContextOptions.cs @@ -10,6 +10,7 @@ public class DbContextOptions public string DbName => ConnectionString.Split('?')[0] .Split('/').Last(); public string DbOperationsCollectionName { get; set; } = "_db_ops"; + public string? Identifier { get; set; } } public class DbContextOptions : DbContextOptions diff --git a/src/MongODM.Core/IDbContext.cs b/src/MongODM.Core/IDbContext.cs index 48577522..8ba3be75 100644 --- a/src/MongODM.Core/IDbContext.cs +++ b/src/MongODM.Core/IDbContext.cs @@ -44,7 +44,12 @@ public interface IDbContext /// Container for model serialization and document schema information. /// IDocumentSchemaRegister DocumentSchemaRegister { get; } - + + /// + /// DbContext unique identifier. + /// + string Identifier { get; } + /// /// Flag reporting eventual current migration operation. /// diff --git a/src/MongODM.Core/Operations/OperationBase.cs b/src/MongODM.Core/Operations/OperationBase.cs index ceadf211..9d1e259b 100644 --- a/src/MongODM.Core/Operations/OperationBase.cs +++ b/src/MongODM.Core/Operations/OperationBase.cs @@ -8,20 +8,16 @@ namespace Etherna.MongODM.Operations public abstract class OperationBase : IEntityModel { // Constructors and dispose. - public OperationBase( - string? authorIdentifier, - IDbContext owner) + public OperationBase(IDbContext owner) { - AuthorIdentifier = authorIdentifier; CreationDateTime = DateTime.Now; - DbContextName = owner.GetType().Name; + DbContextName = owner.Identifier; } protected OperationBase() { } public void DisposeForDelete() { } // Properties. public virtual string Id { get; protected set; } = default!; - public virtual string? AuthorIdentifier { get; protected set; } public virtual DateTime CreationDateTime { get; protected set; } public virtual string DbContextName { get; protected set; } = default!; diff --git a/src/MongODM.Core/Operations/SeedOperation.cs b/src/MongODM.Core/Operations/SeedOperation.cs new file mode 100644 index 00000000..dd54cd51 --- /dev/null +++ b/src/MongODM.Core/Operations/SeedOperation.cs @@ -0,0 +1,11 @@ +namespace Etherna.MongODM.Operations +{ + public class SeedOperation : OperationBase + { + // Constructors. + public SeedOperation(IDbContext owner) + : base(owner) + { } + protected SeedOperation() { } + } +} From cfb5ea3299b3f206e5cc9051e14bc9af53224066 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sat, 11 Jul 2020 22:00:51 +0200 Subject: [PATCH 20/27] Installed Microsoft.CodeAnalysis.FxCopAnalyzers --- .editorconfig | 25 +++++++++ MongODM.sln | 1 + src/MongODM.Core/DbContext.cs | 11 ++-- .../Exceptions/InvalidEntityTypeException.cs | 3 ++ .../Extensions/ClassMapExtensions.cs | 6 +++ .../Migration/MongoCollectionMigration.cs | 5 ++ .../Migration/MongoDocumentMigration.cs | 6 ++- src/MongODM.Core/MongODM.Core.csproj | 4 ++ src/MongODM.Core/Operations/OperationBase.cs | 3 ++ .../ProxyModels/AuditableInterceptor.cs | 10 +++- .../ProxyModels/ModelInterceptorBase.cs | 6 +++ .../ProxyModels/ProxyGenerator.cs | 26 ++++++++-- .../ProxyModels/ReferenceableInterceptor.cs | 17 +++++-- src/MongODM.Core/ReflectionHelper.cs | 18 ++++++- .../Repositories/CollectionRepository.cs | 34 ++++++++----- .../Repositories/GridFSRepository.cs | 20 +++++--- .../Repositories/RepositoryBase.cs | 39 +++++++------- .../Serialization/DocumentSchemaRegister.cs | 18 +++++-- .../Serialization/SemanticVersion.cs | 19 ++++--- .../Serializers/DictionarySerializer.cs | 16 +++--- .../Serializers/EnumerableSerializer.cs | 24 ++++++--- .../Serializers/ExtendedClassMapSerializer.cs | 13 ++++- .../Serializers/ExtraElementsSerializer.cs | 10 +++- .../Serializers/GeoPointSerializer.cs | 2 +- .../Serializers/HexToBinaryDataSerializer.cs | 6 +++ .../ReadOnlyDictionarySerializer.cs | 16 +++--- .../Serializers/ReferenceSerializer.cs | 51 +++++++++++-------- .../Serializers/ReferenceSerializerSwitch.cs | 5 +- src/MongODM.Core/Tasks/Queues.cs | 2 +- .../Tasks/UpdateDocDependenciesTask.cs | 11 ++-- .../Utility/DbContextDependencies.cs | 2 + .../Utility/IStaticConfigurationBuilder.cs | 2 + 32 files changed, 318 insertions(+), 113 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..24b71126 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +# 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 + +# 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/MongODM.sln b/MongODM.sln index ae71d5ad..faea0cf0 100644 --- a/MongODM.sln +++ b/MongODM.sln @@ -21,6 +21,7 @@ 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 EndProjectSection EndProject diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index 322c78fa..2ed8ede6 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -28,6 +28,11 @@ public DbContext( IDbContextDependencies dependencies, DbContextOptions options) { + 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; @@ -102,11 +107,11 @@ public async Task MigrateRepositoriesAsync(CancellationToken cancellationToken = // Migrate collections. foreach (var migration in MigrationTaskList) - await migration.MigrateAsync(cancellationToken); + await migration.MigrateAsync(cancellationToken).ConfigureAwait(false); // Build indexes. foreach (var repository in RepositoryRegister.ModelCollectionRepositoryMap.Values) - await repository.BuildIndexesAsync(DocumentSchemaRegister, cancellationToken); + await repository.BuildIndexesAsync(DocumentSchemaRegister, cancellationToken).ConfigureAwait(false); IsMigrating = false; } @@ -150,7 +155,7 @@ public virtual async Task SaveChangesAsync(CancellationToken cancellationToken = if (RepositoryRegister.ModelCollectionRepositoryMap.ContainsKey(modelType)) //can't replace if is a file { var repository = RepositoryRegister.ModelCollectionRepositoryMap[modelType]; - await repository.ReplaceAsync(model); + await repository.ReplaceAsync(model).ConfigureAwait(false); } } } diff --git a/src/MongODM.Core/Exceptions/InvalidEntityTypeException.cs b/src/MongODM.Core/Exceptions/InvalidEntityTypeException.cs index bf1fb4d6..a8783650 100644 --- a/src/MongODM.Core/Exceptions/InvalidEntityTypeException.cs +++ b/src/MongODM.Core/Exceptions/InvalidEntityTypeException.cs @@ -9,5 +9,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 aea66a6e..1a5caaa7 100644 --- a/src/MongODM.Core/Extensions/ClassMapExtensions.cs +++ b/src/MongODM.Core/Extensions/ClassMapExtensions.cs @@ -14,6 +14,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); @@ -27,6 +30,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/Migration/MongoCollectionMigration.cs b/src/MongODM.Core/Migration/MongoCollectionMigration.cs index de10a709..00040e3d 100644 --- a/src/MongODM.Core/Migration/MongoCollectionMigration.cs +++ b/src/MongODM.Core/Migration/MongoCollectionMigration.cs @@ -29,6 +29,11 @@ public MongoCollectionMigration( Func converter, Func discriminator) { + if (sourceCollection is null) + throw new ArgumentNullException(nameof(sourceCollection)); + if (destinationCollection is null) + throw new ArgumentNullException(nameof(destinationCollection)); + this.sourceCollection = sourceCollection.Collection; this.destinationCollection = destinationCollection.Collection; this.converter = converter; diff --git a/src/MongODM.Core/Migration/MongoDocumentMigration.cs b/src/MongODM.Core/Migration/MongoDocumentMigration.cs index 3a7d9862..5c8c4731 100644 --- a/src/MongODM.Core/Migration/MongoDocumentMigration.cs +++ b/src/MongODM.Core/Migration/MongoDocumentMigration.cs @@ -3,6 +3,7 @@ using Etherna.MongODM.Serialization; using MongoDB.Bson; using MongoDB.Driver; +using System; using System.Threading; using System.Threading.Tasks; @@ -23,6 +24,9 @@ public MongoDocumentMigration( ICollectionRepository sourceCollection, SemanticVersion minimumDocumentVersion) { + if (sourceCollection is null) + throw new ArgumentNullException(nameof(sourceCollection)); + this.sourceCollection = sourceCollection.Collection; this.minimumDocumentVersion = minimumDocumentVersion; } @@ -58,7 +62,7 @@ public override async Task MigrateAsync(CancellationToken cancellationToken = de // Replace documents. await sourceCollection.Find(filter, new FindOptions { NoCursorTimeout = true }) - .ForEachAsync(obj => sourceCollection.ReplaceOneAsync(Builders.Filter.Eq(m => m.Id, obj.Id), obj), cancellationToken); + .ForEachAsync(obj => sourceCollection.ReplaceOneAsync(Builders.Filter.Eq(m => m.Id, obj.Id), obj), cancellationToken).ConfigureAwait(false); } } } diff --git a/src/MongODM.Core/MongODM.Core.csproj b/src/MongODM.Core/MongODM.Core.csproj index 37160e43..39685c1d 100644 --- a/src/MongODM.Core/MongODM.Core.csproj +++ b/src/MongODM.Core/MongODM.Core.csproj @@ -22,6 +22,10 @@ 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/Operations/OperationBase.cs b/src/MongODM.Core/Operations/OperationBase.cs index 9d1e259b..32861265 100644 --- a/src/MongODM.Core/Operations/OperationBase.cs +++ b/src/MongODM.Core/Operations/OperationBase.cs @@ -10,6 +10,9 @@ public abstract class OperationBase : IEntityModel // Constructors and dispose. public OperationBase(IDbContext owner) { + if (owner is null) + throw new ArgumentNullException(nameof(owner)); + CreationDateTime = DateTime.Now; DbContextName = owner.Identifier; } diff --git a/src/MongODM.Core/ProxyModels/AuditableInterceptor.cs b/src/MongODM.Core/ProxyModels/AuditableInterceptor.cs index eaad5579..f31ba20e 100644 --- a/src/MongODM.Core/ProxyModels/AuditableInterceptor.cs +++ b/src/MongODM.Core/ProxyModels/AuditableInterceptor.cs @@ -21,6 +21,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)) { @@ -47,10 +50,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(); @@ -58,7 +64,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/ModelInterceptorBase.cs b/src/MongODM.Core/ProxyModels/ModelInterceptorBase.cs index 5f6232fe..b711f169 100644 --- a/src/MongODM.Core/ProxyModels/ModelInterceptorBase.cs +++ b/src/MongODM.Core/ProxyModels/ModelInterceptorBase.cs @@ -16,6 +16,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); @@ -52,6 +55,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 bb9006b0..4bf59af9 100644 --- a/src/MongODM.Core/ProxyModels/ProxyGenerator.cs +++ b/src/MongODM.Core/ProxyModels/ProxyGenerator.cs @@ -7,7 +7,7 @@ namespace Etherna.MongODM.ProxyModels { - public class ProxyGenerator : IProxyGenerator + public class ProxyGenerator : IProxyGenerator, IDisposable { // Fields. private readonly Castle.DynamicProxy.IProxyGenerator proxyGeneratorCore; @@ -33,6 +33,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(); @@ -114,6 +119,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(); @@ -127,10 +138,15 @@ public bool IsProxyType(Type type) } } - public Type PurgeProxyType(Type type) => - IsProxyType(type) ? - type.BaseType : - 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) => diff --git a/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs b/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs index cd4650c5..ebce91be 100644 --- a/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs +++ b/src/MongODM.Core/ProxyModels/ReferenceableInterceptor.cs @@ -24,6 +24,9 @@ public ReferenceableInterceptor( IDbContext dbContext) : base(additionalInterfaces) { + if (dbContext is null) + throw new ArgumentNullException(nameof(dbContext)); + var repositoryModelType = typeof(TModel); while (!dbContext.RepositoryRegister.ModelRepositoryMap.ContainsKey(repositoryModelType)) { @@ -38,6 +41,9 @@ public ReferenceableInterceptor( // 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)) { @@ -77,8 +83,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); @@ -91,7 +100,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); @@ -102,7 +111,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,7 +146,7 @@ private async Task FullLoadAsync(TModel model) if (isSummary) { // Merge full object to current. - var fullModel = (await repository.TryFindOneAsync(model.Id)) as TModel; + 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 d170aacb..bb19ed95 100644 --- a/src/MongODM.Core/ReflectionHelper.cs +++ b/src/MongODM.Core/ReflectionHelper.cs @@ -14,13 +14,18 @@ public static class ReflectionHelper 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; @@ -29,7 +34,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) @@ -53,6 +58,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 @@ -84,6 +94,9 @@ 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) @@ -148,6 +161,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 1fbb575a..643e50ff 100644 --- a/src/MongODM.Core/Repositories/CollectionRepository.cs +++ b/src/MongODM.Core/Repositories/CollectionRepository.cs @@ -81,7 +81,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); @@ -92,11 +92,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( @@ -112,8 +112,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, @@ -148,7 +153,7 @@ public virtual Task ReplaceAsync( try { - return await FindOneAsync(predicate, cancellationToken); + return await FindOneAsync(predicate, cancellationToken).ConfigureAwait(false); } catch (EntityNotFoundException) { @@ -163,10 +168,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) { @@ -175,7 +185,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) { @@ -193,7 +203,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"); @@ -215,7 +225,7 @@ private async Task ReplaceHelperAsync( await Collection.ReplaceOneAsync( Builders.Filter.Eq(m => m.Id, model.Id), model, - cancellationToken: cancellationToken); + cancellationToken: cancellationToken).ConfigureAwait(false); } else { @@ -223,7 +233,7 @@ await Collection.ReplaceOneAsync( session, Builders.Filter.Eq(m => m.Id, model.Id), model, - cancellationToken: cancellationToken); + cancellationToken: cancellationToken).ConfigureAwait(false); } // Update dependent documents. diff --git a/src/MongODM.Core/Repositories/GridFSRepository.cs b/src/MongODM.Core/Repositories/GridFSRepository.cs index 57fb69fd..4d7215e3 100644 --- a/src/MongODM.Core/Repositories/GridFSRepository.cs +++ b/src/MongODM.Core/Repositories/GridFSRepository.cs @@ -42,13 +42,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) @@ -61,12 +64,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) { @@ -74,7 +82,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/RepositoryBase.cs b/src/MongODM.Core/Repositories/RepositoryBase.cs index cc6d7f25..22237fc1 100644 --- a/src/MongODM.Core/Repositories/RepositoryBase.cs +++ b/src/MongODM.Core/Repositories/RepositoryBase.cs @@ -37,24 +37,27 @@ public virtual void Initialize(IDbContext dbContext) public virtual async Task CreateAsync(IEnumerable models, CancellationToken cancellationToken = default) { - await CreateOnDBAsync(models, cancellationToken); - await DbContext.SaveChangesAsync(); + await CreateOnDBAsync(models, cancellationToken).ConfigureAwait(false); + await DbContext.SaveChangesAsync().ConfigureAwait(false); } public virtual async Task CreateAsync(TModel model, CancellationToken cancellationToken = default) { - await CreateOnDBAsync(model, cancellationToken); - await DbContext.SaveChangesAsync(); + await CreateOnDBAsync(model, cancellationToken).ConfigureAwait(false); + await DbContext.SaveChangesAsync().ConfigureAwait(false); } public async Task DeleteAsync(TKey id, CancellationToken cancellationToken = default) { - var model = await FindOneAsync(id, cancellationToken: cancellationToken); - await DeleteAsync(model, cancellationToken); + var model = await FindOneAsync(id, cancellationToken: cancellationToken).ConfigureAwait(false); + await DeleteAsync(model, cancellationToken).ConfigureAwait(false); } public virtual async Task DeleteAsync(TModel model, CancellationToken cancellationToken = default) { + if (model is null) + throw new ArgumentNullException(nameof(model)); + // Process cascade delete. var referencesIdsPaths = DbContext.DocumentSchemaRegister.GetModelEntityReferencesIds(typeof(TModel)) .Where(d => d.UseCascadeDelete == true) @@ -63,14 +66,14 @@ public virtual async Task DeleteAsync(TModel model, CancellationToken cancellati .Select(d => d.MemberPath); foreach (var idPath in referencesIdsPaths) - await CascadeDeleteMembersAsync(model, idPath); + await CascadeDeleteMembersAsync(model, idPath).ConfigureAwait(false); // Unlink dependent models. model.DisposeForDelete(); - await DbContext.SaveChangesAsync(); + await DbContext.SaveChangesAsync().ConfigureAwait(false); // Delete model. - await DeleteOnDBAsync(model, cancellationToken); + await DeleteOnDBAsync(model, cancellationToken).ConfigureAwait(false); // Remove from cache. if (DbContext.DbCache.LoadedModels.ContainsKey(model.Id!)) @@ -81,11 +84,11 @@ public async Task DeleteAsync(IEntityModel model, CancellationToken cancellation { if (!(model is TModel castedModel)) throw new InvalidEntityTypeException("Invalid model type"); - await DeleteAsync(castedModel, cancellationToken); + await DeleteAsync(castedModel, cancellationToken).ConfigureAwait(false); } public async Task FindOneAsync(object id, CancellationToken cancellationToken = default) => - await FindOneAsync((TKey)id, cancellationToken); + await FindOneAsync((TKey)id, cancellationToken).ConfigureAwait(false); public virtual async Task FindOneAsync( TKey id, @@ -98,11 +101,11 @@ public virtual async Task FindOneAsync( return cachedModel!; } - return await FindOneOnDBAsync(id, cancellationToken); + return await FindOneOnDBAsync(id, cancellationToken).ConfigureAwait(false); } public async Task TryFindOneAsync(object id, CancellationToken cancellationToken = default) => - await TryFindOneAsync((TKey)id, cancellationToken); + await TryFindOneAsync((TKey)id, cancellationToken).ConfigureAwait(false); public async Task TryFindOneAsync( TKey id, @@ -115,7 +118,7 @@ public virtual async Task FindOneAsync( try { - return await FindOneAsync(id, cancellationToken); + return await FindOneAsync(id, cancellationToken).ConfigureAwait(false); } catch (EntityNotFoundException) { @@ -145,8 +148,10 @@ private async Task CascadeDeleteMembersAsync(object currentModel, IEnumerable().ToArray()) - await CascadeDeleteMembersAsync(itemValue, memberTail); + await CascadeDeleteMembersAsync(itemValue, memberTail).ConfigureAwait(false); } else { - await CascadeDeleteMembersAsync(memberValue, memberTail); + await CascadeDeleteMembersAsync(memberValue, memberTail).ConfigureAwait(false); } } } diff --git a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs index 23b9b416..56a17800 100644 --- a/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs +++ b/src/MongODM.Core/Serialization/DocumentSchemaRegister.cs @@ -13,7 +13,7 @@ namespace Etherna.MongODM.Serialization { - public class DocumentSchemaRegister : IDocumentSchemaRegister + public class DocumentSchemaRegister : IDocumentSchemaRegister, IDisposable { // Fields. private readonly ReaderWriterLockSlim configLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); @@ -50,6 +50,11 @@ public void Initialize(IDbContext dbContext) public IEnumerable Schemas => schemas; // Methods. + public void Dispose() + { + configLock.Dispose(); + } + public void Freeze() { configLock.EnterReadLock(); @@ -92,7 +97,7 @@ public void Freeze() CompileDependencyRegisters( schema.ModelType, - new EntityMember[0], + Array.Empty(), schema.ClassMap, schema.Version); } @@ -113,7 +118,7 @@ public IEnumerable GetMemberDependencies(MemberInfo mem if (memberDependenciesMap.TryGetValue(memberInfo, out List dependencies)) return dependencies; - return new DocumentSchemaMemberMap[0]; + return Array.Empty(); } public IEnumerable GetModelDependencies(Type modelType) @@ -122,7 +127,7 @@ public IEnumerable GetModelDependencies(Type modelType) if (modelDependenciesMap.TryGetValue(modelType, out List dependencies)) return dependencies; - return new DocumentSchemaMemberMap[0]; + return Array.Empty(); } public IEnumerable GetModelEntityReferencesIds(Type modelType) @@ -131,7 +136,7 @@ public IEnumerable GetModelEntityReferencesIds(Type mod if (modelEntityReferencesIdsMap.TryGetValue(modelType, out List dependencies)) return dependencies; - return new DocumentSchemaMemberMap[0]; + return Array.Empty(); } public void RegisterModelSchema( @@ -164,6 +169,9 @@ public void RegisterModelSchema( Func>? modelMigrationAsync = null) where TModel : class { + if (classMap is null) + throw new ArgumentNullException(nameof(classMap)); + configLock.EnterWriteLock(); try { diff --git a/src/MongODM.Core/Serialization/SemanticVersion.cs b/src/MongODM.Core/Serialization/SemanticVersion.cs index 23e22f79..84625631 100644 --- a/src/MongODM.Core/Serialization/SemanticVersion.cs +++ b/src/MongODM.Core/Serialization/SemanticVersion.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Text; using System.Text.RegularExpressions; @@ -28,11 +29,11 @@ public SemanticVersion(string version) var patchGroup = match.Groups["patch"]; var labelGroup = match.Groups["label"]; - MajorRelease = int.Parse(majorGroup.Value); + MajorRelease = int.Parse(majorGroup.Value, CultureInfo.InvariantCulture); if (minorGroup.Success) - MinorRelease = int.Parse(minorGroup.Value); + MinorRelease = int.Parse(minorGroup.Value, CultureInfo.InvariantCulture); if (patchGroup.Success) - PatchRelease = int.Parse(patchGroup.Value); + PatchRelease = int.Parse(patchGroup.Value, CultureInfo.InvariantCulture); if (labelGroup.Success) LabelRelease = labelGroup.Value; } @@ -68,9 +69,9 @@ public int CompareTo(SemanticVersion? other) // If other is not a valid object reference, this instance is greater. if (other is null) return 1; - if (this > other) return 1; + if (this < other) return -1; if (this == other) return 0; - else return -1; + else return 1; } public override bool Equals(object obj) => this == (obj as SemanticVersion); @@ -101,9 +102,9 @@ public override string ToString() public static bool operator < (SemanticVersion? x, SemanticVersion? y) { // Check if null. - if (y == null) + if (y is null) return false; - else if (x == null) //y != null + else if (x is null) //y != null return true; // Check major release. @@ -136,6 +137,10 @@ public override string ToString() public static bool operator != (SemanticVersion x, SemanticVersion y) => !(x == y); + public static bool operator <= (SemanticVersion x, SemanticVersion y) => x < y || x == y; + + public static bool operator >=(SemanticVersion x, SemanticVersion y) => y <= x; + public static implicit operator SemanticVersion(string version) => new SemanticVersion(version); } } diff --git a/src/MongODM.Core/Serialization/Serializers/DictionarySerializer.cs b/src/MongODM.Core/Serialization/Serializers/DictionarySerializer.cs index 61982577..2184e177 100644 --- a/src/MongODM.Core/Serialization/Serializers/DictionarySerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/DictionarySerializer.cs @@ -1,6 +1,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Options; using MongoDB.Bson.Serialization.Serializers; +using System; using System.Collections.Generic; namespace Etherna.MongODM.Serialization.Serializers @@ -28,9 +29,11 @@ public DictionarySerializer(DictionaryRepresentation dictionaryRepresentation, I { } // Properties. + public IBsonSerializer ChildSerializer => ValueSerializer; + public IEnumerable ContainedClassMaps => ValueSerializer is IClassMapContainerSerializer classMapContainer ? - classMapContainer.ContainedClassMaps : new BsonClassMap[0]; + classMapContainer.ContainedClassMaps : Array.Empty(); public bool? UseCascadeDelete => (ValueSerializer as IReferenceContainerSerializer)?.UseCascadeDelete; @@ -38,12 +41,18 @@ ValueSerializer is IClassMapContainerSerializer classMapContainer ? // Public methods. public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, IDictionary value) { + if (value is null) + throw new ArgumentNullException(nameof(value)); + // Force to exclude enumerable actual type from serialization. args = new BsonSerializationArgs(value.GetType(), true, args.SerializeIdFirst); base.Serialize(context, args, value); } + public IBsonSerializer WithChildSerializer(IBsonSerializer childSerializer) => + WithValueSerializer((IBsonSerializer)childSerializer); + /// /// Returns a serializer that has been reconfigured with the specified dictionary representation. /// @@ -94,11 +103,6 @@ protected override ICollection> CreateAccumulator() = new Dictionary(); // Explicit interface implementations. - IBsonSerializer IChildSerializerConfigurable.ChildSerializer => ValueSerializer; - - IBsonSerializer IChildSerializerConfigurable.WithChildSerializer(IBsonSerializer childSerializer) => - WithValueSerializer((IBsonSerializer)childSerializer); - IBsonSerializer IDictionaryRepresentationConfigurable.WithDictionaryRepresentation(DictionaryRepresentation dictionaryRepresentation) => WithDictionaryRepresentation(dictionaryRepresentation); } diff --git a/src/MongODM.Core/Serialization/Serializers/EnumerableSerializer.cs b/src/MongODM.Core/Serialization/Serializers/EnumerableSerializer.cs index f108bf27..aa93237e 100644 --- a/src/MongODM.Core/Serialization/Serializers/EnumerableSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/EnumerableSerializer.cs @@ -1,5 +1,6 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; +using System; using System.Collections.Generic; namespace Etherna.MongODM.Serialization.Serializers @@ -33,9 +34,11 @@ public EnumerableSerializer(IBsonSerializerRegistry serializerRegistry) { } // Properties. + public IBsonSerializer ChildSerializer => ItemSerializer; + public IEnumerable ContainedClassMaps => ItemSerializer is IClassMapContainerSerializer classMapContainer ? - classMapContainer.ContainedClassMaps : new BsonClassMap[0]; + classMapContainer.ContainedClassMaps : Array.Empty(); public bool? UseCascadeDelete => (ItemSerializer as IReferenceContainerSerializer)?.UseCascadeDelete; @@ -43,12 +46,18 @@ ItemSerializer is IClassMapContainerSerializer classMapContainer ? // Public methods. public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, IEnumerable value) { + if (value is null) + throw new ArgumentNullException(nameof(value)); + // Force to exclude enumerable actual type from serialization. args = new BsonSerializationArgs(value.GetType(), true, args.SerializeIdFirst); base.Serialize(context, args, value); } + public IBsonSerializer WithChildSerializer(IBsonSerializer childSerializer) => + WithItemSerializer((IBsonSerializer)childSerializer); + /// /// Returns a serializer that has been reconfigured with the specified item serializer. /// @@ -60,8 +69,13 @@ public EnumerableSerializer WithItemSerializer(IBsonSerializer ite } // Protected methods. - protected override void AddItem(object accumulator, TItem item) => + protected override void AddItem(object accumulator, TItem item) + { + if (accumulator is null) + throw new ArgumentNullException(nameof(accumulator)); + ((List)accumulator).Add(item); + } protected override object CreateAccumulator() => new List(); @@ -71,11 +85,5 @@ protected override IEnumerable EnumerateItemsInSerializationOrder(IEnumer protected override IEnumerable FinalizeResult(object accumulator) => (IEnumerable)accumulator; - - // Explicit interface implementations. - IBsonSerializer IChildSerializerConfigurable.ChildSerializer => ItemSerializer; - - IBsonSerializer IChildSerializerConfigurable.WithChildSerializer(IBsonSerializer childSerializer) => - WithItemSerializer((IBsonSerializer)childSerializer); } } diff --git a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs index 3949b636..a4bc991d 100644 --- a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs @@ -41,6 +41,9 @@ public ExtendedClassMapSerializer( ISerializerModifierAccessor serializerModifierAccessor, Func>? fixDeserializedModelAsync = null) { + if (documentVersion is null) + throw new ArgumentNullException(nameof(documentVersion)); + this.dbCache = dbCache; this.serializerModifierAccessor = serializerModifierAccessor; extraElements = new List(); @@ -81,6 +84,9 @@ public ExtendedClassMapSerializer AddExtraElement( public override TModel Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { + if (context is null) + throw new ArgumentNullException(nameof(context)); + // Check if null. if (context.Reader.CurrentBsonType == BsonType.Null) { @@ -97,7 +103,7 @@ public override TModel Deserialize(BsonDeserializationContext context, BsonDeser documentVersion = BsonValueToDocumentVersion(versionElement.Value); // Initialize localContext and bsonReader - var bsonReader = new ExtendedBsonDocumentReader(bsonDocument) + using var bsonReader = new ExtendedBsonDocumentReader(bsonDocument) { DocumentVersion = documentVersion }; @@ -145,6 +151,9 @@ public bool GetDocumentId(object document, out object id, out Type idNominalType public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TModel value) { + if (context is null) + throw new ArgumentNullException(nameof(context)); + // Serialize null object. if (value == null) { @@ -154,7 +163,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati // Initialize localContext, bsonDocument and bsonWriter. var bsonDocument = new BsonDocument(); - var bsonWriter = new ExtendedBsonDocumentWriter(bsonDocument) + using var bsonWriter = new ExtendedBsonDocumentWriter(bsonDocument) { IsRootDocument = !(context.Writer is ExtendedBsonDocumentWriter) }; diff --git a/src/MongODM.Core/Serialization/Serializers/ExtraElementsSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ExtraElementsSerializer.cs index 77778436..8b96c273 100644 --- a/src/MongODM.Core/Serialization/Serializers/ExtraElementsSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ExtraElementsSerializer.cs @@ -2,6 +2,7 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; +using System; using System.Collections.Generic; namespace Etherna.MongODM.Serialization.Serializers @@ -12,6 +13,9 @@ public class ExtraElementsSerializer : SerializerBase public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) { + if (context is null) + throw new ArgumentNullException(nameof(context)); + if (value is IDictionary dictionary) { context.Writer.WriteStartDocument(); @@ -48,7 +52,8 @@ public static TValue DeserializeValue( * can't be serialized on root of documents. */ var document = new BsonDocument(); - var serializationContext = BsonSerializationContext.CreateRoot(new BsonDocumentWriter(document)); + using var documentWriter = new BsonDocumentWriter(document); + var serializationContext = BsonSerializationContext.CreateRoot(documentWriter); serializationContext.Writer.WriteStartDocument(); serializationContext.Writer.WriteName("container"); @@ -62,7 +67,8 @@ public static TValue DeserializeValue( } // Deserialize. - var deserializationContext = BsonDeserializationContext.CreateRoot(new BsonDocumentReader(document)); + using var documentReader = new BsonDocumentReader(document); + var deserializationContext = BsonDeserializationContext.CreateRoot(documentReader); deserializationContext.Reader.ReadStartDocument(); deserializationContext.Reader.ReadName(); diff --git a/src/MongODM.Core/Serialization/Serializers/GeoPointSerializer.cs b/src/MongODM.Core/Serialization/Serializers/GeoPointSerializer.cs index 0b117bb6..9921d486 100644 --- a/src/MongODM.Core/Serialization/Serializers/GeoPointSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/GeoPointSerializer.cs @@ -28,7 +28,7 @@ public GeoPointSerializer( longitudeMemberInfo = ReflectionHelper.GetMemberInfoFromLambda(longitudeMember); latitudeMemberInfo = ReflectionHelper.GetMemberInfoFromLambda(latitudeMember); pointSerializer = new GeoJsonPointSerializer(); - this.dbContext = dbContext; + this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); proxyGenerator = dbContext.ProxyGenerator; } diff --git a/src/MongODM.Core/Serialization/Serializers/HexToBinaryDataSerializer.cs b/src/MongODM.Core/Serialization/Serializers/HexToBinaryDataSerializer.cs index 908a7e28..2c72fa35 100644 --- a/src/MongODM.Core/Serialization/Serializers/HexToBinaryDataSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/HexToBinaryDataSerializer.cs @@ -9,6 +9,9 @@ public class HexToBinaryDataSerializer : SerializerBase { public override string Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { + if (context is null) + throw new ArgumentNullException(nameof(context)); + var bsonReader = context.Reader; var bsonType = bsonReader.GetCurrentBsonType(); switch (bsonType) @@ -29,6 +32,9 @@ public override string Deserialize(BsonDeserializationContext context, BsonDeser public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, string value) { + if (context is null) + throw new ArgumentNullException(nameof(context)); + if (value == null) { context.Writer.WriteNull(); diff --git a/src/MongODM.Core/Serialization/Serializers/ReadOnlyDictionarySerializer.cs b/src/MongODM.Core/Serialization/Serializers/ReadOnlyDictionarySerializer.cs index d3eff7ab..cbdd27ed 100644 --- a/src/MongODM.Core/Serialization/Serializers/ReadOnlyDictionarySerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ReadOnlyDictionarySerializer.cs @@ -1,6 +1,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Options; using MongoDB.Bson.Serialization.Serializers; +using System; using System.Collections.Generic; namespace Etherna.MongODM.Serialization.Serializers @@ -28,9 +29,11 @@ public ReadOnlyDictionarySerializer(DictionaryRepresentation dictionaryRepresent { } // Properties. + public IBsonSerializer ChildSerializer => ValueSerializer; + public IEnumerable ContainedClassMaps => ValueSerializer is IClassMapContainerSerializer classMapContainer ? - classMapContainer.ContainedClassMaps : new BsonClassMap[0]; + classMapContainer.ContainedClassMaps : Array.Empty(); public bool? UseCascadeDelete => (ValueSerializer as IReferenceContainerSerializer)?.UseCascadeDelete; @@ -38,12 +41,18 @@ ValueSerializer is IClassMapContainerSerializer classMapContainer ? // Public methods. public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, IReadOnlyDictionary value) { + if (value is null) + throw new ArgumentNullException(nameof(value)); + // Force to exclude enumerable actual type from serialization. args = new BsonSerializationArgs(value.GetType(), true, args.SerializeIdFirst); base.Serialize(context, args, value); } + public IBsonSerializer WithChildSerializer(IBsonSerializer childSerializer) => + WithValueSerializer((IBsonSerializer)childSerializer); + /// /// Returns a serializer that has been reconfigured with the specified dictionary representation. /// @@ -94,11 +103,6 @@ protected override ICollection> CreateAccumulator() = new Dictionary(); // Explicit interface implementations. - IBsonSerializer IChildSerializerConfigurable.ChildSerializer => ValueSerializer; - - IBsonSerializer IChildSerializerConfigurable.WithChildSerializer(IBsonSerializer childSerializer) => - WithValueSerializer((IBsonSerializer)childSerializer); - IBsonSerializer IDictionaryRepresentationConfigurable.WithDictionaryRepresentation(DictionaryRepresentation dictionaryRepresentation) => WithDictionaryRepresentation(dictionaryRepresentation); } diff --git a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs index b62a6d84..8b12cb15 100644 --- a/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ReferenceSerializer.cs @@ -1,7 +1,5 @@ using Etherna.MongODM.Models; using Etherna.MongODM.ProxyModels; -using Etherna.MongODM.Serialization.Modifiers; -using Etherna.MongODM.Utility; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; @@ -15,7 +13,7 @@ namespace Etherna.MongODM.Serialization.Serializers { public class ReferenceSerializer : - SerializerBase, IBsonSerializer, IBsonDocumentSerializer, IBsonIdProvider, IReferenceContainerSerializer + SerializerBase, IBsonSerializer, IBsonDocumentSerializer, IBsonIdProvider, IReferenceContainerSerializer, IDisposable where TModelBase : class, IEntityModel { // Fields. @@ -24,10 +22,7 @@ public class ReferenceSerializer : private readonly ReaderWriterLockSlim configLockAdapters = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); private readonly ReaderWriterLockSlim configLockClassMaps = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); private readonly ReaderWriterLockSlim configLockSerializers = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - private readonly IDbCache dbCache; private readonly IDbContext dbContext; - private readonly IProxyGenerator proxyGenerator; - private readonly ISerializerModifierAccessor serializerModifierAccessor; private readonly IDictionary registeredAdapters = new Dictionary(); private readonly IDictionary registeredClassMaps = new Dictionary(); @@ -38,10 +33,7 @@ public ReferenceSerializer( IDbContext dbContext, bool useCascadeDelete) { - this.dbCache = dbContext.DbCache; - this.proxyGenerator = dbContext.ProxyGenerator; - this.serializerModifierAccessor = dbContext.SerializerModifierAccessor; - this.dbContext = dbContext; + this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); UseCascadeDelete = useCascadeDelete; } @@ -61,6 +53,9 @@ public IDiscriminatorConvention DiscriminatorConvention // Methods. public override TModelBase Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { + if (context is null) + throw new ArgumentNullException(nameof(context)); + // Check bson type. var bsonReader = context.Reader; var bsonType = bsonReader.GetCurrentBsonType(); @@ -91,10 +86,10 @@ public override TModelBase Deserialize(BsonDeserializationContext context, BsonD return null!; // Check if model as been loaded in cache. - if (dbCache.LoadedModels.ContainsKey(id) && - !serializerModifierAccessor.IsNoCacheEnabled) + if (dbContext.DbCache.LoadedModels.ContainsKey(id) && + !dbContext.SerializerModifierAccessor.IsNoCacheEnabled) { - var cachedModel = (TModelBase)dbCache.LoadedModels[id]; + var cachedModel = (TModelBase)dbContext.DbCache.LoadedModels[id]; if (((IReferenceable)cachedModel).IsSummary) { @@ -125,7 +120,7 @@ public override TModelBase Deserialize(BsonDeserializationContext context, BsonD else { // Set model as summarizable. - if (serializerModifierAccessor.IsReadOnlyReferencedIdEnabled) + if (dbContext.SerializerModifierAccessor.IsReadOnlyReferencedIdEnabled) { ((IReferenceable)model).ClearSettedMembers(); ((IReferenceable)model).SetAsSummary(new[] { nameof(model.Id) }); @@ -139,14 +134,21 @@ public override TModelBase Deserialize(BsonDeserializationContext context, BsonD ((IAuditable)model).EnableAuditing(); // Add in cache. - if (!serializerModifierAccessor.IsNoCacheEnabled) - dbCache.AddModel(model.Id!, model); + if (!dbContext.SerializerModifierAccessor.IsNoCacheEnabled) + dbContext.DbCache.AddModel(model.Id!, model); } } return model!; } + public void Dispose() + { + configLockAdapters.Dispose(); + configLockClassMaps.Dispose(); + configLockSerializers.Dispose(); + } + public IBsonSerializer GetAdapter() where TModel : class, TModelBase { @@ -180,14 +182,17 @@ public IBsonSerializer GetAdapter() public bool GetDocumentId(object document, out object id, out Type idNominalType, out IIdGenerator idGenerator) { - var documentType = proxyGenerator.PurgeProxyType(document.GetType()); + if (document is null) + throw new ArgumentNullException(nameof(document)); + + var documentType = dbContext.ProxyGenerator.PurgeProxyType(document.GetType()); var serializer = (IBsonIdProvider)GetSerializer(documentType); return serializer.GetDocumentId(document, out id, out idNominalType, out idGenerator); } public ReferenceSerializer RegisterProxyType() { - var proxyType = proxyGenerator.CreateInstance(dbContext)!.GetType(); + var proxyType = dbContext.ProxyGenerator.CreateInstance(dbContext)!.GetType(); // Initialize class map. var createBsonClassMapInfo = GetType().GetMethod(nameof(CreateBsonClassMap), BindingFlags.Instance | BindingFlags.NonPublic); @@ -218,7 +223,7 @@ public ReferenceSerializer RegisterType(Action proxyGenerator.CreateInstance(dbContext)); + classMap.SetCreator(() => dbContext.ProxyGenerator.CreateInstance(dbContext)); // Add info to dictionary of registered types. configLockClassMaps.EnterWriteLock(); @@ -237,6 +242,9 @@ public ReferenceSerializer RegisterType(Action deserializer)[] caseDeserializers) { this.caseDeserializers = caseDeserializers ?? - new(Func, Func)[0]; + Array.Empty<(Func, Func)>(); this.defaultSerializer = defaultSerializer ?? throw new ArgumentNullException(nameof(defaultSerializer)); } @@ -39,6 +39,9 @@ public ReferenceSerializerSwitch( // Methods. public override TModel Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { + if (context is null) + throw new ArgumentNullException(nameof(context)); + var extendedReader = context.Reader as ExtendedBsonDocumentReader; var switchContext = new CaseContext { DocumentVersion = extendedReader?.DocumentVersion }; diff --git a/src/MongODM.Core/Tasks/Queues.cs b/src/MongODM.Core/Tasks/Queues.cs index d44fde97..f11b2525 100644 --- a/src/MongODM.Core/Tasks/Queues.cs +++ b/src/MongODM.Core/Tasks/Queues.cs @@ -1,6 +1,6 @@ namespace Etherna.MongODM.Tasks { - public class Queues + public static class Queues { public const string DB_MAINTENANCE = "db_maintenance"; } diff --git a/src/MongODM.Core/Tasks/UpdateDocDependenciesTask.cs b/src/MongODM.Core/Tasks/UpdateDocDependenciesTask.cs index 67b24a67..f0877d34 100644 --- a/src/MongODM.Core/Tasks/UpdateDocDependenciesTask.cs +++ b/src/MongODM.Core/Tasks/UpdateDocDependenciesTask.cs @@ -30,6 +30,9 @@ public async Task RunAsync( where TModel : class, IEntityModel where TDbContext : class, IDbContext { + if (idPaths is null) + throw new ArgumentNullException(nameof(idPaths)); + var dbContext = (TDbContext)serviceProvider.GetService(typeof(TDbContext)); // Get repository. @@ -45,10 +48,10 @@ public async Task RunAsync( { using var cursor = await repository.FindAsync( Builders.Filter.Eq(idPath, modelId), - new FindOptions { NoCursorTimeout = true }); + new FindOptions { NoCursorTimeout = true }).ConfigureAwait(false); // Load and replace. - while (await cursor.MoveNextAsync()) + while (await cursor.MoveNextAsync().ConfigureAwait(false)) { foreach (var model in cursor.Current) { @@ -57,9 +60,11 @@ public async Task RunAsync( try { // Replace on db. - await repository.ReplaceAsync(model, false); + await repository.ReplaceAsync(model, false).ConfigureAwait(false); } +#pragma warning disable CA1031 // Do not catch general exception types. Internal exceptions thrown by MongoDB drivers catch { } +#pragma warning restore CA1031 // Do not catch general exception types // Add id to upgraded list. upgradedDocumentsId.Add(model.Id); diff --git a/src/MongODM.Core/Utility/DbContextDependencies.cs b/src/MongODM.Core/Utility/DbContextDependencies.cs index 371cf960..03b90883 100644 --- a/src/MongODM.Core/Utility/DbContextDependencies.cs +++ b/src/MongODM.Core/Utility/DbContextDependencies.cs @@ -15,7 +15,9 @@ public DbContextDependencies( IRepositoryRegister repositoryRegister, ISerializerModifierAccessor serializerModifierAccessor, #pragma warning disable IDE0060 // Remove unused parameter. It's needed for run static configurations +#pragma warning disable CA1801 // Review unused parameters. Same of above IStaticConfigurationBuilder staticConfigurationBuilder) +#pragma warning restore CA1801 // Review unused parameters #pragma warning restore IDE0060 // Remove unused parameter { DbCache = dbCache; diff --git a/src/MongODM.Core/Utility/IStaticConfigurationBuilder.cs b/src/MongODM.Core/Utility/IStaticConfigurationBuilder.cs index 92e55ab4..b7ef7da6 100644 --- a/src/MongODM.Core/Utility/IStaticConfigurationBuilder.cs +++ b/src/MongODM.Core/Utility/IStaticConfigurationBuilder.cs @@ -6,7 +6,9 @@ /// For a proper use, implements it in a class where configuration is invoked by constructor. /// So configure it as a singleton on IoC system, and injectit as a dependency for DbContext. /// +#pragma warning disable CA1040 // Avoid empty interfaces public interface IStaticConfigurationBuilder { } +#pragma warning restore CA1040 // Avoid empty interfaces } \ No newline at end of file From bdee0a5863a206b8cd22017544e72eb72dfa4f5d Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 12 Jul 2020 02:29:20 +0200 Subject: [PATCH 21/27] Started implementation of background migration tasks --- src/MongODM.Core/DbContext.cs | 18 +---- src/MongODM.Core/IDbContext.cs | 26 +++---- .../Migration/IMigrationManager.cs | 22 ++++++ .../Migration/MigrationManager.cs | 68 +++++++++++++++++++ .../Operations/MigrateOperation.cs | 65 ++++++++++++++++++ src/MongODM.Core/Tasks/IMigrateDbTask.cs | 10 +++ src/MongODM.Core/Tasks/MigrateDbTask.cs | 33 +++++++++ 7 files changed, 213 insertions(+), 29 deletions(-) create mode 100644 src/MongODM.Core/Migration/IMigrationManager.cs create mode 100644 src/MongODM.Core/Migration/MigrationManager.cs create mode 100644 src/MongODM.Core/Operations/MigrateOperation.cs create mode 100644 src/MongODM.Core/Tasks/IMigrateDbTask.cs create mode 100644 src/MongODM.Core/Tasks/MigrateDbTask.cs diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index 2ed8ede6..905d8969 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -90,32 +90,16 @@ public DbContext( public ICollectionRepository DbOperations { get; } public IDocumentSchemaRegister DocumentSchemaRegister { get; } public string Identifier { get; } - public bool IsMigrating { get; private set; } public SemanticVersion LibraryVersion { get; } + public virtual IEnumerable MigrationTaskList { get; } = Array.Empty(); public IProxyGenerator ProxyGenerator { get; } public IRepositoryRegister RepositoryRegister { get; } public ISerializerModifierAccessor SerializerModifierAccessor { get; } // Protected properties. - protected virtual IEnumerable MigrationTaskList { get; } = Array.Empty(); 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).ConfigureAwait(false); - - // Build indexes. - foreach (var repository in RepositoryRegister.ModelCollectionRepositoryMap.Values) - await repository.BuildIndexesAsync(DocumentSchemaRegister, cancellationToken).ConfigureAwait(false); - - IsMigrating = false; - } - public virtual async Task SaveChangesAsync(CancellationToken cancellationToken = default) { /* diff --git a/src/MongODM.Core/IDbContext.cs b/src/MongODM.Core/IDbContext.cs index 8ba3be75..66e4e585 100644 --- a/src/MongODM.Core/IDbContext.cs +++ b/src/MongODM.Core/IDbContext.cs @@ -1,9 +1,12 @@ -using Etherna.MongODM.ProxyModels; +using Etherna.MongODM.Migration; +using Etherna.MongODM.Operations; +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; @@ -39,7 +42,12 @@ public interface IDbContext /// Database operator interested into maintenance tasks. /// IDbMaintainer DbMaintainer { get; } - + + /// + /// Internal collection for keep db operations execution log + /// + ICollectionRepository DbOperations { get; } + /// /// Container for model serialization and document schema information. /// @@ -51,14 +59,14 @@ public interface IDbContext string Identifier { get; } /// - /// Flag reporting eventual current migration operation. + /// Current MongODM library version /// - bool IsMigrating { get; } + SemanticVersion LibraryVersion { get; } /// - /// Current MongODM library version + /// List of registered migration tasks /// - SemanticVersion LibraryVersion { get; } + IEnumerable MigrationTaskList { get; } /// /// Current model proxy generator. @@ -76,12 +84,6 @@ public interface IDbContext ISerializerModifierAccessor SerializerModifierAccessor { get; } // Methods. - /// - /// Start a database migration process. - /// - /// Cancellation token - Task MigrateRepositoriesAsync(CancellationToken cancellationToken = default); - /// /// Save current model changes on db. /// diff --git a/src/MongODM.Core/Migration/IMigrationManager.cs b/src/MongODM.Core/Migration/IMigrationManager.cs new file mode 100644 index 00000000..7a3f2c2f --- /dev/null +++ b/src/MongODM.Core/Migration/IMigrationManager.cs @@ -0,0 +1,22 @@ +using Etherna.MongODM.Operations; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Etherna.MongODM.Migration +{ + internal interface IMigrationManager + { + Task IsMigrationRunningAsync(); + + Task> GetLastMigrationsAsync(int page, int take); + + Task GetMigrationAsync(string migrateOperationId); + + /// + /// Start a database migration process. + /// + /// Cancellation token + Task MigrateRepositoriesAsync(CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/MongODM.Core/Migration/MigrationManager.cs b/src/MongODM.Core/Migration/MigrationManager.cs new file mode 100644 index 00000000..7d4b6ece --- /dev/null +++ b/src/MongODM.Core/Migration/MigrationManager.cs @@ -0,0 +1,68 @@ +using Etherna.MongODM.Extensions; +using Etherna.MongODM.Operations; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Etherna.MongODM.Migration +{ + public class MigrationManager : IMigrationManager, IDbContextInitializable + { + // Fields. + private IDbContext dbContext = default!; + + // Constructor and initialization. + public void Initialize(IDbContext dbContext) + { + if (IsInitialized) + throw new InvalidOperationException("Instance already initialized"); + + this.dbContext = dbContext; + + IsInitialized = true; + } + + // Properties. + public bool IsInitialized { get; private set; } + + // Methods. + public async Task> GetLastMigrationsAsync(int page, int take) => + await dbContext.DbOperations.QueryElementsAsync(elements => + elements.OfType() + .PaginateDescending(r => r.CreationDateTime, page, take) + .ToListAsync()).ConfigureAwait(false); + + public async Task GetMigrationAsync(string migrateOperationId) + { + if (migrateOperationId is null) + throw new ArgumentNullException(nameof(migrateOperationId)); + + var migrateOp = await dbContext.DbOperations.QueryElementsAsync(elements => + elements.OfType() + .Where(op => op.Id == migrateOperationId) + .FirstAsync()).ConfigureAwait(false); + + return migrateOp; + } + + public async Task IsMigrationRunningAsync() + { + var migrateOp = await dbContext.DbOperations.QueryElementsAsync(elements => + elements.OfType() + .Where(op => op.DbContextName == dbContext.Identifier) + .Where(op => op.CurrentStatus == MigrateOperation.Status.Running) + .FirstOrDefaultAsync()).ConfigureAwait(false); + + return migrateOp; + } + + public Task MigrateRepositoriesAsync(CancellationToken cancellationToken = default) + { + // Run task. + throw new NotImplementedException(); + } + } +} diff --git a/src/MongODM.Core/Operations/MigrateOperation.cs b/src/MongODM.Core/Operations/MigrateOperation.cs new file mode 100644 index 00000000..1e3d0cce --- /dev/null +++ b/src/MongODM.Core/Operations/MigrateOperation.cs @@ -0,0 +1,65 @@ +using Etherna.MongODM.Attributes; +using System; + +namespace Etherna.MongODM.Operations +{ + public class MigrateOperation : OperationBase + { + // Enums. + public enum Status + { + New, + Running, + Completed, + Cancelled + } + + // Constructors. + public MigrateOperation(IDbContext owner, string? author) + : base(owner) + { + Author = author; + } + + // Properties. + public virtual string? Author { get; protected set; } + public virtual DateTime CompletedDateTime { get; protected set; } + public virtual Status CurrentStatus { get; protected set; } + public virtual string? TaskId { get; protected set; } + + // Methods. + [PropertyAlterer(nameof(CurrentStatus))] + public void TaskCancelled() + { + if (CurrentStatus == Status.Completed) + throw new InvalidOperationException(); + + CurrentStatus = Status.Cancelled; + } + + [PropertyAlterer(nameof(CompletedDateTime))] + [PropertyAlterer(nameof(CurrentStatus))] + public void TaskCompleted() + { + if (CurrentStatus != Status.Running) + throw new InvalidOperationException(); + + CompletedDateTime = DateTime.Now; + CurrentStatus = Status.Completed; + } + + [PropertyAlterer(nameof(CurrentStatus))] + [PropertyAlterer(nameof(TaskId))] + public 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/Tasks/IMigrateDbTask.cs b/src/MongODM.Core/Tasks/IMigrateDbTask.cs new file mode 100644 index 00000000..2a4afa21 --- /dev/null +++ b/src/MongODM.Core/Tasks/IMigrateDbTask.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Etherna.MongODM.Tasks +{ + public interface IMigrateDbTask + { + Task RunAsync() + where TDbContext : class, IDbContext; + } +} \ No newline at end of file diff --git a/src/MongODM.Core/Tasks/MigrateDbTask.cs b/src/MongODM.Core/Tasks/MigrateDbTask.cs new file mode 100644 index 00000000..ba472c65 --- /dev/null +++ b/src/MongODM.Core/Tasks/MigrateDbTask.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; + +namespace Etherna.MongODM.Tasks +{ + public class MigrateDbTask : IMigrateDbTask + { + // Fields. + private readonly IServiceProvider serviceProvider; + + // Constructors. + public MigrateDbTask( + IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + // Methods. + public async Task RunAsync() + where TDbContext : class, IDbContext + { + var dbContext = (TDbContext)serviceProvider.GetService(typeof(TDbContext)); + + // Migrate collections. + foreach (var migration in dbContext.MigrationTaskList) + await migration.MigrateAsync(cancellationToken).ConfigureAwait(false); + + // Build indexes. + foreach (var repository in dbContext.RepositoryRegister.ModelCollectionRepositoryMap.Values) + await repository.BuildIndexesAsync(dbContext.DocumentSchemaRegister, cancellationToken).ConfigureAwait(false); + } + } +} From 3a9a56028a77e0dbfb931a14590737b147b72da7 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Mon, 13 Jul 2020 02:01:37 +0200 Subject: [PATCH 22/27] Updating migration task --- .editorconfig | 3 ++ .../ServiceCollectionExtensions.cs | 5 +-- src/MongODM.Core/DbContext.cs | 10 +++--- src/MongODM.Core/IDbContext.cs | 9 ++++-- .../Operations/MigrateOperation.cs | 5 +-- src/MongODM.Core/Operations/OperationBase.cs | 8 ++--- .../Serializers/ExtendedClassMapSerializer.cs | 4 +-- ...rateDbTask.cs => IMigrateDbContextTask.cs} | 4 +-- src/MongODM.Core/Tasks/ITaskRunner.cs | 1 + ...grateDbTask.cs => MigrateDbContextTask.cs} | 14 ++++++--- .../Utility/{DBCache.cs => DbContextCache.cs} | 4 +-- .../Utility/DbContextDependencies.cs | 15 +++++---- ...DBMaintainer.cs => DbContextMaintainer.cs} | 4 +-- .../DbContextMigrationManager.cs} | 21 ++++++++----- .../{IDBCache.cs => IDbContextCache.cs} | 4 +-- .../Utility/IDbContextDependencies.cs | 5 +-- ...BMaintainer.cs => IDbContextMaintainer.cs} | 4 +-- .../IDbContextMigrationManager.cs} | 11 +++---- .../Tasks/HangfireTaskRunner.cs | 4 +++ .../Tasks/MigrateDbContextTaskFacade.cs | 31 +++++++++++++++++++ .../Tasks/UpdateDocDependenciesTaskFacade.cs | 2 -- .../ExtendedClassMapSerializerTest.cs | 4 +-- 22 files changed, 117 insertions(+), 55 deletions(-) rename src/MongODM.Core/Tasks/{IMigrateDbTask.cs => IMigrateDbContextTask.cs} (58%) rename src/MongODM.Core/Tasks/{MigrateDbTask.cs => MigrateDbContextTask.cs} (65%) rename src/MongODM.Core/Utility/{DBCache.cs => DbContextCache.cs} (95%) rename src/MongODM.Core/Utility/{DBMaintainer.cs => DbContextMaintainer.cs} (94%) rename src/MongODM.Core/{Migration/MigrationManager.cs => Utility/DbContextMigrationManager.cs} (76%) rename src/MongODM.Core/Utility/{IDBCache.cs => IDbContextCache.cs} (90%) rename src/MongODM.Core/Utility/{IDBMaintainer.cs => IDbContextMaintainer.cs} (78%) rename src/MongODM.Core/{Migration/IMigrationManager.cs => Utility/IDbContextMigrationManager.cs} (55%) create mode 100644 src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs diff --git a/.editorconfig b/.editorconfig index 24b71126..c545a099 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,6 +18,9 @@ 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 diff --git a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs index dae9c683..f0961743 100644 --- a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs @@ -60,9 +60,10 @@ public static MongODMConfiguration UseMongODM(); + services.TryAddTransient(); services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddSingleton(); diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index 905d8969..fa838c80 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -34,8 +34,9 @@ public DbContext( throw new ArgumentNullException(nameof(options)); ApplicationVersion = options.ApplicationVersion; - DbCache = dependencies.DbCache; - DbMaintainer = dependencies.DbMaintainer; + DbCache = dependencies.DbContextCache; + DbMaintainer = dependencies.DbContextMaintainer; + DbContextMigrationManager = dependencies.DbContextMigrationManager; DbOperations = new CollectionRepository(options.DbOperationsCollectionName); DocumentSchemaRegister = dependencies.DocumentSchemaRegister; Identifier = options.Identifier ?? GetType().Name; @@ -85,8 +86,9 @@ public DbContext( .ToList(); public IMongoClient Client { get; } public IMongoDatabase Database { get; } - public IDbCache DbCache { get; } - public IDbMaintainer DbMaintainer { get; } + public IDbContextCache DbCache { get; } + public IDbContextMaintainer DbMaintainer { get; } + public IDbContextMigrationManager DbContextMigrationManager { get; } public ICollectionRepository DbOperations { get; } public IDocumentSchemaRegister DocumentSchemaRegister { get; } public string Identifier { get; } diff --git a/src/MongODM.Core/IDbContext.cs b/src/MongODM.Core/IDbContext.cs index 66e4e585..76caad34 100644 --- a/src/MongODM.Core/IDbContext.cs +++ b/src/MongODM.Core/IDbContext.cs @@ -36,12 +36,17 @@ public interface IDbContext /// /// Database cache container. /// - IDbCache DbCache { get; } + IDbContextCache DbCache { get; } /// /// Database operator interested into maintenance tasks. /// - IDbMaintainer DbMaintainer { get; } + IDbContextMaintainer DbMaintainer { get; } + + /// + /// Manage migrations over database context + /// + IDbContextMigrationManager DbContextMigrationManager { get; } /// /// Internal collection for keep db operations execution log diff --git a/src/MongODM.Core/Operations/MigrateOperation.cs b/src/MongODM.Core/Operations/MigrateOperation.cs index 1e3d0cce..194a5723 100644 --- a/src/MongODM.Core/Operations/MigrateOperation.cs +++ b/src/MongODM.Core/Operations/MigrateOperation.cs @@ -15,10 +15,11 @@ public enum Status } // Constructors. - public MigrateOperation(IDbContext owner, string? author) - : base(owner) + public MigrateOperation(IDbContext dbContext, string? author) + : base(dbContext) { Author = author; + CurrentStatus = Status.New; } // Properties. diff --git a/src/MongODM.Core/Operations/OperationBase.cs b/src/MongODM.Core/Operations/OperationBase.cs index 32861265..e8661da6 100644 --- a/src/MongODM.Core/Operations/OperationBase.cs +++ b/src/MongODM.Core/Operations/OperationBase.cs @@ -8,13 +8,13 @@ namespace Etherna.MongODM.Operations public abstract class OperationBase : IEntityModel { // Constructors and dispose. - public OperationBase(IDbContext owner) + public OperationBase(IDbContext dbContext) { - if (owner is null) - throw new ArgumentNullException(nameof(owner)); + if (dbContext is null) + throw new ArgumentNullException(nameof(dbContext)); CreationDateTime = DateTime.Now; - DbContextName = owner.Identifier; + DbContextName = dbContext.Identifier; } protected OperationBase() { } public void DisposeForDelete() { } diff --git a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs index a4bc991d..cd201b0e 100644 --- a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs @@ -28,7 +28,7 @@ private struct ExtraElementCondition private readonly BsonElement documentVersionElement; // Fields. - private readonly IDbCache dbCache; + private readonly IDbContextCache dbCache; private readonly ISerializerModifierAccessor serializerModifierAccessor; private readonly ICollection extraElements; private readonly Func> fixDeserializedModelAsync; @@ -36,7 +36,7 @@ private struct ExtraElementCondition // Constructor. public ExtendedClassMapSerializer( - IDbCache dbCache, + IDbContextCache dbCache, SemanticVersion documentVersion, ISerializerModifierAccessor serializerModifierAccessor, Func>? fixDeserializedModelAsync = null) diff --git a/src/MongODM.Core/Tasks/IMigrateDbTask.cs b/src/MongODM.Core/Tasks/IMigrateDbContextTask.cs similarity index 58% rename from src/MongODM.Core/Tasks/IMigrateDbTask.cs rename to src/MongODM.Core/Tasks/IMigrateDbContextTask.cs index 2a4afa21..b7b34b64 100644 --- a/src/MongODM.Core/Tasks/IMigrateDbTask.cs +++ b/src/MongODM.Core/Tasks/IMigrateDbContextTask.cs @@ -2,9 +2,9 @@ namespace Etherna.MongODM.Tasks { - public interface IMigrateDbTask + public interface IMigrateDbContextTask { - Task RunAsync() + Task RunAsync(string authorId) where TDbContext : class, IDbContext; } } \ No newline at end of file diff --git a/src/MongODM.Core/Tasks/ITaskRunner.cs b/src/MongODM.Core/Tasks/ITaskRunner.cs index 51af70d0..32415ea5 100644 --- a/src/MongODM.Core/Tasks/ITaskRunner.cs +++ b/src/MongODM.Core/Tasks/ITaskRunner.cs @@ -5,6 +5,7 @@ namespace Etherna.MongODM.Tasks { public interface ITaskRunner { + void RunMigrateDbContextTask(Type dbContextType, string migrateOpId); void RunUpdateDocDependenciesTask(Type dbContextType, Type modelType, Type keyType, IEnumerable idPaths, object modelId); } } diff --git a/src/MongODM.Core/Tasks/MigrateDbTask.cs b/src/MongODM.Core/Tasks/MigrateDbContextTask.cs similarity index 65% rename from src/MongODM.Core/Tasks/MigrateDbTask.cs rename to src/MongODM.Core/Tasks/MigrateDbContextTask.cs index ba472c65..2739662b 100644 --- a/src/MongODM.Core/Tasks/MigrateDbTask.cs +++ b/src/MongODM.Core/Tasks/MigrateDbContextTask.cs @@ -1,25 +1,31 @@ -using System; +using Etherna.MongODM.Operations; +using System; using System.Threading.Tasks; namespace Etherna.MongODM.Tasks { - public class MigrateDbTask : IMigrateDbTask + public class MigrateDbContextTask : IMigrateDbContextTask { // Fields. private readonly IServiceProvider serviceProvider; // Constructors. - public MigrateDbTask( + public MigrateDbContextTask( IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } // Methods. - public async Task RunAsync() + public async Task RunAsync(string migrateOpId) where TDbContext : class, IDbContext { var dbContext = (TDbContext)serviceProvider.GetService(typeof(TDbContext)); + var migrateOp = (MigrateOperation)await dbContext.DbOperations.FindOneAsync(migrateOpId).ConfigureAwait(false); + + // Start migrate operation. + migrateOp.TaskStarted(); + await dbContext.SaveChangesAsync().ConfigureAwait(false); // Migrate collections. foreach (var migration in dbContext.MigrationTaskList) diff --git a/src/MongODM.Core/Utility/DBCache.cs b/src/MongODM.Core/Utility/DbContextCache.cs similarity index 95% rename from src/MongODM.Core/Utility/DBCache.cs rename to src/MongODM.Core/Utility/DbContextCache.cs index 6eaa5b7d..20c7065b 100644 --- a/src/MongODM.Core/Utility/DBCache.cs +++ b/src/MongODM.Core/Utility/DbContextCache.cs @@ -5,7 +5,7 @@ namespace Etherna.MongODM.Utility { - public class DbCache : IDbCache + public class DbContextCache : IDbContextCache { // Consts. private const string CacheKey = "DBCache"; @@ -14,7 +14,7 @@ public class DbCache : IDbCache private readonly IExecutionContext executionContext; // Constructors. - public DbCache(IExecutionContext executionContext) + public DbContextCache(IExecutionContext executionContext) { this.executionContext = executionContext ?? throw new ArgumentNullException(nameof(executionContext)); } diff --git a/src/MongODM.Core/Utility/DbContextDependencies.cs b/src/MongODM.Core/Utility/DbContextDependencies.cs index 03b90883..c108da6d 100644 --- a/src/MongODM.Core/Utility/DbContextDependencies.cs +++ b/src/MongODM.Core/Utility/DbContextDependencies.cs @@ -8,8 +8,9 @@ namespace Etherna.MongODM.Utility public class DbContextDependencies : IDbContextDependencies { public DbContextDependencies( - IDbCache dbCache, - IDbMaintainer dbMaintainer, + IDbContextCache dbCache, + IDbContextMaintainer dbMaintainer, + IDbContextMigrationManager dbContextMigrationManager, IDocumentSchemaRegister documentSchemaRegister, IProxyGenerator proxyGenerator, IRepositoryRegister repositoryRegister, @@ -20,16 +21,18 @@ public DbContextDependencies( #pragma warning restore CA1801 // Review unused parameters #pragma warning restore IDE0060 // Remove unused parameter { - DbCache = dbCache; - DbMaintainer = dbMaintainer; + DbContextCache = dbCache; + DbContextMaintainer = dbMaintainer; + DbContextMigrationManager = dbContextMigrationManager; DocumentSchemaRegister = documentSchemaRegister; ProxyGenerator = proxyGenerator; RepositoryRegister = repositoryRegister; SerializerModifierAccessor = serializerModifierAccessor; } - public IDbCache DbCache { get; } - public IDbMaintainer DbMaintainer { get; } + public IDbContextCache DbContextCache { get; } + public IDbContextMaintainer DbContextMaintainer { get; } + public IDbContextMigrationManager DbContextMigrationManager { get; } public IDocumentSchemaRegister DocumentSchemaRegister { get; } public IProxyGenerator ProxyGenerator { get; } public IRepositoryRegister RepositoryRegister { get; } diff --git a/src/MongODM.Core/Utility/DBMaintainer.cs b/src/MongODM.Core/Utility/DbContextMaintainer.cs similarity index 94% rename from src/MongODM.Core/Utility/DBMaintainer.cs rename to src/MongODM.Core/Utility/DbContextMaintainer.cs index e871015a..9e838a08 100644 --- a/src/MongODM.Core/Utility/DBMaintainer.cs +++ b/src/MongODM.Core/Utility/DbContextMaintainer.cs @@ -6,14 +6,14 @@ namespace Etherna.MongODM.Utility { - public class DbMaintainer : IDbMaintainer + public class DbContextMaintainer : IDbContextMaintainer { // Fields. private IDbContext dbContext = default!; private readonly ITaskRunner taskRunner; // Constructors and initialization. - public DbMaintainer(ITaskRunner taskRunner) + public DbContextMaintainer(ITaskRunner taskRunner) { this.taskRunner = taskRunner; } diff --git a/src/MongODM.Core/Migration/MigrationManager.cs b/src/MongODM.Core/Utility/DbContextMigrationManager.cs similarity index 76% rename from src/MongODM.Core/Migration/MigrationManager.cs rename to src/MongODM.Core/Utility/DbContextMigrationManager.cs index 7d4b6ece..dd9afe1d 100644 --- a/src/MongODM.Core/Migration/MigrationManager.cs +++ b/src/MongODM.Core/Utility/DbContextMigrationManager.cs @@ -1,20 +1,25 @@ using Etherna.MongODM.Extensions; using Etherna.MongODM.Operations; +using Etherna.MongODM.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; -namespace Etherna.MongODM.Migration +namespace Etherna.MongODM.Utility { - public class MigrationManager : IMigrationManager, IDbContextInitializable + public class DbContextMigrationManager : IDbContextMigrationManager, IDbContextInitializable { // Fields. private IDbContext dbContext = default!; - + private readonly ITaskRunner taskRunner; + // Constructor and initialization. + public DbContextMigrationManager(ITaskRunner taskRunner) + { + this.taskRunner = taskRunner; + } public void Initialize(IDbContext dbContext) { if (IsInitialized) @@ -59,10 +64,12 @@ public async Task GetMigrationAsync(string migrateOperationId) return migrateOp; } - public Task MigrateRepositoriesAsync(CancellationToken cancellationToken = default) + public async Task StartDbContextMigrationAsync(string authorId) { - // Run task. - throw new NotImplementedException(); + var migrateOp = new MigrateOperation(dbContext, authorId); + await dbContext.DbOperations.CreateAsync(migrateOp).ConfigureAwait(false); + + taskRunner.RunMigrateDbContextTask(dbContext.GetType(), migrateOp.Id); } } } diff --git a/src/MongODM.Core/Utility/IDBCache.cs b/src/MongODM.Core/Utility/IDbContextCache.cs similarity index 90% rename from src/MongODM.Core/Utility/IDBCache.cs rename to src/MongODM.Core/Utility/IDbContextCache.cs index 7979c869..a1832174 100644 --- a/src/MongODM.Core/Utility/IDBCache.cs +++ b/src/MongODM.Core/Utility/IDbContextCache.cs @@ -4,9 +4,9 @@ namespace Etherna.MongODM.Utility { /// - /// Interface for implementation. + /// Interface for implementation. /// - public interface IDbCache + public interface IDbContextCache { // Properties. /// diff --git a/src/MongODM.Core/Utility/IDbContextDependencies.cs b/src/MongODM.Core/Utility/IDbContextDependencies.cs index bc8d335b..f362e8ad 100644 --- a/src/MongODM.Core/Utility/IDbContextDependencies.cs +++ b/src/MongODM.Core/Utility/IDbContextDependencies.cs @@ -7,8 +7,9 @@ namespace Etherna.MongODM.Utility { public interface IDbContextDependencies { - IDbCache DbCache { get; } - IDbMaintainer DbMaintainer { get; } + IDbContextCache DbContextCache { get; } + IDbContextMaintainer DbContextMaintainer { get; } + IDbContextMigrationManager DbContextMigrationManager { get; } IDocumentSchemaRegister DocumentSchemaRegister { get; } IProxyGenerator ProxyGenerator { get; } IRepositoryRegister RepositoryRegister { get; } diff --git a/src/MongODM.Core/Utility/IDBMaintainer.cs b/src/MongODM.Core/Utility/IDbContextMaintainer.cs similarity index 78% rename from src/MongODM.Core/Utility/IDBMaintainer.cs rename to src/MongODM.Core/Utility/IDbContextMaintainer.cs index db9c9da3..770e6449 100644 --- a/src/MongODM.Core/Utility/IDBMaintainer.cs +++ b/src/MongODM.Core/Utility/IDbContextMaintainer.cs @@ -3,9 +3,9 @@ namespace Etherna.MongODM.Utility { /// - /// Interface for implementation. + /// Interface for implementation. /// - public interface IDbMaintainer : IDbContextInitializable + public interface IDbContextMaintainer : IDbContextInitializable { // Methods. /// diff --git a/src/MongODM.Core/Migration/IMigrationManager.cs b/src/MongODM.Core/Utility/IDbContextMigrationManager.cs similarity index 55% rename from src/MongODM.Core/Migration/IMigrationManager.cs rename to src/MongODM.Core/Utility/IDbContextMigrationManager.cs index 7a3f2c2f..a9298212 100644 --- a/src/MongODM.Core/Migration/IMigrationManager.cs +++ b/src/MongODM.Core/Utility/IDbContextMigrationManager.cs @@ -1,11 +1,10 @@ using Etherna.MongODM.Operations; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; -namespace Etherna.MongODM.Migration +namespace Etherna.MongODM.Utility { - internal interface IMigrationManager + public interface IDbContextMigrationManager { Task IsMigrationRunningAsync(); @@ -14,9 +13,9 @@ internal interface IMigrationManager Task GetMigrationAsync(string migrateOperationId); /// - /// Start a database migration process. + /// Start a db context migration process. /// - /// Cancellation token - Task MigrateRepositoriesAsync(CancellationToken cancellationToken = default); + /// Id of user requiring the migration + Task StartDbContextMigrationAsync(string authorId); } } \ No newline at end of file diff --git a/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs b/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs index d86d0da9..1b069a0d 100644 --- a/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs +++ b/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs @@ -24,6 +24,10 @@ public HangfireTaskRunner( } // Methods. + public void RunMigrateDbContextTask(Type dbContextType, string migrateOpId) => + backgroundJobClient.Enqueue( + task => task.RunAsync(dbContextType, migrateOpId)); + public void RunUpdateDocDependenciesTask(Type dbContextType, Type modelType, Type keyType, IEnumerable idPaths, object modelId) => backgroundJobClient.Enqueue( task => task.RunAsync(dbContextType, modelType, keyType, idPaths, modelId)); diff --git a/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs b/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs new file mode 100644 index 00000000..4b304b0e --- /dev/null +++ b/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs @@ -0,0 +1,31 @@ +using Etherna.MongODM.Tasks; +using Hangfire; +using System; +using System.Reflection; +using System.Threading.Tasks; + +namespace Etherna.MongODM.HF.Tasks +{ + class MigrateDbContextTaskFacade + { + // Fields. + private readonly IMigrateDbContextTask task; + + // Constructors. + public MigrateDbContextTaskFacade(IMigrateDbContextTask task) + { + this.task = task; + } + + // Methods. + [Queue(Queues.DB_MAINTENANCE)] + public Task RunAsync(Type dbContextType, string migrateOpId) + { + var method = typeof(MigrateDbContextTask).GetMethod( + nameof(MigrateDbContextTask.RunAsync), BindingFlags.Public | BindingFlags.Instance) + .MakeGenericMethod(dbContextType); + + return (Task)method.Invoke(task, new object[] { migrateOpId }); + } + } +} diff --git a/src/MongODM.Hangfire/Tasks/UpdateDocDependenciesTaskFacade.cs b/src/MongODM.Hangfire/Tasks/UpdateDocDependenciesTaskFacade.cs index 16644c88..95139ac1 100644 --- a/src/MongODM.Hangfire/Tasks/UpdateDocDependenciesTaskFacade.cs +++ b/src/MongODM.Hangfire/Tasks/UpdateDocDependenciesTaskFacade.cs @@ -2,13 +2,11 @@ using Hangfire; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Threading.Tasks; namespace Etherna.MongODM.HF.Tasks { - [SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "It is instantiated by Hangfire")] class UpdateDocDependenciesTaskFacade { // Fields. diff --git a/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs b/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs index 5e8ab713..7230c977 100644 --- a/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs +++ b/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs @@ -65,14 +65,14 @@ public SerializationTestElement( } // Fields. - private readonly Mock dbCacheMock; + private readonly Mock dbCacheMock; private readonly SemanticVersion documentVersion = new SemanticVersion("1.0.0"); private readonly Mock serializerModifierAccessorMock; // Constructor. public ExtendedClassMapSerializerTest() { - dbCacheMock = new Mock(); + dbCacheMock = new Mock(); dbCacheMock.Setup(c => c.LoadedModels.ContainsKey(It.IsAny())) .Returns(() => false); From b20cb598cc48c7a2ca08e6b484c787cd0c0265f3 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Mon, 13 Jul 2020 19:30:43 +0200 Subject: [PATCH 23/27] Updating background migration --- .../StaticConfigurationBuilder.cs | 4 +- src/MongODM.Core/DbContext.cs | 4 +- src/MongODM.Core/IDbContext.cs | 2 +- src/MongODM.Core/Migration/MigrationResult.cs | 26 +++++++++++ .../Migration/MongoCollectionMigration.cs | 12 ++++- .../Migration/MongoDocumentMigration.cs | 15 ++++-- .../Migration/MongoMigrationBase.cs | 21 ++++++++- .../Models/Internal/EntityModelBase.cs | 46 +++++++++++++++++++ .../MigrateOpAgg/MigrateExecutionLog.cs | 38 +++++++++++++++ .../Internal}/MigrateOperation.cs | 28 +++++++++-- src/MongODM.Core/Models/Internal/ModelBase.cs | 11 +++++ .../ModelMaps/MigrateExecutionLogMap.cs | 14 ++++++ .../Internal}/ModelMaps/OperationBaseMap.cs | 2 +- .../Models/Internal/OperationBase.cs | 21 +++++++++ .../Internal}/SeedOperation.cs | 2 +- src/MongODM.Core/Operations/OperationBase.cs | 30 ------------ .../Tasks/IMigrateDbContextTask.cs | 2 +- .../Tasks/MigrateDbContextTask.cs | 35 ++++++++++++-- .../Utility/DbContextMigrationManager.cs | 2 +- .../Utility/IDbContextMigrationManager.cs | 2 +- .../Tasks/HangfireTaskRunner.cs | 2 +- .../Tasks/MigrateDbContextTaskFacade.cs | 5 +- 22 files changed, 264 insertions(+), 60 deletions(-) create mode 100644 src/MongODM.Core/Migration/MigrationResult.cs create mode 100644 src/MongODM.Core/Models/Internal/EntityModelBase.cs create mode 100644 src/MongODM.Core/Models/Internal/MigrateOpAgg/MigrateExecutionLog.cs rename src/MongODM.Core/{Operations => Models/Internal}/MigrateOperation.cs (66%) create mode 100644 src/MongODM.Core/Models/Internal/ModelBase.cs create mode 100644 src/MongODM.Core/Models/Internal/ModelMaps/MigrateExecutionLogMap.cs rename src/MongODM.Core/{Operations => Models/Internal}/ModelMaps/OperationBaseMap.cs (95%) create mode 100644 src/MongODM.Core/Models/Internal/OperationBase.cs rename src/MongODM.Core/{Operations => Models/Internal}/SeedOperation.cs (82%) delete mode 100644 src/MongODM.Core/Operations/OperationBase.cs diff --git a/src/MongODM.AspNetCore/StaticConfigurationBuilder.cs b/src/MongODM.AspNetCore/StaticConfigurationBuilder.cs index f0389ab6..4ab051a2 100644 --- a/src/MongODM.AspNetCore/StaticConfigurationBuilder.cs +++ b/src/MongODM.AspNetCore/StaticConfigurationBuilder.cs @@ -1,5 +1,5 @@ using Etherna.MongODM.Conventions; -using Etherna.MongODM.Operations; +using Etherna.MongODM.Models.Internal; using Etherna.MongODM.ProxyModels; using Etherna.MongODM.Utility; using MongoDB.Bson; @@ -20,7 +20,7 @@ public StaticConfigurationBuilder(IProxyGenerator proxyGenerator) BsonSerializer.RegisterDiscriminatorConvention(typeof(TModelBase), new HierarchicalProxyTolerantDiscriminatorConvention("_t", proxyGenerator)); - BsonSerializer.RegisterDiscriminatorConvention(typeof(OperationBase), + BsonSerializer.RegisterDiscriminatorConvention(typeof(EntityModelBase), new HierarchicalProxyTolerantDiscriminatorConvention("_t", proxyGenerator)); } } diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index fa838c80..94bbcd43 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -1,7 +1,7 @@ using Etherna.MongODM.Migration; using Etherna.MongODM.Models; -using Etherna.MongODM.Operations; -using Etherna.MongODM.Operations.ModelMaps; +using Etherna.MongODM.Models.Internal; +using Etherna.MongODM.Models.Internal.ModelMaps; using Etherna.MongODM.ProxyModels; using Etherna.MongODM.Repositories; using Etherna.MongODM.Serialization; diff --git a/src/MongODM.Core/IDbContext.cs b/src/MongODM.Core/IDbContext.cs index 76caad34..7e004769 100644 --- a/src/MongODM.Core/IDbContext.cs +++ b/src/MongODM.Core/IDbContext.cs @@ -1,5 +1,5 @@ using Etherna.MongODM.Migration; -using Etherna.MongODM.Operations; +using Etherna.MongODM.Models.Internal; using Etherna.MongODM.ProxyModels; using Etherna.MongODM.Repositories; using Etherna.MongODM.Serialization; diff --git a/src/MongODM.Core/Migration/MigrationResult.cs b/src/MongODM.Core/Migration/MigrationResult.cs new file mode 100644 index 00000000..8b2f276d --- /dev/null +++ b/src/MongODM.Core/Migration/MigrationResult.cs @@ -0,0 +1,26 @@ +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 00040e3d..f2033432 100644 --- a/src/MongODM.Core/Migration/MongoCollectionMigration.cs +++ b/src/MongODM.Core/Migration/MongoCollectionMigration.cs @@ -18,16 +18,20 @@ public class MongoCollectionMigration where TModelDest : class, IEntityModel { + // Fields. private readonly Func converter; private readonly Func discriminator; private readonly IMongoCollection destinationCollection; private readonly IMongoCollection sourceCollection; + // Constructor. public MongoCollectionMigration( ICollectionRepository sourceCollection, ICollectionRepository destinationCollection, Func converter, - Func discriminator) + Func discriminator, + string id) + : base(id) { if (sourceCollection is null) throw new ArgumentNullException(nameof(sourceCollection)); @@ -40,7 +44,11 @@ public MongoCollectionMigration( this.discriminator = discriminator; } - public override Task MigrateAsync(CancellationToken cancellationToken = default) => + // Methods. + public override Task MigrateAsync( + int callbackEveryDocuments = 0, + Func? callbackAsync = null, + CancellationToken cancellationToken = default) => sourceCollection.Find(Builders.Filter.Empty, new FindOptions { NoCursorTimeout = true }) .ForEachAsync(obj => { diff --git a/src/MongODM.Core/Migration/MongoDocumentMigration.cs b/src/MongODM.Core/Migration/MongoDocumentMigration.cs index 5c8c4731..3a3ea97f 100644 --- a/src/MongODM.Core/Migration/MongoDocumentMigration.cs +++ b/src/MongODM.Core/Migration/MongoDocumentMigration.cs @@ -17,12 +17,16 @@ namespace Etherna.MongODM.Migration public class MongoDocumentMigration : MongoMigrationBase where TModel : class, IEntityModel { + // Fields. private readonly SemanticVersion minimumDocumentVersion; private readonly IMongoCollection sourceCollection; + // Constructors. public MongoDocumentMigration( ICollectionRepository sourceCollection, - SemanticVersion minimumDocumentVersion) + SemanticVersion minimumDocumentVersion, + string id) + : base(id) { if (sourceCollection is null) throw new ArgumentNullException(nameof(sourceCollection)); @@ -31,10 +35,11 @@ public MongoDocumentMigration( this.minimumDocumentVersion = minimumDocumentVersion; } - /// - /// Fix all documents prev of MinimumDocumentVersion - /// - public override async Task MigrateAsync(CancellationToken cancellationToken = default) + // Methods. + public override async Task MigrateAsync( + int callbackEveryDocuments = 0, + Func? callbackAsync = null, + CancellationToken cancellationToken = default) { var filterBuilder = Builders.Filter; var filter = filterBuilder.Or( diff --git a/src/MongODM.Core/Migration/MongoMigrationBase.cs b/src/MongODM.Core/Migration/MongoMigrationBase.cs index 53a67fdc..1643880e 100644 --- a/src/MongODM.Core/Migration/MongoMigrationBase.cs +++ b/src/MongODM.Core/Migration/MongoMigrationBase.cs @@ -1,10 +1,27 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; 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; } + + // 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/EntityModelBase.cs b/src/MongODM.Core/Models/Internal/EntityModelBase.cs new file mode 100644 index 00000000..4761fe39 --- /dev/null +++ b/src/MongODM.Core/Models/Internal/EntityModelBase.cs @@ -0,0 +1,46 @@ +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/MigrateOpAgg/MigrateExecutionLog.cs b/src/MongODM.Core/Models/Internal/MigrateOpAgg/MigrateExecutionLog.cs new file mode 100644 index 00000000..6887532b --- /dev/null +++ b/src/MongODM.Core/Models/Internal/MigrateOpAgg/MigrateExecutionLog.cs @@ -0,0 +1,38 @@ +using System; + +namespace Etherna.MongODM.Models.Internal.MigrateOpAgg +{ + public class MigrateExecutionLog : ModelBase + { + // Enums. + public enum ExecutionState + { + Executing, + Succeded, + Skipped, + Failed + } + + // Constructors. + public MigrateExecutionLog( + ExecutionState action, + string migrationId, + string migrationType, + long totMigratedDocs) + { + Action = action; + CreationDateTime = DateTime.Now; + MigrationId = migrationId; + MigrationType = migrationType; + TotMigratedDocs = totMigratedDocs; + } + protected MigrateExecutionLog() { } + + // Properties. + public virtual ExecutionState Action { get; protected set; } + public virtual DateTime CreationDateTime { get; protected set; } + public virtual string MigrationId { get; protected set; } = default!; + public string MigrationType { get; } = default!; + public long TotMigratedDocs { get; protected set; } + } +} diff --git a/src/MongODM.Core/Operations/MigrateOperation.cs b/src/MongODM.Core/Models/Internal/MigrateOperation.cs similarity index 66% rename from src/MongODM.Core/Operations/MigrateOperation.cs rename to src/MongODM.Core/Models/Internal/MigrateOperation.cs index 194a5723..9486b579 100644 --- a/src/MongODM.Core/Operations/MigrateOperation.cs +++ b/src/MongODM.Core/Models/Internal/MigrateOperation.cs @@ -1,7 +1,9 @@ using Etherna.MongODM.Attributes; +using Etherna.MongODM.Models.Internal.MigrateOpAgg; using System; +using System.Collections.Generic; -namespace Etherna.MongODM.Operations +namespace Etherna.MongODM.Models.Internal { public class MigrateOperation : OperationBase { @@ -14,6 +16,9 @@ public enum Status Cancelled } + // Fields. + private List _logs = new List(); + // Constructors. public MigrateOperation(IDbContext dbContext, string? author) : base(dbContext) @@ -21,16 +26,31 @@ public MigrateOperation(IDbContext dbContext, string? author) Author = author; CurrentStatus = Status.New; } + protected MigrateOperation() { } // 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(MigrateExecutionLog log) + { + if (log is null) + throw new ArgumentNullException(nameof(log)); + + _logs.Add(log); + } + [PropertyAlterer(nameof(CurrentStatus))] - public void TaskCancelled() + public virtual void TaskCancelled() { if (CurrentStatus == Status.Completed) throw new InvalidOperationException(); @@ -40,7 +60,7 @@ public void TaskCancelled() [PropertyAlterer(nameof(CompletedDateTime))] [PropertyAlterer(nameof(CurrentStatus))] - public void TaskCompleted() + public virtual void TaskCompleted() { if (CurrentStatus != Status.Running) throw new InvalidOperationException(); @@ -51,7 +71,7 @@ public void TaskCompleted() [PropertyAlterer(nameof(CurrentStatus))] [PropertyAlterer(nameof(TaskId))] - public void TaskStarted(string taskId) + public virtual void TaskStarted(string taskId) { if (taskId is null) throw new ArgumentNullException(nameof(taskId)); diff --git a/src/MongODM.Core/Models/Internal/ModelBase.cs b/src/MongODM.Core/Models/Internal/ModelBase.cs new file mode 100644 index 00000000..4ac98839 --- /dev/null +++ b/src/MongODM.Core/Models/Internal/ModelBase.cs @@ -0,0 +1,11 @@ +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/MigrateExecutionLogMap.cs b/src/MongODM.Core/Models/Internal/ModelMaps/MigrateExecutionLogMap.cs new file mode 100644 index 00000000..d6a4795a --- /dev/null +++ b/src/MongODM.Core/Models/Internal/ModelMaps/MigrateExecutionLogMap.cs @@ -0,0 +1,14 @@ +using Etherna.MongODM.Models.Internal.MigrateOpAgg; +using Etherna.MongODM.Serialization; + +namespace Etherna.MongODM.Models.Internal.ModelMaps +{ + class MigrateExecutionLogMap : IModelMapsCollector + { + public void Register(IDbContext dbContext) + { + dbContext.DocumentSchemaRegister.RegisterModelSchema( + "0.20.0"); //mongodm library's version + } + } +} diff --git a/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs b/src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs similarity index 95% rename from src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs rename to src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs index 479276ea..6a4be08d 100644 --- a/src/MongODM.Core/Operations/ModelMaps/OperationBaseMap.cs +++ b/src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs @@ -4,7 +4,7 @@ using MongoDB.Bson.Serialization.IdGenerators; using MongoDB.Bson.Serialization.Serializers; -namespace Etherna.MongODM.Operations.ModelMaps +namespace Etherna.MongODM.Models.Internal.ModelMaps { class OperationBaseMap : IModelMapsCollector { diff --git a/src/MongODM.Core/Models/Internal/OperationBase.cs b/src/MongODM.Core/Models/Internal/OperationBase.cs new file mode 100644 index 00000000..fa3f258a --- /dev/null +++ b/src/MongODM.Core/Models/Internal/OperationBase.cs @@ -0,0 +1,21 @@ +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/Operations/SeedOperation.cs b/src/MongODM.Core/Models/Internal/SeedOperation.cs similarity index 82% rename from src/MongODM.Core/Operations/SeedOperation.cs rename to src/MongODM.Core/Models/Internal/SeedOperation.cs index dd54cd51..9ed838e8 100644 --- a/src/MongODM.Core/Operations/SeedOperation.cs +++ b/src/MongODM.Core/Models/Internal/SeedOperation.cs @@ -1,4 +1,4 @@ -namespace Etherna.MongODM.Operations +namespace Etherna.MongODM.Models.Internal { public class SeedOperation : OperationBase { diff --git a/src/MongODM.Core/Operations/OperationBase.cs b/src/MongODM.Core/Operations/OperationBase.cs deleted file mode 100644 index e8661da6..00000000 --- a/src/MongODM.Core/Operations/OperationBase.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Etherna.MongODM.Models; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Etherna.MongODM.Operations -{ - public abstract class OperationBase : IEntityModel - { - // Constructors and dispose. - public OperationBase(IDbContext dbContext) - { - if (dbContext is null) - throw new ArgumentNullException(nameof(dbContext)); - - CreationDateTime = DateTime.Now; - DbContextName = dbContext.Identifier; - } - protected OperationBase() { } - public void DisposeForDelete() { } - - // Properties. - public virtual string Id { get; protected set; } = default!; - public virtual DateTime CreationDateTime { get; protected set; } - public virtual string DbContextName { get; protected set; } = default!; - - [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/Tasks/IMigrateDbContextTask.cs b/src/MongODM.Core/Tasks/IMigrateDbContextTask.cs index b7b34b64..c1068187 100644 --- a/src/MongODM.Core/Tasks/IMigrateDbContextTask.cs +++ b/src/MongODM.Core/Tasks/IMigrateDbContextTask.cs @@ -4,7 +4,7 @@ namespace Etherna.MongODM.Tasks { public interface IMigrateDbContextTask { - Task RunAsync(string authorId) + Task RunAsync(string authorId, string taskId) where TDbContext : class, IDbContext; } } \ No newline at end of file diff --git a/src/MongODM.Core/Tasks/MigrateDbContextTask.cs b/src/MongODM.Core/Tasks/MigrateDbContextTask.cs index 2739662b..337deb6c 100644 --- a/src/MongODM.Core/Tasks/MigrateDbContextTask.cs +++ b/src/MongODM.Core/Tasks/MigrateDbContextTask.cs @@ -1,4 +1,5 @@ -using Etherna.MongODM.Operations; +using Etherna.MongODM.Models.Internal; +using Etherna.MongODM.Models.Internal.MigrateOpAgg; using System; using System.Threading.Tasks; @@ -17,19 +18,45 @@ public MigrateDbContextTask( } // Methods. - public async Task RunAsync(string migrateOpId) + public async Task RunAsync(string migrateOpId, string taskId) where TDbContext : class, IDbContext { var dbContext = (TDbContext)serviceProvider.GetService(typeof(TDbContext)); var migrateOp = (MigrateOperation)await dbContext.DbOperations.FindOneAsync(migrateOpId).ConfigureAwait(false); // Start migrate operation. - migrateOp.TaskStarted(); + migrateOp.TaskStarted(taskId); await dbContext.SaveChangesAsync().ConfigureAwait(false); // Migrate collections. foreach (var migration in dbContext.MigrationTaskList) - await migration.MigrateAsync(cancellationToken).ConfigureAwait(false); + { + var migrationType = migration.GetType().Name; + + //running migration + var result = await migration.MigrateAsync(500, + async procDocs => + { + migrateOp.AddLog(new MigrateExecutionLog( + MigrateExecutionLog.ExecutionState.Executing, + migration.Id, + migrationType, + procDocs)); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + }).ConfigureAwait(false); + + //ended migration log + migrateOp.AddLog(new MigrateExecutionLog( + result.Succeded ? + MigrateExecutionLog.ExecutionState.Succeded : + MigrateExecutionLog.ExecutionState.Failed, + migration.Id, + migrationType, + result.MigratedDocuments)); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } // Build indexes. foreach (var repository in dbContext.RepositoryRegister.ModelCollectionRepositoryMap.Values) diff --git a/src/MongODM.Core/Utility/DbContextMigrationManager.cs b/src/MongODM.Core/Utility/DbContextMigrationManager.cs index dd9afe1d..eedea143 100644 --- a/src/MongODM.Core/Utility/DbContextMigrationManager.cs +++ b/src/MongODM.Core/Utility/DbContextMigrationManager.cs @@ -1,5 +1,5 @@ using Etherna.MongODM.Extensions; -using Etherna.MongODM.Operations; +using Etherna.MongODM.Models.Internal; using Etherna.MongODM.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; diff --git a/src/MongODM.Core/Utility/IDbContextMigrationManager.cs b/src/MongODM.Core/Utility/IDbContextMigrationManager.cs index a9298212..88b918ca 100644 --- a/src/MongODM.Core/Utility/IDbContextMigrationManager.cs +++ b/src/MongODM.Core/Utility/IDbContextMigrationManager.cs @@ -1,4 +1,4 @@ -using Etherna.MongODM.Operations; +using Etherna.MongODM.Models.Internal; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs b/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs index 1b069a0d..ef0c059f 100644 --- a/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs +++ b/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs @@ -26,7 +26,7 @@ public HangfireTaskRunner( // Methods. public void RunMigrateDbContextTask(Type dbContextType, string migrateOpId) => backgroundJobClient.Enqueue( - task => task.RunAsync(dbContextType, migrateOpId)); + task => task.RunAsync(dbContextType, migrateOpId, null!)); public void RunUpdateDocDependenciesTask(Type dbContextType, Type modelType, Type keyType, IEnumerable idPaths, object modelId) => backgroundJobClient.Enqueue( diff --git a/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs b/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs index 4b304b0e..39282ff4 100644 --- a/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs +++ b/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs @@ -1,5 +1,6 @@ using Etherna.MongODM.Tasks; using Hangfire; +using Hangfire.Server; using System; using System.Reflection; using System.Threading.Tasks; @@ -19,13 +20,13 @@ public MigrateDbContextTaskFacade(IMigrateDbContextTask task) // Methods. [Queue(Queues.DB_MAINTENANCE)] - public Task RunAsync(Type dbContextType, string migrateOpId) + public Task RunAsync(Type dbContextType, string migrateOpId, PerformingContext context) { var method = typeof(MigrateDbContextTask).GetMethod( nameof(MigrateDbContextTask.RunAsync), BindingFlags.Public | BindingFlags.Instance) .MakeGenericMethod(dbContextType); - return (Task)method.Invoke(task, new object[] { migrateOpId }); + return (Task)method.Invoke(task, new object[] { migrateOpId, context.BackgroundJob.Id }); } } } From 6c45c90f857d742d17c5b473892a2f2d10ea4d49 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Fri, 17 Jul 2020 00:31:38 +0200 Subject: [PATCH 24/27] Update background migration task implementation --- .../ServiceCollectionExtensions.cs | 8 +-- src/MongODM.Core/DbContext.cs | 19 ++++--- src/MongODM.Core/IDbContext.cs | 16 +++--- .../Migration/MongoCollectionMigration.cs | 30 ++++++++--- .../Migration/MongoDocumentMigration.cs | 21 +++++++- .../DbMigrationOpAgg/DocumentMigrationLog.cs | 23 ++++++++ .../DbMigrationOpAgg/IndexMigrationLog.cs | 18 +++++++ .../DbMigrationOpAgg/MigrationLogBase.cs | 28 ++++++++++ ...teOperation.cs => DbMigrationOperation.cs} | 16 +++--- .../MigrateOpAgg/MigrateExecutionLog.cs | 38 ------------- .../ModelMaps/DbMigrationOperationMap.cs | 23 ++++++++ .../ModelMaps/MigrateExecutionLogMap.cs | 14 ----- .../Models/Internal/ModelMaps/ModelBaseMap.cs | 26 +++++++++ .../Internal/ModelMaps/SeedOperationMap.cs | 13 +++++ .../Repositories/CollectionRepository.cs | 1 + .../Repositories/GridFSRepository.cs | 1 + src/MongODM.Core/Repositories/IRepository.cs | 1 + .../Repositories/RepositoryBase.cs | 1 + .../Serializers/ExtendedClassMapSerializer.cs | 4 +- .../Tasks/IMigrateDbContextTask.cs | 2 +- src/MongODM.Core/Tasks/ITaskRunner.cs | 2 +- .../Tasks/MigrateDbContextTask.cs | 54 +++++++++++-------- .../Utility/{DbContextCache.cs => DbCache.cs} | 4 +- ...ntextDependencies.cs => DbDependencies.cs} | 22 ++++---- ...DbContextMaintainer.cs => DbMaintainer.cs} | 4 +- ...rationManager.cs => DbMigrationManager.cs} | 22 ++++---- .../{IDbContextCache.cs => IDbCache.cs} | 4 +- ...textDependencies.cs => IDbDependencies.cs} | 8 +-- ...bContextMaintainer.cs => IDbMaintainer.cs} | 4 +- ...ationManager.cs => IDbMigrationManager.cs} | 8 +-- .../Tasks/HangfireTaskRunner.cs | 4 +- .../Tasks/MigrateDbContextTaskFacade.cs | 4 +- .../ExtendedClassMapSerializerTest.cs | 4 +- 33 files changed, 289 insertions(+), 158 deletions(-) create mode 100644 src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs create mode 100644 src/MongODM.Core/Models/Internal/DbMigrationOpAgg/IndexMigrationLog.cs create mode 100644 src/MongODM.Core/Models/Internal/DbMigrationOpAgg/MigrationLogBase.cs rename src/MongODM.Core/Models/Internal/{MigrateOperation.cs => DbMigrationOperation.cs} (79%) delete mode 100644 src/MongODM.Core/Models/Internal/MigrateOpAgg/MigrateExecutionLog.cs create mode 100644 src/MongODM.Core/Models/Internal/ModelMaps/DbMigrationOperationMap.cs delete mode 100644 src/MongODM.Core/Models/Internal/ModelMaps/MigrateExecutionLogMap.cs create mode 100644 src/MongODM.Core/Models/Internal/ModelMaps/ModelBaseMap.cs create mode 100644 src/MongODM.Core/Models/Internal/ModelMaps/SeedOperationMap.cs rename src/MongODM.Core/Utility/{DbContextCache.cs => DbCache.cs} (95%) rename src/MongODM.Core/Utility/{DbContextDependencies.cs => DbDependencies.cs} (68%) rename src/MongODM.Core/Utility/{DbContextMaintainer.cs => DbMaintainer.cs} (94%) rename src/MongODM.Core/Utility/{DbContextMigrationManager.cs => DbMigrationManager.cs} (70%) rename src/MongODM.Core/Utility/{IDbContextCache.cs => IDbCache.cs} (90%) rename src/MongODM.Core/Utility/{IDbContextDependencies.cs => IDbDependencies.cs} (67%) rename src/MongODM.Core/Utility/{IDbContextMaintainer.cs => IDbMaintainer.cs} (78%) rename src/MongODM.Core/Utility/{IDbContextMigrationManager.cs => IDbMigrationManager.cs} (59%) diff --git a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs index f0961743..eb7a05fa 100644 --- a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs @@ -60,10 +60,10 @@ public static MongODMConfiguration UseMongODM(); - services.TryAddTransient(); - services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddSingleton(); diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index 94bbcd43..8e484864 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -25,7 +25,7 @@ public abstract class DbContext : IDbContext // Constructors and initialization. public DbContext( - IDbContextDependencies dependencies, + IDbDependencies dependencies, DbContextOptions options) { if (dependencies is null) @@ -34,9 +34,9 @@ public DbContext( throw new ArgumentNullException(nameof(options)); ApplicationVersion = options.ApplicationVersion; - DbCache = dependencies.DbContextCache; - DbMaintainer = dependencies.DbContextMaintainer; - DbContextMigrationManager = dependencies.DbContextMigrationManager; + DbCache = dependencies.DbCache; + DbMaintainer = dependencies.DbMaintainer; + DbMigrationManager = dependencies.DbMigrationManager; DbOperations = new CollectionRepository(options.DbOperationsCollectionName); DocumentSchemaRegister = dependencies.DocumentSchemaRegister; Identifier = options.Identifier ?? GetType().Name; @@ -65,7 +65,10 @@ public DbContext( // Register model maps. //internal maps + new DbMigrationOperationMap().Register(this); + new ModelBaseMap().Register(this); new OperationBaseMap().Register(this); + new SeedOperationMap().Register(this); //application maps foreach (var maps in ModelMapsCollectors) @@ -86,14 +89,14 @@ public DbContext( .ToList(); public IMongoClient Client { get; } public IMongoDatabase Database { get; } - public IDbContextCache DbCache { get; } - public IDbContextMaintainer DbMaintainer { get; } - public IDbContextMigrationManager DbContextMigrationManager { 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 string Identifier { get; } public SemanticVersion LibraryVersion { get; } - public virtual IEnumerable MigrationTaskList { get; } = Array.Empty(); public IProxyGenerator ProxyGenerator { get; } public IRepositoryRegister RepositoryRegister { get; } public ISerializerModifierAccessor SerializerModifierAccessor { get; } diff --git a/src/MongODM.Core/IDbContext.cs b/src/MongODM.Core/IDbContext.cs index 7e004769..5cbe8ab6 100644 --- a/src/MongODM.Core/IDbContext.cs +++ b/src/MongODM.Core/IDbContext.cs @@ -36,23 +36,28 @@ public interface IDbContext /// /// Database cache container. /// - IDbContextCache DbCache { get; } + IDbCache DbCache { get; } /// /// Database operator interested into maintenance tasks. /// - IDbContextMaintainer DbMaintainer { get; } + IDbMaintainer DbMaintainer { get; } /// /// Manage migrations over database context /// - IDbContextMigrationManager DbContextMigrationManager { get; } + 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. /// @@ -68,11 +73,6 @@ public interface IDbContext /// SemanticVersion LibraryVersion { get; } - /// - /// List of registered migration tasks - /// - IEnumerable MigrationTaskList { get; } - /// /// Current model proxy generator. /// diff --git a/src/MongODM.Core/Migration/MongoCollectionMigration.cs b/src/MongODM.Core/Migration/MongoCollectionMigration.cs index f2033432..9b2237d1 100644 --- a/src/MongODM.Core/Migration/MongoCollectionMigration.cs +++ b/src/MongODM.Core/Migration/MongoCollectionMigration.cs @@ -45,15 +45,31 @@ public MongoCollectionMigration( } // Methods. - public override Task MigrateAsync( + public override async Task MigrateAsync( int callbackEveryDocuments = 0, Func? callbackAsync = null, - CancellationToken cancellationToken = default) => - sourceCollection.Find(Builders.Filter.Empty, new FindOptions { NoCursorTimeout = true }) - .ForEachAsync(obj => + CancellationToken cancellationToken = default) + { + if (callbackEveryDocuments < 0) + throw new ArgumentOutOfRangeException(nameof(callbackEveryDocuments), "Value can't be negative"); + + // Migrate documents. + var totMigratedDocuments = 0L; + await sourceCollection.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 3a3ea97f..59a758e1 100644 --- a/src/MongODM.Core/Migration/MongoDocumentMigration.cs +++ b/src/MongODM.Core/Migration/MongoDocumentMigration.cs @@ -41,6 +41,10 @@ public override async Task MigrateAsync( 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). @@ -65,9 +69,22 @@ public override async Task MigrateAsync( filterBuilder.Eq($"{DbContext.DocumentVersionElementName}.1", minimumDocumentVersion.MinorRelease), filterBuilder.Lt($"{DbContext.DocumentVersionElementName}.2", minimumDocumentVersion.PatchRelease))); - // Replace documents. + // Migrate documents. + var totMigratedDocuments = 0L; await sourceCollection.Find(filter, new FindOptions { NoCursorTimeout = true }) - .ForEachAsync(obj => sourceCollection.ReplaceOneAsync(Builders.Filter.Eq(m => m.Id, obj.Id), obj), cancellationToken).ConfigureAwait(false); + .ForEachAsync(async model => + { + if (callbackEveryDocuments > 0 && + totMigratedDocuments % callbackEveryDocuments == 0 && + callbackAsync != null) + await callbackAsync.Invoke(totMigratedDocuments).ConfigureAwait(false); + + await sourceCollection.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/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs new file mode 100644 index 00000000..49477d93 --- /dev/null +++ b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs @@ -0,0 +1,23 @@ +using System; + +namespace Etherna.MongODM.Models.Internal.DbMigrationOpAgg +{ + public class DocumentMigrationLog : MigrationLogBase + { + // Constructors. + public DocumentMigrationLog( + string documentMigrationId, + ExecutionState state, + long totMigratedDocs) + : base(state) + { + DocumentMigrationId = documentMigrationId; + TotMigratedDocs = totMigratedDocs; + } + protected DocumentMigrationLog() { } + + // Properties. + 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..c75d408d --- /dev/null +++ b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/IndexMigrationLog.cs @@ -0,0 +1,18 @@ +namespace Etherna.MongODM.Models.Internal.DbMigrationOpAgg +{ + 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..71126870 --- /dev/null +++ b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/MigrationLogBase.cs @@ -0,0 +1,28 @@ +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/MigrateOperation.cs b/src/MongODM.Core/Models/Internal/DbMigrationOperation.cs similarity index 79% rename from src/MongODM.Core/Models/Internal/MigrateOperation.cs rename to src/MongODM.Core/Models/Internal/DbMigrationOperation.cs index 9486b579..73e15cdb 100644 --- a/src/MongODM.Core/Models/Internal/MigrateOperation.cs +++ b/src/MongODM.Core/Models/Internal/DbMigrationOperation.cs @@ -1,11 +1,11 @@ using Etherna.MongODM.Attributes; -using Etherna.MongODM.Models.Internal.MigrateOpAgg; +using Etherna.MongODM.Models.Internal.DbMigrationOpAgg; using System; using System.Collections.Generic; namespace Etherna.MongODM.Models.Internal { - public class MigrateOperation : OperationBase + public class DbMigrationOperation : OperationBase { // Enums. public enum Status @@ -17,31 +17,31 @@ public enum Status } // Fields. - private List _logs = new List(); + private List _logs = new List(); // Constructors. - public MigrateOperation(IDbContext dbContext, string? author) + public DbMigrationOperation(IDbContext dbContext, string? author) : base(dbContext) { Author = author; CurrentStatus = Status.New; } - protected MigrateOperation() { } + 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 + public virtual IEnumerable Logs { get => _logs; - protected set => _logs = new List(value ?? Array.Empty()); + protected set => _logs = new List(value ?? Array.Empty()); } public virtual string? TaskId { get; protected set; } // Methods. [PropertyAlterer(nameof(Logs))] - public virtual void AddLog(MigrateExecutionLog log) + public virtual void AddLog(MigrationLogBase log) { if (log is null) throw new ArgumentNullException(nameof(log)); diff --git a/src/MongODM.Core/Models/Internal/MigrateOpAgg/MigrateExecutionLog.cs b/src/MongODM.Core/Models/Internal/MigrateOpAgg/MigrateExecutionLog.cs deleted file mode 100644 index 6887532b..00000000 --- a/src/MongODM.Core/Models/Internal/MigrateOpAgg/MigrateExecutionLog.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -namespace Etherna.MongODM.Models.Internal.MigrateOpAgg -{ - public class MigrateExecutionLog : ModelBase - { - // Enums. - public enum ExecutionState - { - Executing, - Succeded, - Skipped, - Failed - } - - // Constructors. - public MigrateExecutionLog( - ExecutionState action, - string migrationId, - string migrationType, - long totMigratedDocs) - { - Action = action; - CreationDateTime = DateTime.Now; - MigrationId = migrationId; - MigrationType = migrationType; - TotMigratedDocs = totMigratedDocs; - } - protected MigrateExecutionLog() { } - - // Properties. - public virtual ExecutionState Action { get; protected set; } - public virtual DateTime CreationDateTime { get; protected set; } - public virtual string MigrationId { get; protected set; } = default!; - public string MigrationType { get; } = default!; - public long TotMigratedDocs { 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..2977955e --- /dev/null +++ b/src/MongODM.Core/Models/Internal/ModelMaps/DbMigrationOperationMap.cs @@ -0,0 +1,23 @@ +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/MigrateExecutionLogMap.cs b/src/MongODM.Core/Models/Internal/ModelMaps/MigrateExecutionLogMap.cs deleted file mode 100644 index d6a4795a..00000000 --- a/src/MongODM.Core/Models/Internal/ModelMaps/MigrateExecutionLogMap.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Etherna.MongODM.Models.Internal.MigrateOpAgg; -using Etherna.MongODM.Serialization; - -namespace Etherna.MongODM.Models.Internal.ModelMaps -{ - class MigrateExecutionLogMap : IModelMapsCollector - { - public void Register(IDbContext dbContext) - { - dbContext.DocumentSchemaRegister.RegisterModelSchema( - "0.20.0"); //mongodm library's version - } - } -} 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..f7dd9ebe --- /dev/null +++ b/src/MongODM.Core/Models/Internal/ModelMaps/ModelBaseMap.cs @@ -0,0 +1,26 @@ +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/SeedOperationMap.cs b/src/MongODM.Core/Models/Internal/ModelMaps/SeedOperationMap.cs new file mode 100644 index 00000000..5a918790 --- /dev/null +++ b/src/MongODM.Core/Models/Internal/ModelMaps/SeedOperationMap.cs @@ -0,0 +1,13 @@ +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/Repositories/CollectionRepository.cs b/src/MongODM.Core/Repositories/CollectionRepository.cs index 643e50ff..cd7b0273 100644 --- a/src/MongODM.Core/Repositories/CollectionRepository.cs +++ b/src/MongODM.Core/Repositories/CollectionRepository.cs @@ -35,6 +35,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) diff --git a/src/MongODM.Core/Repositories/GridFSRepository.cs b/src/MongODM.Core/Repositories/GridFSRepository.cs index 4d7215e3..cfd371da 100644 --- a/src/MongODM.Core/Repositories/GridFSRepository.cs +++ b/src/MongODM.Core/Repositories/GridFSRepository.cs @@ -34,6 +34,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; diff --git a/src/MongODM.Core/Repositories/IRepository.cs b/src/MongODM.Core/Repositories/IRepository.cs index 636faffe..acfb301a 100644 --- a/src/MongODM.Core/Repositories/IRepository.cs +++ b/src/MongODM.Core/Repositories/IRepository.cs @@ -12,6 +12,7 @@ public interface IRepository : IDbContextInitializable IDbContext DbContext { get; } Type GetKeyType { get; } Type GetModelType { get; } + string Name { get; } Task BuildIndexesAsync( IDocumentSchemaRegister schemaRegister, diff --git a/src/MongODM.Core/Repositories/RepositoryBase.cs b/src/MongODM.Core/Repositories/RepositoryBase.cs index 22237fc1..1f8b7ba4 100644 --- a/src/MongODM.Core/Repositories/RepositoryBase.cs +++ b/src/MongODM.Core/Repositories/RepositoryBase.cs @@ -31,6 +31,7 @@ public virtual void Initialize(IDbContext dbContext) public Type GetKeyType => typeof(TKey); public Type GetModelType => typeof(TModel); public bool IsInitialized { get; private set; } + public abstract string Name { get; } // Methods. public abstract Task BuildIndexesAsync(IDocumentSchemaRegister schemaRegister, CancellationToken cancellationToken = default); diff --git a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs index cd201b0e..a4bc991d 100644 --- a/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs +++ b/src/MongODM.Core/Serialization/Serializers/ExtendedClassMapSerializer.cs @@ -28,7 +28,7 @@ private struct ExtraElementCondition private readonly BsonElement documentVersionElement; // Fields. - private readonly IDbContextCache dbCache; + private readonly IDbCache dbCache; private readonly ISerializerModifierAccessor serializerModifierAccessor; private readonly ICollection extraElements; private readonly Func> fixDeserializedModelAsync; @@ -36,7 +36,7 @@ private struct ExtraElementCondition // Constructor. public ExtendedClassMapSerializer( - IDbContextCache dbCache, + IDbCache dbCache, SemanticVersion documentVersion, ISerializerModifierAccessor serializerModifierAccessor, Func>? fixDeserializedModelAsync = null) diff --git a/src/MongODM.Core/Tasks/IMigrateDbContextTask.cs b/src/MongODM.Core/Tasks/IMigrateDbContextTask.cs index c1068187..23c2e226 100644 --- a/src/MongODM.Core/Tasks/IMigrateDbContextTask.cs +++ b/src/MongODM.Core/Tasks/IMigrateDbContextTask.cs @@ -4,7 +4,7 @@ namespace Etherna.MongODM.Tasks { public interface IMigrateDbContextTask { - Task RunAsync(string authorId, string taskId) + Task RunAsync(string dbMigrationOpId, string taskId) where TDbContext : class, IDbContext; } } \ No newline at end of file diff --git a/src/MongODM.Core/Tasks/ITaskRunner.cs b/src/MongODM.Core/Tasks/ITaskRunner.cs index 32415ea5..1a0e241f 100644 --- a/src/MongODM.Core/Tasks/ITaskRunner.cs +++ b/src/MongODM.Core/Tasks/ITaskRunner.cs @@ -5,7 +5,7 @@ namespace Etherna.MongODM.Tasks { public interface ITaskRunner { - void RunMigrateDbContextTask(Type dbContextType, string migrateOpId); + void RunMigrateDbTask(Type dbContextType, string dbMigrationOpId); void RunUpdateDocDependenciesTask(Type dbContextType, Type modelType, Type keyType, IEnumerable idPaths, object modelId); } } diff --git a/src/MongODM.Core/Tasks/MigrateDbContextTask.cs b/src/MongODM.Core/Tasks/MigrateDbContextTask.cs index 337deb6c..65a64b3c 100644 --- a/src/MongODM.Core/Tasks/MigrateDbContextTask.cs +++ b/src/MongODM.Core/Tasks/MigrateDbContextTask.cs @@ -1,5 +1,5 @@ using Etherna.MongODM.Models.Internal; -using Etherna.MongODM.Models.Internal.MigrateOpAgg; +using Etherna.MongODM.Models.Internal.DbMigrationOpAgg; using System; using System.Threading.Tasks; @@ -18,41 +18,37 @@ public MigrateDbContextTask( } // Methods. - public async Task RunAsync(string migrateOpId, string taskId) + public async Task RunAsync(string dbMigrationOpId, string taskId) where TDbContext : class, IDbContext { var dbContext = (TDbContext)serviceProvider.GetService(typeof(TDbContext)); - var migrateOp = (MigrateOperation)await dbContext.DbOperations.FindOneAsync(migrateOpId).ConfigureAwait(false); + var dbMigrationOp = (DbMigrationOperation)await dbContext.DbOperations.FindOneAsync(dbMigrationOpId).ConfigureAwait(false); // Start migrate operation. - migrateOp.TaskStarted(taskId); + dbMigrationOp.TaskStarted(taskId); await dbContext.SaveChangesAsync().ConfigureAwait(false); - // Migrate collections. - foreach (var migration in dbContext.MigrationTaskList) + // Migrate documents. + foreach (var docMigration in dbContext.DocumentMigrationList) { - var migrationType = migration.GetType().Name; - - //running migration - var result = await migration.MigrateAsync(500, + //running document migration + var result = await docMigration.MigrateAsync(500, async procDocs => { - migrateOp.AddLog(new MigrateExecutionLog( - MigrateExecutionLog.ExecutionState.Executing, - migration.Id, - migrationType, + dbMigrationOp.AddLog(new DocumentMigrationLog( + docMigration.Id, + MigrationLogBase.ExecutionState.Executing, procDocs)); await dbContext.SaveChangesAsync().ConfigureAwait(false); }).ConfigureAwait(false); - //ended migration log - migrateOp.AddLog(new MigrateExecutionLog( + //ended document migration log + dbMigrationOp.AddLog(new DocumentMigrationLog( + docMigration.Id, result.Succeded ? - MigrateExecutionLog.ExecutionState.Succeded : - MigrateExecutionLog.ExecutionState.Failed, - migration.Id, - migrationType, + MigrationLogBase.ExecutionState.Succeded : + MigrationLogBase.ExecutionState.Failed, result.MigratedDocuments)); await dbContext.SaveChangesAsync().ConfigureAwait(false); @@ -60,7 +56,23 @@ public async Task RunAsync(string migrateOpId, string taskId) // Build indexes. foreach (var repository in dbContext.RepositoryRegister.ModelCollectionRepositoryMap.Values) - await repository.BuildIndexesAsync(dbContext.DocumentSchemaRegister, cancellationToken).ConfigureAwait(false); + { + dbMigrationOp.AddLog(new IndexMigrationLog( + repository.Name, + MigrationLogBase.ExecutionState.Executing)); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + + await repository.BuildIndexesAsync(dbContext.DocumentSchemaRegister).ConfigureAwait(false); + + dbMigrationOp.AddLog(new IndexMigrationLog( + repository.Name, + MigrationLogBase.ExecutionState.Succeded)); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + // Complete task. + dbMigrationOp.TaskCompleted(); + await dbContext.SaveChangesAsync().ConfigureAwait(false); } } } diff --git a/src/MongODM.Core/Utility/DbContextCache.cs b/src/MongODM.Core/Utility/DbCache.cs similarity index 95% rename from src/MongODM.Core/Utility/DbContextCache.cs rename to src/MongODM.Core/Utility/DbCache.cs index 20c7065b..6eaa5b7d 100644 --- a/src/MongODM.Core/Utility/DbContextCache.cs +++ b/src/MongODM.Core/Utility/DbCache.cs @@ -5,7 +5,7 @@ namespace Etherna.MongODM.Utility { - public class DbContextCache : IDbContextCache + public class DbCache : IDbCache { // Consts. private const string CacheKey = "DBCache"; @@ -14,7 +14,7 @@ public class DbContextCache : IDbContextCache private readonly IExecutionContext executionContext; // Constructors. - public DbContextCache(IExecutionContext executionContext) + public DbCache(IExecutionContext executionContext) { this.executionContext = executionContext ?? throw new ArgumentNullException(nameof(executionContext)); } diff --git a/src/MongODM.Core/Utility/DbContextDependencies.cs b/src/MongODM.Core/Utility/DbDependencies.cs similarity index 68% rename from src/MongODM.Core/Utility/DbContextDependencies.cs rename to src/MongODM.Core/Utility/DbDependencies.cs index c108da6d..be2096c3 100644 --- a/src/MongODM.Core/Utility/DbContextDependencies.cs +++ b/src/MongODM.Core/Utility/DbDependencies.cs @@ -5,12 +5,12 @@ namespace Etherna.MongODM.Utility { - public class DbContextDependencies : IDbContextDependencies + public class DbDependencies : IDbDependencies { - public DbContextDependencies( - IDbContextCache dbCache, - IDbContextMaintainer dbMaintainer, - IDbContextMigrationManager dbContextMigrationManager, + public DbDependencies( + IDbCache dbCache, + IDbMaintainer dbMaintainer, + IDbMigrationManager dbContextMigrationManager, IDocumentSchemaRegister documentSchemaRegister, IProxyGenerator proxyGenerator, IRepositoryRegister repositoryRegister, @@ -21,18 +21,18 @@ public DbContextDependencies( #pragma warning restore CA1801 // Review unused parameters #pragma warning restore IDE0060 // Remove unused parameter { - DbContextCache = dbCache; - DbContextMaintainer = dbMaintainer; - DbContextMigrationManager = dbContextMigrationManager; + DbCache = dbCache; + DbMaintainer = dbMaintainer; + DbMigrationManager = dbContextMigrationManager; DocumentSchemaRegister = documentSchemaRegister; ProxyGenerator = proxyGenerator; RepositoryRegister = repositoryRegister; SerializerModifierAccessor = serializerModifierAccessor; } - public IDbContextCache DbContextCache { get; } - public IDbContextMaintainer DbContextMaintainer { get; } - public IDbContextMigrationManager DbContextMigrationManager { get; } + public IDbCache DbCache { get; } + public IDbMaintainer DbMaintainer { get; } + public IDbMigrationManager DbMigrationManager { get; } public IDocumentSchemaRegister DocumentSchemaRegister { get; } public IProxyGenerator ProxyGenerator { get; } public IRepositoryRegister RepositoryRegister { get; } diff --git a/src/MongODM.Core/Utility/DbContextMaintainer.cs b/src/MongODM.Core/Utility/DbMaintainer.cs similarity index 94% rename from src/MongODM.Core/Utility/DbContextMaintainer.cs rename to src/MongODM.Core/Utility/DbMaintainer.cs index 9e838a08..e871015a 100644 --- a/src/MongODM.Core/Utility/DbContextMaintainer.cs +++ b/src/MongODM.Core/Utility/DbMaintainer.cs @@ -6,14 +6,14 @@ namespace Etherna.MongODM.Utility { - public class DbContextMaintainer : IDbContextMaintainer + public class DbMaintainer : IDbMaintainer { // Fields. private IDbContext dbContext = default!; private readonly ITaskRunner taskRunner; // Constructors and initialization. - public DbContextMaintainer(ITaskRunner taskRunner) + public DbMaintainer(ITaskRunner taskRunner) { this.taskRunner = taskRunner; } diff --git a/src/MongODM.Core/Utility/DbContextMigrationManager.cs b/src/MongODM.Core/Utility/DbMigrationManager.cs similarity index 70% rename from src/MongODM.Core/Utility/DbContextMigrationManager.cs rename to src/MongODM.Core/Utility/DbMigrationManager.cs index eedea143..16823581 100644 --- a/src/MongODM.Core/Utility/DbContextMigrationManager.cs +++ b/src/MongODM.Core/Utility/DbMigrationManager.cs @@ -9,14 +9,14 @@ namespace Etherna.MongODM.Utility { - public class DbContextMigrationManager : IDbContextMigrationManager, IDbContextInitializable + public class DbMigrationManager : IDbMigrationManager, IDbContextInitializable { // Fields. private IDbContext dbContext = default!; private readonly ITaskRunner taskRunner; // Constructor and initialization. - public DbContextMigrationManager(ITaskRunner taskRunner) + public DbMigrationManager(ITaskRunner taskRunner) { this.taskRunner = taskRunner; } @@ -34,31 +34,31 @@ public void Initialize(IDbContext dbContext) public bool IsInitialized { get; private set; } // Methods. - public async Task> GetLastMigrationsAsync(int page, int take) => + public async Task> GetLastMigrationsAsync(int page, int take) => await dbContext.DbOperations.QueryElementsAsync(elements => - elements.OfType() + elements.OfType() .PaginateDescending(r => r.CreationDateTime, page, take) .ToListAsync()).ConfigureAwait(false); - public async Task GetMigrationAsync(string migrateOperationId) + public async Task GetMigrationAsync(string migrateOperationId) { if (migrateOperationId is null) throw new ArgumentNullException(nameof(migrateOperationId)); var migrateOp = await dbContext.DbOperations.QueryElementsAsync(elements => - elements.OfType() + elements.OfType() .Where(op => op.Id == migrateOperationId) .FirstAsync()).ConfigureAwait(false); return migrateOp; } - public async Task IsMigrationRunningAsync() + public async Task IsMigrationRunningAsync() { var migrateOp = await dbContext.DbOperations.QueryElementsAsync(elements => - elements.OfType() + elements.OfType() .Where(op => op.DbContextName == dbContext.Identifier) - .Where(op => op.CurrentStatus == MigrateOperation.Status.Running) + .Where(op => op.CurrentStatus == DbMigrationOperation.Status.Running) .FirstOrDefaultAsync()).ConfigureAwait(false); return migrateOp; @@ -66,10 +66,10 @@ public async Task GetMigrationAsync(string migrateOperationId) public async Task StartDbContextMigrationAsync(string authorId) { - var migrateOp = new MigrateOperation(dbContext, authorId); + var migrateOp = new DbMigrationOperation(dbContext, authorId); await dbContext.DbOperations.CreateAsync(migrateOp).ConfigureAwait(false); - taskRunner.RunMigrateDbContextTask(dbContext.GetType(), migrateOp.Id); + taskRunner.RunMigrateDbTask(dbContext.GetType(), migrateOp.Id); } } } diff --git a/src/MongODM.Core/Utility/IDbContextCache.cs b/src/MongODM.Core/Utility/IDbCache.cs similarity index 90% rename from src/MongODM.Core/Utility/IDbContextCache.cs rename to src/MongODM.Core/Utility/IDbCache.cs index a1832174..7979c869 100644 --- a/src/MongODM.Core/Utility/IDbContextCache.cs +++ b/src/MongODM.Core/Utility/IDbCache.cs @@ -4,9 +4,9 @@ namespace Etherna.MongODM.Utility { /// - /// Interface for implementation. + /// Interface for implementation. /// - public interface IDbContextCache + public interface IDbCache { // Properties. /// diff --git a/src/MongODM.Core/Utility/IDbContextDependencies.cs b/src/MongODM.Core/Utility/IDbDependencies.cs similarity index 67% rename from src/MongODM.Core/Utility/IDbContextDependencies.cs rename to src/MongODM.Core/Utility/IDbDependencies.cs index f362e8ad..ed4515ff 100644 --- a/src/MongODM.Core/Utility/IDbContextDependencies.cs +++ b/src/MongODM.Core/Utility/IDbDependencies.cs @@ -5,11 +5,11 @@ namespace Etherna.MongODM.Utility { - public interface IDbContextDependencies + public interface IDbDependencies { - IDbContextCache DbContextCache { get; } - IDbContextMaintainer DbContextMaintainer { get; } - IDbContextMigrationManager DbContextMigrationManager { get; } + IDbCache DbCache { get; } + IDbMaintainer DbMaintainer { get; } + IDbMigrationManager DbMigrationManager { get; } IDocumentSchemaRegister DocumentSchemaRegister { get; } IProxyGenerator ProxyGenerator { get; } IRepositoryRegister RepositoryRegister { get; } diff --git a/src/MongODM.Core/Utility/IDbContextMaintainer.cs b/src/MongODM.Core/Utility/IDbMaintainer.cs similarity index 78% rename from src/MongODM.Core/Utility/IDbContextMaintainer.cs rename to src/MongODM.Core/Utility/IDbMaintainer.cs index 770e6449..db9c9da3 100644 --- a/src/MongODM.Core/Utility/IDbContextMaintainer.cs +++ b/src/MongODM.Core/Utility/IDbMaintainer.cs @@ -3,9 +3,9 @@ namespace Etherna.MongODM.Utility { /// - /// Interface for implementation. + /// Interface for implementation. /// - public interface IDbContextMaintainer : IDbContextInitializable + public interface IDbMaintainer : IDbContextInitializable { // Methods. /// diff --git a/src/MongODM.Core/Utility/IDbContextMigrationManager.cs b/src/MongODM.Core/Utility/IDbMigrationManager.cs similarity index 59% rename from src/MongODM.Core/Utility/IDbContextMigrationManager.cs rename to src/MongODM.Core/Utility/IDbMigrationManager.cs index 88b918ca..ef0ae156 100644 --- a/src/MongODM.Core/Utility/IDbContextMigrationManager.cs +++ b/src/MongODM.Core/Utility/IDbMigrationManager.cs @@ -4,13 +4,13 @@ namespace Etherna.MongODM.Utility { - public interface IDbContextMigrationManager + public interface IDbMigrationManager { - Task IsMigrationRunningAsync(); + Task IsMigrationRunningAsync(); - Task> GetLastMigrationsAsync(int page, int take); + Task> GetLastMigrationsAsync(int page, int take); - Task GetMigrationAsync(string migrateOperationId); + Task GetMigrationAsync(string migrateOperationId); /// /// Start a db context migration process. diff --git a/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs b/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs index ef0c059f..28310ef3 100644 --- a/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs +++ b/src/MongODM.Hangfire/Tasks/HangfireTaskRunner.cs @@ -24,9 +24,9 @@ public HangfireTaskRunner( } // Methods. - public void RunMigrateDbContextTask(Type dbContextType, string migrateOpId) => + public void RunMigrateDbTask(Type dbContextType, string dbMigrationOpId) => backgroundJobClient.Enqueue( - task => task.RunAsync(dbContextType, migrateOpId, null!)); + task => task.RunAsync(dbContextType, dbMigrationOpId, null!)); public void RunUpdateDocDependenciesTask(Type dbContextType, Type modelType, Type keyType, IEnumerable idPaths, object modelId) => backgroundJobClient.Enqueue( diff --git a/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs b/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs index 39282ff4..0406975f 100644 --- a/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs +++ b/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs @@ -20,13 +20,13 @@ public MigrateDbContextTaskFacade(IMigrateDbContextTask task) // Methods. [Queue(Queues.DB_MAINTENANCE)] - public Task RunAsync(Type dbContextType, string migrateOpId, PerformingContext context) + public Task RunAsync(Type dbContextType, string dbMigrationOpId, PerformingContext context) { var method = typeof(MigrateDbContextTask).GetMethod( nameof(MigrateDbContextTask.RunAsync), BindingFlags.Public | BindingFlags.Instance) .MakeGenericMethod(dbContextType); - return (Task)method.Invoke(task, new object[] { migrateOpId, context.BackgroundJob.Id }); + return (Task)method.Invoke(task, new object[] { dbMigrationOpId, context.BackgroundJob.Id }); } } } diff --git a/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs b/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs index 7230c977..5e8ab713 100644 --- a/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs +++ b/test/MongODM.Core.Tests/ExtendedClassMapSerializerTest.cs @@ -65,14 +65,14 @@ public SerializationTestElement( } // Fields. - private readonly Mock dbCacheMock; + private readonly Mock dbCacheMock; private readonly SemanticVersion documentVersion = new SemanticVersion("1.0.0"); private readonly Mock serializerModifierAccessorMock; // Constructor. public ExtendedClassMapSerializerTest() { - dbCacheMock = new Mock(); + dbCacheMock = new Mock(); dbCacheMock.Setup(c => c.LoadedModels.ContainsKey(It.IsAny())) .Returns(() => false); From 5e3ab4ea06e7129e9c00951e1551e9857fd0ea99 Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sat, 18 Jul 2020 03:33:09 +0200 Subject: [PATCH 25/27] Fixes --- .../ServiceCollectionExtensions.cs | 1 + src/MongODM.Core/DbContext.cs | 61 +++++++++---------- src/MongODM.Core/IDbContext.cs | 6 ++ .../Migration/MongoCollectionMigration.cs | 9 ++- .../Migration/MongoDocumentMigration.cs | 11 ++-- .../Migration/MongoMigrationBase.cs | 4 +- .../DbMigrationOpAgg/DocumentMigrationLog.cs | 3 + .../DbMigrationOpAgg/IndexMigrationLog.cs | 2 +- .../Models/Internal/DbMigrationOperation.cs | 2 +- .../Internal/ModelMaps/OperationBaseMap.cs | 12 +--- .../Repositories/RepositoryRegister.cs | 6 +- .../Tasks/MigrateDbContextTask.cs | 2 + .../Utility/DbMigrationManager.cs | 2 +- .../Utility/IDbMigrationManager.cs | 2 +- .../Tasks/MigrateDbContextTaskFacade.cs | 2 +- 15 files changed, 67 insertions(+), 58 deletions(-) diff --git a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs index eb7a05fa..89bc8e18 100644 --- a/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/MongODM.AspNetCore/ServiceCollectionExtensions.cs @@ -69,6 +69,7 @@ public static MongODMConfiguration UseMongODM(); //tasks + services.TryAddTransient(); services.TryAddTransient(); //castle proxy generator diff --git a/src/MongODM.Core/DbContext.cs b/src/MongODM.Core/DbContext.cs index 8e484864..4caa8a0a 100644 --- a/src/MongODM.Core/DbContext.cs +++ b/src/MongODM.Core/DbContext.cs @@ -55,8 +55,9 @@ public DbContext( Database = Client.GetDatabase(options.DbName); // Initialize internal dependencies. - DocumentSchemaRegister.Initialize(this); DbMaintainer.Initialize(this); + DbMigrationManager.Initialize(this); + DocumentSchemaRegister.Initialize(this); RepositoryRegister.Initialize(this); // Initialize repositories. @@ -76,9 +77,6 @@ public DbContext( // Build and freeze document schema register. DocumentSchemaRegister.Freeze(); - - // Check for seeding. - SeedIfNeeded(); } // Public properties. @@ -140,45 +138,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).ConfigureAwait(false); + if (RepositoryRegister.ModelCollectionRepositoryMap.ContainsKey(modelType)) + { + var repository = RepositoryRegister.ModelCollectionRepositoryMap[modelType]; + await repository.ReplaceAsync(model).ConfigureAwait(false); + break; + } + else + { + modelType = modelType.BaseType; + } } } } - public Task StartSessionAsync(CancellationToken cancellationToken = default) => - Client.StartSessionAsync(cancellationToken: cancellationToken); - - // Protected methods. - protected virtual Task Seed() => - Task.CompletedTask; - - // Helpers. - private void SeedIfNeeded() + public async Task SeedIfNeededAsync() { // Check if already seeded. - var queryTask = DbOperations.QueryElementsAsync(elements => - elements.OfType().AnyAsync(sop => sop.DbContextName == Identifier)); - queryTask.ConfigureAwait(false); - queryTask.Wait(); - - //skip if already seeded - if (queryTask.Result) - return; + if (await DbOperations.QueryElementsAsync(elements => + elements.OfType() + .AnyAsync(sop => sop.DbContextName == Identifier)).ConfigureAwait(false)) + return false; // Seed. - var seedTask = Seed(); - seedTask.ConfigureAwait(false); - seedTask.Wait(); + await SeedAsync().ConfigureAwait(false); // Report operation. var seedOperation = new SeedOperation(this); - var createTask = DbOperations.CreateAsync(seedOperation); - createTask.ConfigureAwait(false); - createTask.Wait(); + 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/IDbContext.cs b/src/MongODM.Core/IDbContext.cs index 5cbe8ab6..94e752e5 100644 --- a/src/MongODM.Core/IDbContext.cs +++ b/src/MongODM.Core/IDbContext.cs @@ -95,6 +95,12 @@ public interface IDbContext /// Cancellation token Task SaveChangesAsync(CancellationToken cancellationToken = default); + /// + /// Seed database context if still not seeded + /// + /// True if seed has been executed. False otherwise + Task SeedIfNeededAsync(); + /// /// Start a new database transaction session. /// diff --git a/src/MongODM.Core/Migration/MongoCollectionMigration.cs b/src/MongODM.Core/Migration/MongoCollectionMigration.cs index 9b2237d1..81a7976e 100644 --- a/src/MongODM.Core/Migration/MongoCollectionMigration.cs +++ b/src/MongODM.Core/Migration/MongoCollectionMigration.cs @@ -22,7 +22,7 @@ public class MongoCollectionMigration converter; private readonly Func discriminator; private readonly IMongoCollection destinationCollection; - private readonly IMongoCollection sourceCollection; + private readonly ICollectionRepository _sourceCollection; // Constructor. public MongoCollectionMigration( @@ -38,12 +38,15 @@ public MongoCollectionMigration( if (destinationCollection is null) throw new ArgumentNullException(nameof(destinationCollection)); - this.sourceCollection = sourceCollection.Collection; + _sourceCollection = sourceCollection; this.destinationCollection = destinationCollection.Collection; this.converter = converter; this.discriminator = discriminator; } + // Properties. + public override ICollectionRepository SourceCollection => _sourceCollection; + // Methods. public override async Task MigrateAsync( int callbackEveryDocuments = 0, @@ -55,7 +58,7 @@ public override async Task MigrateAsync( // Migrate documents. var totMigratedDocuments = 0L; - await sourceCollection.Find(Builders.Filter.Empty, new FindOptions { NoCursorTimeout = true }) + await _sourceCollection.Collection.Find(Builders.Filter.Empty, new FindOptions { NoCursorTimeout = true }) .ForEachAsync(async model => { if (callbackEveryDocuments > 0 && diff --git a/src/MongODM.Core/Migration/MongoDocumentMigration.cs b/src/MongODM.Core/Migration/MongoDocumentMigration.cs index 59a758e1..f011a2b3 100644 --- a/src/MongODM.Core/Migration/MongoDocumentMigration.cs +++ b/src/MongODM.Core/Migration/MongoDocumentMigration.cs @@ -19,7 +19,7 @@ public class MongoDocumentMigration : MongoMigrationBase { // Fields. private readonly SemanticVersion minimumDocumentVersion; - private readonly IMongoCollection sourceCollection; + private readonly ICollectionRepository _sourceCollection; // Constructors. public MongoDocumentMigration( @@ -31,10 +31,13 @@ public MongoDocumentMigration( if (sourceCollection is null) throw new ArgumentNullException(nameof(sourceCollection)); - this.sourceCollection = sourceCollection.Collection; + _sourceCollection = sourceCollection; this.minimumDocumentVersion = minimumDocumentVersion; } + // Properties. + public override ICollectionRepository SourceCollection => _sourceCollection; + // Methods. public override async Task MigrateAsync( int callbackEveryDocuments = 0, @@ -71,7 +74,7 @@ public override async Task MigrateAsync( // Migrate documents. var totMigratedDocuments = 0L; - await sourceCollection.Find(filter, new FindOptions { NoCursorTimeout = true }) + await _sourceCollection.Collection.Find(filter, new FindOptions { NoCursorTimeout = true }) .ForEachAsync(async model => { if (callbackEveryDocuments > 0 && @@ -79,7 +82,7 @@ public override async Task MigrateAsync( callbackAsync != null) await callbackAsync.Invoke(totMigratedDocuments).ConfigureAwait(false); - await sourceCollection.ReplaceOneAsync(Builders.Filter.Eq(m => m.Id, model.Id), model).ConfigureAwait(false); + await _sourceCollection.Collection.ReplaceOneAsync(Builders.Filter.Eq(m => m.Id, model.Id), model).ConfigureAwait(false); totMigratedDocuments++; }, cancellationToken).ConfigureAwait(false); diff --git a/src/MongODM.Core/Migration/MongoMigrationBase.cs b/src/MongODM.Core/Migration/MongoMigrationBase.cs index 1643880e..7c64aff6 100644 --- a/src/MongODM.Core/Migration/MongoMigrationBase.cs +++ b/src/MongODM.Core/Migration/MongoMigrationBase.cs @@ -1,4 +1,5 @@ -using System; +using Etherna.MongODM.Repositories; +using System; using System.Threading; using System.Threading.Tasks; @@ -14,6 +15,7 @@ public MongoMigrationBase(string id) // Properties. public string Id { get; } + public abstract ICollectionRepository SourceCollection { get; } // Methods. /// diff --git a/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs index 49477d93..a64b36b3 100644 --- a/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs +++ b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/DocumentMigrationLog.cs @@ -6,17 +6,20 @@ 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 index c75d408d..28ccd38b 100644 --- a/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/IndexMigrationLog.cs +++ b/src/MongODM.Core/Models/Internal/DbMigrationOpAgg/IndexMigrationLog.cs @@ -1,6 +1,6 @@ namespace Etherna.MongODM.Models.Internal.DbMigrationOpAgg { - class IndexMigrationLog : MigrationLogBase + public class IndexMigrationLog : MigrationLogBase { // Constructors. public IndexMigrationLog( diff --git a/src/MongODM.Core/Models/Internal/DbMigrationOperation.cs b/src/MongODM.Core/Models/Internal/DbMigrationOperation.cs index 73e15cdb..e268aa87 100644 --- a/src/MongODM.Core/Models/Internal/DbMigrationOperation.cs +++ b/src/MongODM.Core/Models/Internal/DbMigrationOperation.cs @@ -30,7 +30,7 @@ protected DbMigrationOperation() { } // Properties. public virtual string? Author { get; protected set; } - public virtual DateTime CompletedDateTime { get; protected set; } + public virtual DateTime? CompletedDateTime { get; protected set; } public virtual Status CurrentStatus { get; protected set; } public virtual IEnumerable Logs { diff --git a/src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs b/src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs index 6a4be08d..6eab9092 100644 --- a/src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs +++ b/src/MongODM.Core/Models/Internal/ModelMaps/OperationBaseMap.cs @@ -1,8 +1,5 @@ using Etherna.MongODM.Serialization; using Etherna.MongODM.Serialization.Serializers; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.IdGenerators; -using MongoDB.Bson.Serialization.Serializers; namespace Etherna.MongODM.Models.Internal.ModelMaps { @@ -12,14 +9,7 @@ public void Register(IDbContext dbContext) { dbContext.DocumentSchemaRegister.RegisterModelSchema( "0.20.0", //mongodm library's version - cm => - { - cm.AutoMap(); - - // Set Id representation. - cm.IdMemberMap.SetSerializer(new StringSerializer(BsonType.ObjectId)) - .SetIdGenerator(new StringObjectIdGenerator()); - }, + cm => cm.AutoMap(), initCustomSerializer: () => new ExtendedClassMapSerializer( dbContext.DbCache, diff --git a/src/MongODM.Core/Repositories/RepositoryRegister.cs b/src/MongODM.Core/Repositories/RepositoryRegister.cs index ad1dec50..f19d5dc4 100644 --- a/src/MongODM.Core/Repositories/RepositoryRegister.cs +++ b/src/MongODM.Core/Repositories/RepositoryRegister.cs @@ -34,8 +34,8 @@ public IReadOnlyDictionary ModelCollectionRepositor { var dbContextType = dbContext.GetType(); - //select ICollectionRepository<,> implementing properties - var repos = dbContextType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + // Select ICollectionRepository<,> from dbcontext properties. + var repos = dbContextType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy) .Where(prop => { var propType = prop.PropertyType; @@ -53,7 +53,7 @@ public IReadOnlyDictionary ModelCollectionRepositor return false; }); - //construct register + // Initialize register. _modelCollectionRepositoryMap = repos.ToDictionary( prop => ((ICollectionRepository)prop.GetValue(dbContext)).GetModelType, prop => (ICollectionRepository)prop.GetValue(dbContext)); diff --git a/src/MongODM.Core/Tasks/MigrateDbContextTask.cs b/src/MongODM.Core/Tasks/MigrateDbContextTask.cs index 65a64b3c..0c27c2a1 100644 --- a/src/MongODM.Core/Tasks/MigrateDbContextTask.cs +++ b/src/MongODM.Core/Tasks/MigrateDbContextTask.cs @@ -36,6 +36,7 @@ public async Task RunAsync(string dbMigrationOpId, string taskId) async procDocs => { dbMigrationOp.AddLog(new DocumentMigrationLog( + docMigration.SourceCollection.Name, docMigration.Id, MigrationLogBase.ExecutionState.Executing, procDocs)); @@ -45,6 +46,7 @@ public async Task RunAsync(string dbMigrationOpId, string taskId) //ended document migration log dbMigrationOp.AddLog(new DocumentMigrationLog( + docMigration.SourceCollection.Name, docMigration.Id, result.Succeded ? MigrationLogBase.ExecutionState.Succeded : diff --git a/src/MongODM.Core/Utility/DbMigrationManager.cs b/src/MongODM.Core/Utility/DbMigrationManager.cs index 16823581..52db45db 100644 --- a/src/MongODM.Core/Utility/DbMigrationManager.cs +++ b/src/MongODM.Core/Utility/DbMigrationManager.cs @@ -9,7 +9,7 @@ namespace Etherna.MongODM.Utility { - public class DbMigrationManager : IDbMigrationManager, IDbContextInitializable + public class DbMigrationManager : IDbMigrationManager { // Fields. private IDbContext dbContext = default!; diff --git a/src/MongODM.Core/Utility/IDbMigrationManager.cs b/src/MongODM.Core/Utility/IDbMigrationManager.cs index ef0ae156..481992de 100644 --- a/src/MongODM.Core/Utility/IDbMigrationManager.cs +++ b/src/MongODM.Core/Utility/IDbMigrationManager.cs @@ -4,7 +4,7 @@ namespace Etherna.MongODM.Utility { - public interface IDbMigrationManager + public interface IDbMigrationManager : IDbContextInitializable { Task IsMigrationRunningAsync(); diff --git a/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs b/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs index 0406975f..3e9aa0c1 100644 --- a/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs +++ b/src/MongODM.Hangfire/Tasks/MigrateDbContextTaskFacade.cs @@ -20,7 +20,7 @@ public MigrateDbContextTaskFacade(IMigrateDbContextTask task) // Methods. [Queue(Queues.DB_MAINTENANCE)] - public Task RunAsync(Type dbContextType, string dbMigrationOpId, PerformingContext context) + public Task RunAsync(Type dbContextType, string dbMigrationOpId, PerformContext context) { var method = typeof(MigrateDbContextTask).GetMethod( nameof(MigrateDbContextTask.RunAsync), BindingFlags.Public | BindingFlags.Instance) From 5c836a577e856e33df7d75e4bd4ded422418409a Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 19 Jul 2020 01:01:13 +0200 Subject: [PATCH 26/27] First readme created --- MongODM.sln | 1 + README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 README.md diff --git a/MongODM.sln b/MongODM.sln index c0ffd1cb..cfd11552 100644 --- a/MongODM.sln +++ b/MongODM.sln @@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .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}" diff --git a/README.md b/README.md new file mode 100644 index 00000000..68ce26dd --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +MongODM +========= + +## Overview + +MongODM is a **framework ODM** (Object-Documental Mapping) developed for work with **MongoDB** (with its [official drivers](https://github.com/mongodb/mongo-csharp-driver)) and **Asp.NET** applications. + +With MongODM you can concentrate on developing your application, without worring on how data is organized in documents. **Documental databases are very efficient**, but very often using them we have to concern about **how data is structured** at the application layer. MongODM creates a document-to-object mapping layer, solving this kind of problems. + +Infact, the real powerful and different vision of documental DBs against SQL DBs resides into **data denormalization**. With denormalization we can optimize data structure for get all required information with a single document read, without join, but price of this efficiency is often paid with a more difficult to develop application layer. Indeed, often writing applications with documental DBs we can encounter these kind of issues: + +- Related models are connected on our application domain, but different models are saved on different documents. Logical join operations have so to be performed on application layer instead of db layer using different queries +- Denormalization can optimize reading operations, but if requirements evolve, we could have to update the schema of all saved documents. Or even worse, we could have to decorate our application with explicit checks and conditions on data loaded +- To update a document is easy, but to update a document that has been denormalized into others can be really painful. We have to trace where denormalization has gone, and update every dependent document + +For these reasons SQL databases are often still prefered to documentals, because data management from an application perspective is easier, even if documental can be more efficient. Or often documental DBs are used as a simple CRUD storage layer, where save only plain data without make any use of denormalization advantages. + +**MongODM has the scope to solve these problems**, and its objective is to **bring efficiency of denormalized documental data also to complex applications**, without have to worry at all of how data is organized from an application layer! + +Moreover, MongODM uses official MongoDB's drivers for C#, thus inheriting all the powerful features developed for example about description of data serialization, and keeping compatibility with any new relase of MongoDB Server or MongoDB Atlas. + +Here is a non exhaustive list of implemented features from MongODM: + +- Create relation between documents, and fine configuration of serialized properties for document denormalization +- Transparent lazy loading for unloaded properties +- Manage repositories grouped in database contexts +- Automatic denormalization dependent documents update +- Work with asynchronous database maintenance tasks based on [Hangfire](https://www.hangfire.io/), or on your independent system +- Customizable database indexes +- Handle different versioned document schemas on same collections +- Configurable data migration between document versions +- Oriented to Dependency Injection for improve code testability +- Native integration with Asp.NET Core + +**Disclaimer**: MongODM is still in a pre-beta phase, new features are going to be implemented, and current interface is still susceptible to heavy changes. At this stage active use in production is discouraged. + +Package repositories +-------------------- + +You can get latest public releases from Nuget.org feed. Here you can see our [published packages](https://www.nuget.org/profiles/etherna). + +If you'd like to work with latest internal releases, you can use our [custom feed](https://www.myget.org/F/etherna/api/v3/index.json) (NuGet V3) based on MyGet. + +Documentation +------------- + +For specific documentation on how to install and use MongODM, visit our [Wiki](https://github.com/Etherna/mongodm/wiki). + +Issue reports +------------- + +If you've discovered a bug, or have an idea for a new feature, please report it to our issue manager based on Jira https://etherna.atlassian.net/projects/MODM. + +Detailed reports with stack traces, actual and expected behaviours are welcome. + +Questions? Problems? +--------------------- + +For questions or problems please write an email to [info@etherna.io](mailto:info@etherna.io). From ae8da19b06d0adeba3ba4d80afba8f436debd92a Mon Sep 17 00:00:00 2001 From: Mirko Da Corte Date: Sun, 19 Jul 2020 01:04:57 +0200 Subject: [PATCH 27/27] Readme fix --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 68ce26dd..5e9eed82 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## Overview -MongODM is a **framework ODM** (Object-Documental Mapping) developed for work with **MongoDB** (with its [official drivers](https://github.com/mongodb/mongo-csharp-driver)) and **Asp.NET** applications. +MongODM is a **ODM framework** (Object-Documental Mapping) developed for work with **MongoDB** and **Asp.NET** applications. With MongODM you can concentrate on developing your application, without worring on how data is organized in documents. **Documental databases are very efficient**, but very often using them we have to concern about **how data is structured** at the application layer. MongODM creates a document-to-object mapping layer, solving this kind of problems. @@ -17,7 +17,7 @@ For these reasons SQL databases are often still prefered to documentals, because **MongODM has the scope to solve these problems**, and its objective is to **bring efficiency of denormalized documental data also to complex applications**, without have to worry at all of how data is organized from an application layer! -Moreover, MongODM uses official MongoDB's drivers for C#, thus inheriting all the powerful features developed for example about description of data serialization, and keeping compatibility with any new relase of MongoDB Server or MongoDB Atlas. +Moreover, MongODM uses official [MongoDB's drivers for C#](https://github.com/mongodb/mongo-csharp-driver), thus inheriting all the powerful features developed for example about description of data serialization, and keeping compatibility with any new relase of MongoDB Server or MongoDB Atlas. Here is a non exhaustive list of implemented features from MongODM: