From cfecb1f8d9f718735ea2a3cad947840c312625cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 21 Dec 2020 16:31:55 +0300 Subject: [PATCH] Implemented creating dbcontext in async way. --- .../Repositories/IReadOnlyRepository.cs | 9 + .../Abp/Domain/Repositories/RepositoryBase.cs | 20 +++ .../EntityFrameworkCore/EfCoreRepository.cs | 113 ++++++++---- .../EntityFrameworkCore/IEfCoreRepository.cs | 10 +- .../EntityFrameworkCore/IDbContextProvider.cs | 8 +- .../UnitOfWorkDbContextProvider.cs | 104 ++++++++++- .../MemoryDb/MemoryDbRepository.cs | 6 + .../MongoDB/IMongoDbRepository.cs | 14 +- .../Repositories/MongoDB/MongoDbRepository.cs | 161 ++++++++++++------ .../Abp/MongoDB/IMongoDbContextProvider.cs | 13 +- .../UnitOfWorkMongoDbContextProvider.cs | 109 +++++++++++- .../RepositoryRegistration_Tests.cs | 6 + 12 files changed, 477 insertions(+), 96 deletions(-) diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyRepository.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyRepository.cs index 499a1e58be..80d1425044 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyRepository.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyRepository.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Linq.Expressions; +using System.Threading.Tasks; using Volo.Abp.Domain.Entities; using Volo.Abp.Linq; @@ -11,9 +12,17 @@ namespace Volo.Abp.Domain.Repositories { IAsyncQueryableExecuter AsyncExecuter { get; } + [Obsolete("Use WithDetailsAsync method.")] IQueryable WithDetails(); + [Obsolete("Use WithDetailsAsync method.")] IQueryable WithDetails(params Expression>[] propertySelectors); + + Task> WithDetailsAsync(); //TODO: CancellationToken + + Task> WithDetailsAsync(params Expression>[] propertySelectors); //TODO: CancellationToken + + Task> GetQueryableAsync(); //TODO: CancellationToken } public interface IReadOnlyRepository : IReadOnlyRepository, IReadOnlyBasicRepository diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs index fd66b293c9..4ee118fcfe 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs @@ -24,34 +24,54 @@ namespace Volo.Abp.Domain.Repositories public IUnitOfWorkManager UnitOfWorkManager { get; set; } + [Obsolete("This method will be removed in future versions.")] public virtual Type ElementType => GetQueryable().ElementType; + [Obsolete("This method will be removed in future versions.")] public virtual Expression Expression => GetQueryable().Expression; + [Obsolete("This method will be removed in future versions.")] public virtual IQueryProvider Provider => GetQueryable().Provider; + [Obsolete("Use WithDetailsAsync method.")] public virtual IQueryable WithDetails() { return GetQueryable(); } + [Obsolete("Use WithDetailsAsync method.")] public virtual IQueryable WithDetails(params Expression>[] propertySelectors) { return GetQueryable(); } + public virtual Task> WithDetailsAsync() + { + return GetQueryableAsync(); + } + + public virtual Task> WithDetailsAsync(params Expression>[] propertySelectors) + { + return GetQueryableAsync(); + } + + [Obsolete("This method will be removed in future versions.")] IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + [Obsolete("This method will be removed in future versions.")] public IEnumerator GetEnumerator() { return GetQueryable().GetEnumerator(); } + [Obsolete("Use GetQueryableAsync method.")] protected abstract IQueryable GetQueryable(); + public abstract Task> GetQueryableAsync(); + public abstract Task FindAsync( Expression> predicate, bool includeDetails = true, diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index 0d44fd1249..bee421dbe2 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -19,11 +19,34 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore where TDbContext : IEfCoreDbContext where TEntity : class, IEntity { - public virtual DbSet DbSet => DbContext.Set(); + [Obsolete("Use GetDbContextAsync() method.")] + protected virtual TDbContext DbContext => _dbContextProvider.GetDbContext(); + [Obsolete("Use GetDbContextAsync() method.")] DbContext IEfCoreRepository.DbContext => DbContext.As(); - protected virtual TDbContext DbContext => _dbContextProvider.GetDbContext(); + async Task IEfCoreRepository.GetDbContextAsync() + { + return await GetDbContextAsync() as DbContext; + } + + protected virtual Task GetDbContextAsync() + { + return _dbContextProvider.GetDbContextAsync(); + } + + [Obsolete("Use GetDbSetAsync() method.")] + public virtual DbSet DbSet => DbContext.Set(); + + Task> IEfCoreRepository.GetDbSetAsync() + { + return GetDbSetAsync(); + } + + protected async Task> GetDbSetAsync() + { + return (await GetDbContextAsync()).Set(); + } protected virtual AbpEntityOptions AbpEntityOptions => _entityOptionsLazy.Value; @@ -45,64 +68,72 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore ); } - public async override Task InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) + public override async Task InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) { CheckAndSetId(entity); - var savedEntity = DbSet.Add(entity).Entity; + var dbContext = await GetDbContextAsync(); + + var savedEntity = (await dbContext.Set().AddAsync(entity, GetCancellationToken(cancellationToken))).Entity; if (autoSave) { - await DbContext.SaveChangesAsync(GetCancellationToken(cancellationToken)); + await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken)); } return savedEntity; } - public async override Task UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) + public override async Task UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) { - DbContext.Attach(entity); + var dbContext = await GetDbContextAsync(); - var updatedEntity = DbContext.Update(entity).Entity; + dbContext.Attach(entity); + + var updatedEntity = dbContext.Update(entity).Entity; if (autoSave) { - await DbContext.SaveChangesAsync(GetCancellationToken(cancellationToken)); + await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken)); } return updatedEntity; } - public async override Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) + public override async Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) { - DbSet.Remove(entity); + var dbContext = await GetDbContextAsync(); + + dbContext.Set().Remove(entity); if (autoSave) { - await DbContext.SaveChangesAsync(GetCancellationToken(cancellationToken)); + await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken)); } } - public async override Task> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default) + public override async Task> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default) { return includeDetails - ? await WithDetails().ToListAsync(GetCancellationToken(cancellationToken)) - : await DbSet.ToListAsync(GetCancellationToken(cancellationToken)); + ? await (await WithDetailsAsync()).ToListAsync(GetCancellationToken(cancellationToken)) + : await (await GetDbSetAsync()).ToListAsync(GetCancellationToken(cancellationToken)); } - public async override Task GetCountAsync(CancellationToken cancellationToken = default) + public override async Task GetCountAsync(CancellationToken cancellationToken = default) { - return await DbSet.LongCountAsync(GetCancellationToken(cancellationToken)); + return await (await GetDbSetAsync()).LongCountAsync(GetCancellationToken(cancellationToken)); } - public async override Task> GetPagedListAsync( + public override async Task> GetPagedListAsync( int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default) { - var queryable = includeDetails ? WithDetails() : DbSet; + var queryable = includeDetails + ? await WithDetailsAsync() + : await GetDbSetAsync(); return await queryable .OrderBy(sorting) @@ -110,39 +141,48 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore .ToListAsync(GetCancellationToken(cancellationToken)); } + [Obsolete("Use GetQueryableAsync method.")] protected override IQueryable GetQueryable() { return DbSet.AsQueryable(); } - public async override Task FindAsync( + public override async Task> GetQueryableAsync() + { + return (await GetDbSetAsync()).AsQueryable(); + } + + public override async Task FindAsync( Expression> predicate, bool includeDetails = true, CancellationToken cancellationToken = default) { return includeDetails - ? await WithDetails() + ? await (await WithDetailsAsync()) .Where(predicate) .SingleOrDefaultAsync(GetCancellationToken(cancellationToken)) - : await DbSet + : await (await GetDbSetAsync()) .Where(predicate) .SingleOrDefaultAsync(GetCancellationToken(cancellationToken)); } - public async override Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default) + public override async Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default) { - var entities = await GetQueryable() + var dbContext = await GetDbContextAsync(); + var dbSet = dbContext.Set(); + + var entities = await dbSet .Where(predicate) .ToListAsync(GetCancellationToken(cancellationToken)); foreach (var entity in entities) { - DbSet.Remove(entity); + dbSet.Remove(entity); } if (autoSave) { - await DbContext.SaveChangesAsync(GetCancellationToken(cancellationToken)); + await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken)); } } @@ -152,7 +192,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore CancellationToken cancellationToken = default) where TProperty : class { - await DbContext + await (await GetDbContextAsync()) .Entry(entity) .Collection(propertyExpression) .LoadAsync(GetCancellationToken(cancellationToken)); @@ -164,12 +204,13 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore CancellationToken cancellationToken = default) where TProperty : class { - await DbContext + await (await GetDbContextAsync()) .Entry(entity) .Reference(propertyExpression) .LoadAsync(GetCancellationToken(cancellationToken)); } + [Obsolete("Use WithDetailsAsync")] public override IQueryable WithDetails() { if (AbpEntityOptions.DefaultWithDetailsFunc == null) @@ -180,6 +221,17 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return AbpEntityOptions.DefaultWithDetailsFunc(GetQueryable()); } + public override async Task> WithDetailsAsync() + { + if (AbpEntityOptions.DefaultWithDetailsFunc == null) + { + return await base.WithDetailsAsync(); + } + + return AbpEntityOptions.DefaultWithDetailsFunc(await GetQueryableAsync()); + } + + [Obsolete("Use WithDetailsAsync method.")] public override IQueryable WithDetails(params Expression>[] propertySelectors) { var query = GetQueryable(); @@ -195,6 +247,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return query; } + [Obsolete("This method will be deleted in future versions.")] public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { return DbSet.AsAsyncEnumerable().GetAsyncEnumerator(cancellationToken); @@ -251,8 +304,8 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore public virtual async Task FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default) { return includeDetails - ? await WithDetails().FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken)) - : await DbSet.FindAsync(new object[] {id}, GetCancellationToken(cancellationToken)); + ? await (await WithDetailsAsync()).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken)) + : await (await GetDbSetAsync()).FindAsync(new object[] {id}, GetCancellationToken(cancellationToken)); } public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/IEfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/IEfCoreRepository.cs index 31a78f744d..f793dc04ed 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/IEfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/IEfCoreRepository.cs @@ -1,3 +1,5 @@ +using System; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Entities; @@ -6,9 +8,15 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore public interface IEfCoreRepository : IRepository where TEntity : class, IEntity { + [Obsolete("Use GetDbContextAsync() method.")] DbContext DbContext { get; } + [Obsolete("Use GetDbSetAsync() method.")] DbSet DbSet { get; } + + Task GetDbContextAsync(); + + Task> GetDbSetAsync(); } public interface IEfCoreRepository : IEfCoreRepository, IRepository @@ -16,4 +24,4 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore { } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IDbContextProvider.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IDbContextProvider.cs index c4655fddd0..17af400b97 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IDbContextProvider.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IDbContextProvider.cs @@ -1,8 +1,12 @@ +using System.Threading.Tasks; + namespace Volo.Abp.EntityFrameworkCore { - public interface IDbContextProvider + public interface IDbContextProvider where TDbContext : IEfCoreDbContext { TDbContext GetDbContext(); + + Task GetDbContextAsync(); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs index 91ed2f8126..acd414af83 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs @@ -1,6 +1,6 @@ using System; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Data; @@ -47,6 +47,33 @@ namespace Volo.Abp.Uow.EntityFrameworkCore return ((EfCoreDatabaseApi)databaseApi).DbContext; } + public async Task GetDbContextAsync() + { + var unitOfWork = _unitOfWorkManager.Current; + if (unitOfWork == null) + { + throw new AbpException("A DbContext can only be created inside a unit of work!"); + } + + var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(); + var connectionString = _connectionStringResolver.Resolve(connectionStringName); + + var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}"; + + var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey); + + if (databaseApi == null) + { + databaseApi = new EfCoreDatabaseApi( + await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString) + ); + + unitOfWork.AddDatabaseApi(dbContextKey, databaseApi); + } + + return ((EfCoreDatabaseApi)databaseApi).DbContext; + } + private TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) { var creationContext = new DbContextCreationContext(connectionStringName, connectionString); @@ -67,6 +94,26 @@ namespace Volo.Abp.Uow.EntityFrameworkCore } } + private async Task CreateDbContextAsync(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) + { + var creationContext = new DbContextCreationContext(connectionStringName, connectionString); + using (DbContextCreationContext.Use(creationContext)) + { + var dbContext = await CreateDbContextAsync(unitOfWork); + + if (dbContext is IAbpEfCoreDbContext abpEfCoreDbContext) + { + abpEfCoreDbContext.Initialize( + new AbpEfCoreDbContextInitializationContext( + unitOfWork + ) + ); + } + + return dbContext; + } + } + private TDbContext CreateDbContext(IUnitOfWork unitOfWork) { return unitOfWork.Options.IsTransactional @@ -74,7 +121,14 @@ namespace Volo.Abp.Uow.EntityFrameworkCore : unitOfWork.ServiceProvider.GetRequiredService(); } - public TDbContext CreateDbContextWithTransaction(IUnitOfWork unitOfWork) + private async Task CreateDbContextAsync(IUnitOfWork unitOfWork) + { + return unitOfWork.Options.IsTransactional + ? await CreateDbContextWithTransactionAsync(unitOfWork) + : unitOfWork.ServiceProvider.GetRequiredService(); + } + + private TDbContext CreateDbContextWithTransaction(IUnitOfWork unitOfWork) { var transactionApiKey = $"EntityFrameworkCore_{DbContextCreationContext.Current.ConnectionString}"; var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as EfCoreTransactionApi; @@ -117,5 +171,49 @@ namespace Volo.Abp.Uow.EntityFrameworkCore return dbContext; } } + + private async Task CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork) + { + var transactionApiKey = $"EntityFrameworkCore_{DbContextCreationContext.Current.ConnectionString}"; + var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as EfCoreTransactionApi; + + if (activeTransaction == null) + { + var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + + var dbtransaction = unitOfWork.Options.IsolationLevel.HasValue + ? await dbContext.Database.BeginTransactionAsync(unitOfWork.Options.IsolationLevel.Value) + : await dbContext.Database.BeginTransactionAsync(); + + unitOfWork.AddTransactionApi( + transactionApiKey, + new EfCoreTransactionApi( + dbtransaction, + dbContext + ) + ); + + return dbContext; + } + else + { + DbContextCreationContext.Current.ExistingConnection = activeTransaction.DbContextTransaction.GetDbTransaction().Connection; + + var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + + if (dbContext.As().HasRelationalTransactionManager()) + { + await dbContext.Database.UseTransactionAsync(activeTransaction.DbContextTransaction.GetDbTransaction()); + } + else + { + await dbContext.Database.BeginTransactionAsync(); //TODO: Why not using the new created transaction? + } + + activeTransaction.AttendedDbContexts.Add(dbContext); + + return dbContext; + } + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs index bb77149a29..17b3f09adc 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs @@ -46,11 +46,17 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb EntityChangeEventHelper = NullEntityChangeEventHelper.Instance; } + [Obsolete("This method will be removed in future versions.")] protected override IQueryable GetQueryable() { return ApplyDataFilters(Collection.AsQueryable()); } + public override Task> GetQueryableAsync() + { + return Task.FromResult(ApplyDataFilters(Collection.AsQueryable())); + } + protected virtual async Task TriggerDomainEventsAsync(object entity) { var generatesDomainEventsEntity = entity as IGeneratesDomainEvents; diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs index 50155c6df5..960222679f 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs @@ -1,4 +1,7 @@ -using MongoDB.Driver; +using System; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Entities; @@ -7,11 +10,20 @@ namespace Volo.Abp.Domain.Repositories.MongoDB public interface IMongoDbRepository : IRepository where TEntity : class, IEntity { + [Obsolete("Use GetDatabaseAsync method.")] IMongoDatabase Database { get; } + Task GetDatabaseAsync(CancellationToken cancellationToken = default); + + [Obsolete("Use GetCollectionAsync method.")] IMongoCollection Collection { get; } + Task> GetCollectionAsync(CancellationToken cancellationToken = default); + + [Obsolete("Use GetMongoQueryableAsync method.")] IMongoQueryable GetMongoQueryable(); + + Task> GetMongoQueryableAsync(CancellationToken cancellationToken = default); } public interface IMongoDbRepository : IMongoDbRepository, IRepository diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 6af98da56b..5f1025f77d 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -25,13 +25,37 @@ namespace Volo.Abp.Domain.Repositories.MongoDB where TMongoDbContext : IAbpMongoDbContext where TEntity : class, IEntity { + [Obsolete("Use GetCollectionAsync method.")] public virtual IMongoCollection Collection => DbContext.Collection(); + public async Task> GetCollectionAsync(CancellationToken cancellationToken = default) + { + return (await GetDbContextAsync(GetCancellationToken(cancellationToken))).Collection(); + } + + [Obsolete("Use GetDatabaseAsync method.")] public virtual IMongoDatabase Database => DbContext.Database; - public virtual IClientSessionHandle SessionHandle => DbContext.SessionHandle; + public async Task GetDatabaseAsync(CancellationToken cancellationToken = default) + { + return (await GetDbContextAsync(GetCancellationToken(cancellationToken))).Database; + } + + [Obsolete("Use GetSessionHandleAsync method.")] + protected virtual IClientSessionHandle SessionHandle => DbContext.SessionHandle; + + protected async Task GetSessionHandleAsync(CancellationToken cancellationToken = default) + { + return (await GetDbContextAsync(GetCancellationToken(cancellationToken))).SessionHandle; + } + + [Obsolete("Use GetDbContextAsync method.")] + protected virtual TMongoDbContext DbContext => DbContextProvider.GetDbContext(); - public virtual TMongoDbContext DbContext => DbContextProvider.GetDbContext(); + protected Task GetDbContextAsync(CancellationToken cancellationToken = default) + { + return DbContextProvider.GetDbContextAsync(GetCancellationToken(cancellationToken)); + } protected IMongoDbContextProvider DbContextProvider { get; } @@ -55,24 +79,27 @@ namespace Volo.Abp.Domain.Repositories.MongoDB GuidGenerator = SimpleGuidGenerator.Instance; } - public async override Task InsertAsync( + public override async Task InsertAsync( TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) { await ApplyAbpConceptsForAddedEntityAsync(entity); - if (SessionHandle != null) + var dbContext = await GetDbContextAsync(GetCancellationToken(cancellationToken)); + var collection = dbContext.Collection(); + + if (dbContext.SessionHandle != null) { - await Collection.InsertOneAsync( - SessionHandle, + await collection.InsertOneAsync( + dbContext.SessionHandle, entity, cancellationToken: GetCancellationToken(cancellationToken) ); } else { - await Collection.InsertOneAsync( + await collection.InsertOneAsync( entity, cancellationToken: GetCancellationToken(cancellationToken) ); @@ -81,7 +108,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB return entity; } - public async override Task UpdateAsync( + public override async Task UpdateAsync( TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) @@ -103,20 +130,21 @@ namespace Volo.Abp.Domain.Repositories.MongoDB var oldConcurrencyStamp = SetNewConcurrencyStamp(entity); ReplaceOneResult result; - if (SessionHandle != null) + var dbContext = await GetDbContextAsync(GetCancellationToken(cancellationToken)); + var collection = dbContext.Collection(); + + if (dbContext.SessionHandle != null) { - result = await Collection.ReplaceOneAsync( - SessionHandle, + result = await collection.ReplaceOneAsync( + dbContext.SessionHandle, CreateEntityFilter(entity, true, oldConcurrencyStamp), entity, cancellationToken: GetCancellationToken(cancellationToken) ); - - } else { - result = await Collection.ReplaceOneAsync( + result = await collection.ReplaceOneAsync( CreateEntityFilter(entity, true, oldConcurrencyStamp), entity, cancellationToken: GetCancellationToken(cancellationToken) @@ -131,7 +159,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB return entity; } - public async override Task DeleteAsync( + public override async Task DeleteAsync( TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) @@ -139,15 +167,18 @@ namespace Volo.Abp.Domain.Repositories.MongoDB await ApplyAbpConceptsForDeletedEntityAsync(entity); var oldConcurrencyStamp = SetNewConcurrencyStamp(entity); + var dbContext = await GetDbContextAsync(GetCancellationToken(cancellationToken)); + var collection = dbContext.Collection(); + if (entity is ISoftDelete softDeleteEntity && !IsHardDeleted(entity)) { softDeleteEntity.IsDeleted = true; ReplaceOneResult result; - if (SessionHandle != null) + if (dbContext.SessionHandle != null) { - result = await Collection.ReplaceOneAsync( - SessionHandle, + result = await collection.ReplaceOneAsync( + dbContext.SessionHandle, CreateEntityFilter(entity, true, oldConcurrencyStamp), entity, cancellationToken: GetCancellationToken(cancellationToken) @@ -155,7 +186,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB } else { - result = await Collection.ReplaceOneAsync( + result = await collection.ReplaceOneAsync( CreateEntityFilter(entity, true, oldConcurrencyStamp), entity, cancellationToken: GetCancellationToken(cancellationToken) @@ -171,17 +202,17 @@ namespace Volo.Abp.Domain.Repositories.MongoDB { DeleteResult result; - if (SessionHandle != null) + if (dbContext.SessionHandle != null) { - result = await Collection.DeleteOneAsync( - SessionHandle, + result = await collection.DeleteOneAsync( + dbContext.SessionHandle, CreateEntityFilter(entity, true, oldConcurrencyStamp), cancellationToken: GetCancellationToken(cancellationToken) ); } else { - result = await Collection.DeleteOneAsync( + result = await collection.DeleteOneAsync( CreateEntityFilter(entity, true, oldConcurrencyStamp), GetCancellationToken(cancellationToken) ); @@ -194,38 +225,44 @@ namespace Volo.Abp.Domain.Repositories.MongoDB } } - public async override Task> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default) + public override async Task> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default) { - return await GetMongoQueryable().ToListAsync(GetCancellationToken(cancellationToken)); + cancellationToken = GetCancellationToken(cancellationToken); + return await (await GetMongoQueryableAsync(cancellationToken)).ToListAsync(cancellationToken); } - public async override Task GetCountAsync(CancellationToken cancellationToken = default) + public override async Task GetCountAsync(CancellationToken cancellationToken = default) { - return await GetMongoQueryable().LongCountAsync(GetCancellationToken(cancellationToken)); + cancellationToken = GetCancellationToken(cancellationToken); + return await (await GetMongoQueryableAsync(cancellationToken)).LongCountAsync(cancellationToken); } - public async override Task> GetPagedListAsync( + public override async Task> GetPagedListAsync( int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await GetMongoQueryable() + cancellationToken = GetCancellationToken(cancellationToken); + + return await (await GetMongoQueryableAsync(cancellationToken)) .OrderBy(sorting) .As>() .PageBy>(skipCount, maxResultCount) - .ToListAsync(GetCancellationToken(cancellationToken)); + .ToListAsync(cancellationToken); } - public async override Task DeleteAsync( + public override async Task DeleteAsync( Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default) { - var entities = await GetMongoQueryable() + cancellationToken = GetCancellationToken(cancellationToken); + + var entities = await (await GetMongoQueryableAsync(cancellationToken)) .Where(predicate) - .ToListAsync(GetCancellationToken(cancellationToken)); + .ToListAsync(cancellationToken); foreach (var entity in entities) { @@ -233,25 +270,45 @@ namespace Volo.Abp.Domain.Repositories.MongoDB } } + [Obsolete("Use GetQueryableAsync method.")] protected override IQueryable GetQueryable() { return GetMongoQueryable(); } - public async override Task FindAsync( + public override async Task> GetQueryableAsync() + { + return await GetMongoQueryableAsync(); + } + + public override async Task FindAsync( Expression> predicate, bool includeDetails = true, CancellationToken cancellationToken = default) { - return await GetMongoQueryable() + return await (await GetMongoQueryableAsync(cancellationToken)) .Where(predicate) .SingleOrDefaultAsync(GetCancellationToken(cancellationToken)); } + [Obsolete("Use GetMongoQueryableAsync method.")] public virtual IMongoQueryable GetMongoQueryable() { return ApplyDataFilters(SessionHandle != null ? Collection.AsQueryable(SessionHandle) : Collection.AsQueryable()); } + + public async Task> GetMongoQueryableAsync(CancellationToken cancellationToken = default) + { + var dbContext = await GetDbContextAsync(cancellationToken); + var collection = dbContext.Collection(); + + return ApplyDataFilters( + dbContext.SessionHandle != null + ? collection.AsQueryable(dbContext.SessionHandle) + : collection.AsQueryable() + ); + } + protected virtual bool IsHardDeleted(TEntity entity) { var hardDeletedEntities = UnitOfWorkManager?.Current?.Items.GetOrDefault(UnitOfWorkItemNames.HardDeletedEntities) as HashSet; @@ -393,30 +450,19 @@ namespace Volo.Abp.Domain.Repositories.MongoDB throw new AbpDbConcurrencyException("Database operation expected to affect 1 row but actually affected 0 row. Data may have been modified or deleted since entities were loaded. This exception has been thrown on optimistic concurrency check."); } - /// - /// IMongoQueryable - /// - /// + [Obsolete("This method will be removed in future versions.")] public QueryableExecutionModel GetExecutionModel() { return GetMongoQueryable().GetExecutionModel(); } - /// - /// IMongoQueryable - /// - /// - /// + [Obsolete("This method will be removed in future versions.")] public IAsyncCursor ToCursor(CancellationToken cancellationToken = new CancellationToken()) { return GetMongoQueryable().ToCursor(cancellationToken); } - /// - /// IMongoQueryable - /// - /// - /// + [Obsolete("This method will be removed in future versions.")] public Task> ToCursorAsync(CancellationToken cancellationToken = new CancellationToken()) { return GetMongoQueryable().ToCursorAsync(cancellationToken); @@ -457,16 +503,21 @@ namespace Volo.Abp.Domain.Repositories.MongoDB bool includeDetails = true, CancellationToken cancellationToken = default) { - if (SessionHandle != null) + cancellationToken = GetCancellationToken(cancellationToken); + + var dbContext = await GetDbContextAsync(cancellationToken); + var collection = dbContext.Collection(); + + if (dbContext.SessionHandle != null) { - return await Collection - .Find(SessionHandle, RepositoryFilterer.CreateEntityFilter(id, true)) - .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + return await collection + .Find(dbContext.SessionHandle, RepositoryFilterer.CreateEntityFilter(id, true)) + .FirstOrDefaultAsync(cancellationToken); } - return await Collection + return await collection .Find(RepositoryFilterer.CreateEntityFilter(id, true)) - .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + .FirstOrDefaultAsync(cancellationToken); } public virtual Task DeleteAsync( diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/IMongoDbContextProvider.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/IMongoDbContextProvider.cs index 9f89054dcc..959cb39df1 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/IMongoDbContextProvider.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/IMongoDbContextProvider.cs @@ -1,8 +1,15 @@ -namespace Volo.Abp.MongoDB +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Abp.MongoDB { - public interface IMongoDbContextProvider + public interface IMongoDbContextProvider where TMongoDbContext : IAbpMongoDbContext { + [Obsolete("Use CreateDbContextAsync")] TMongoDbContext GetDbContext(); + + Task GetDbContextAsync(CancellationToken cancellationToken = default); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs index d3d8a419fb..8a9ecd5de6 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using MongoDB.Bson; using MongoDB.Driver; @@ -21,6 +23,7 @@ namespace Volo.Abp.Uow.MongoDB _connectionStringResolver = connectionStringResolver; } + [Obsolete("Use CreateDbContextAsync")] public TMongoDbContext GetDbContext() { var unitOfWork = _unitOfWorkManager.Current; @@ -48,6 +51,46 @@ namespace Volo.Abp.Uow.MongoDB return ((MongoDbDatabaseApi) databaseApi).DbContext; } + public async Task GetDbContextAsync(CancellationToken cancellationToken = default) + { + var unitOfWork = _unitOfWorkManager.Current; + if (unitOfWork == null) + { + throw new AbpException( + $"A {nameof(IMongoDatabase)} instance can only be created inside a unit of work!"); + } + + var connectionString = _connectionStringResolver.Resolve(); + var dbContextKey = $"{typeof(TMongoDbContext).FullName}_{connectionString}"; + + var mongoUrl = new MongoUrl(connectionString); + var databaseName = mongoUrl.DatabaseName; + if (databaseName.IsNullOrWhiteSpace()) + { + databaseName = ConnectionStringNameAttribute.GetConnStringName(); + } + + //TODO: Create only single MongoDbClient per connection string in an application (extract MongoClientCache for example). + var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey); + if (databaseApi == null) + { + databaseApi = new MongoDbDatabaseApi( + await CreateDbContextAsync( + unitOfWork, + mongoUrl, + databaseName, + cancellationToken + ) + ); + + unitOfWork.AddDatabaseApi(dbContextKey, databaseApi); + } + + return ((MongoDbDatabaseApi) databaseApi).DbContext; + } + + [Obsolete("Use CreateDbContextAsync")] + private TMongoDbContext CreateDbContext(IUnitOfWork unitOfWork, MongoUrl mongoUrl, string databaseName) { var client = new MongoClient(mongoUrl); @@ -64,7 +107,34 @@ namespace Volo.Abp.Uow.MongoDB return dbContext; } - public TMongoDbContext CreateDbContextWithTransaction( + private async Task CreateDbContextAsync( + IUnitOfWork unitOfWork, + MongoUrl mongoUrl, + string databaseName, + CancellationToken cancellationToken = default) + { + var client = new MongoClient(mongoUrl); + var database = client.GetDatabase(databaseName); + + if (unitOfWork.Options.IsTransactional) + { + return await CreateDbContextWithTransactionAsync( + unitOfWork, + mongoUrl, + client, + database, + cancellationToken + ); + } + + var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + dbContext.ToAbpMongoDbContext().InitializeDatabase(database, client, null); + + return dbContext; + } + + [Obsolete("Use CreateDbContextWithTransactionAsync")] + private TMongoDbContext CreateDbContextWithTransaction( IUnitOfWork unitOfWork, MongoUrl url, MongoClient client, @@ -99,5 +169,42 @@ namespace Volo.Abp.Uow.MongoDB return dbContext; } + + private async Task CreateDbContextWithTransactionAsync( + IUnitOfWork unitOfWork, + MongoUrl url, + MongoClient client, + IMongoDatabase database, + CancellationToken cancellationToken = default) + { + var transactionApiKey = $"MongoDb_{url}"; + var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as MongoDbTransactionApi; + var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + + if (activeTransaction?.SessionHandle == null) + { + var session = await client.StartSessionAsync(cancellationToken: cancellationToken); + + if (unitOfWork.Options.Timeout.HasValue) + { + session.AdvanceOperationTime(new BsonTimestamp(unitOfWork.Options.Timeout.Value)); + } + + session.StartTransaction(); + + unitOfWork.AddTransactionApi( + transactionApiKey, + new MongoDbTransactionApi(session) + ); + + dbContext.ToAbpMongoDbContext().InitializeDatabase(database, client, session); + } + else + { + dbContext.ToAbpMongoDbContext().InitializeDatabase(database, client, activeTransaction.SessionHandle); + } + + return dbContext; + } } } diff --git a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs index 57b8054784..3acd59a2b6 100644 --- a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs +++ b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs @@ -241,11 +241,17 @@ namespace Volo.Abp.Domain.Repositories where TEntity : class, IEntity { + [Obsolete("Use GetQueryableAsync method.")] protected override IQueryable GetQueryable() { throw new NotImplementedException(); } + public override Task> GetQueryableAsync() + { + throw new NotImplementedException(); + } + public override Task FindAsync(Expression> predicate, bool includeDetails = true, CancellationToken cancellationToken = default) { throw new NotImplementedException();