Browse Source

Merge pull request #6676 from abpframework/repo-bulk-operations

Bulk Operations
pull/6788/head
Halil İbrahim Kalkan 5 years ago
committed by GitHub
parent
commit
9329af8e46
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 41
      docs/en/Entity-Framework-Core.md
  2. 41
      docs/en/MongoDB.md
  3. 5
      docs/en/Repositories.md
  4. 76
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs
  5. 52
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IBasicRepository.cs
  6. 26
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs
  7. 95
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
  8. 40
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/IEfCoreBulkOperationProvider.cs
  9. 7
      framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs
  10. 39
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbBulkOperationProvider.cs
  11. 21
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepositoryFilterer.cs
  12. 173
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs
  13. 27
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepositoryFilterer.cs
  14. 6
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs
  15. 69
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs

41
docs/en/Entity-Framework-Core.md

@ -735,6 +735,47 @@ Configure<AbpDbContextOptions>(options =>
});
````
### Customize Bulk Operations
If you have better logic or using an external library for bulk operations, you can override the logic via implementing`IEfCoreBulkOperationProvider`.
- You may use example template below:
```csharp
public class MyCustomEfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency
{
public async Task DeleteManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task InsertManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task UpdateManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
// Your logic here.
}
}
```
## See Also
* [Entities](Entities.md)

41
docs/en/MongoDB.md

@ -382,3 +382,44 @@ context.Services.AddMongoDbContext<OtherMongoDbContext>(options =>
```
In this example, `OtherMongoDbContext` implements `IBookStoreMongoDbContext`. This feature allows you to have multiple MongoDbContext (one per module) on development, but single MongoDbContext (implements all interfaces of all MongoDbContexts) on runtime.
### Customize Bulk Operations
If you have better logic or using an external library for bulk operations, you can override the logic via implementing `IMongoDbBulkOperationProvider`.
- You may use example template below:
```csharp
public class MyCustomMongoDbBulkOperationProvider : IMongoDbBulkOperationProvider, ITransientDependency
{
public async Task DeleteManyAsync<TEntity>(IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task InsertManyAsync<TEntity>(IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task UpdateManyAsync<TEntity>(IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// Your logic here.
}
}
```

5
docs/en/Repositories.md

@ -87,6 +87,11 @@ If your entity is a soft-delete entity, you can use the `HardDeleteAsync` method
See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete.
## Bulk Operations
You can execute bulk operations with `InsertManyAsync`, `UpdateManyAsync`, `DeleteManyAsync` methods.
> **WARNING:** ConcurrencyStamp can't be checked at bulk operations!
## Custom Repositories
Default generic repositories will be sufficient for most cases. However, you may need to create a custom repository class for your entity.

76
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs

@ -1,9 +1,13 @@
using System;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
@ -17,6 +21,14 @@ namespace Volo.Abp.Domain.Repositories
{
public IServiceProvider ServiceProvider { get; set; }
public IDataFilter DataFilter { get; set; }
public ICurrentTenant CurrentTenant { get; set; }
public IAsyncQueryableExecuter AsyncExecuter { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public ICancellationTokenProvider CancellationTokenProvider { get; set; }
protected BasicRepositoryBase()
@ -26,10 +38,59 @@ namespace Volo.Abp.Domain.Repositories
public abstract Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
public virtual async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await InsertAsync(entity, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
protected virtual Task SaveChangesAsync(CancellationToken cancellationToken)
{
if (UnitOfWorkManager?.Current != null)
{
return UnitOfWorkManager.Current.SaveChangesAsync(cancellationToken);
}
return Task.CompletedTask;
}
public abstract Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
public virtual async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await UpdateAsync(entity, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
public abstract Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
public virtual async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await DeleteAsync(entity, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
public abstract Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default);
public abstract Task<long> GetCountAsync(CancellationToken cancellationToken = default);
@ -69,5 +130,18 @@ namespace Volo.Abp.Domain.Repositories
await DeleteAsync(entity, autoSave, cancellationToken);
}
public async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var id in ids)
{
await DeleteAsync(id, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
}
}

52
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IBasicRepository.cs

@ -1,4 +1,5 @@
using System.Threading;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Domain.Entities;
@ -21,7 +22,19 @@ namespace Volo.Abp.Domain.Repositories
Task<TEntity> InsertAsync([NotNull] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Updates an existing entity.
/// Inserts multiple new entities.
/// </summary>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <param name="entities">Entities to be inserted.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task InsertManyAsync([NotNull] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Updates an existing entity.
/// </summary>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
@ -32,6 +45,17 @@ namespace Volo.Abp.Domain.Repositories
[NotNull]
Task<TEntity> UpdateAsync([NotNull] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Updates multiple entities.
/// </summary>
/// <param name="entities">Entities to be updated.</param>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.</param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task UpdateManyAsync([NotNull] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes an entity.
/// </summary>
@ -42,6 +66,18 @@ namespace Volo.Abp.Domain.Repositories
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
Task DeleteAsync([NotNull] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes multiple entities.
/// </summary>
/// <param name="entities">Entities to be deleted.</param>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task DeleteManyAsync([NotNull] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
}
public interface IBasicRepository<TEntity, TKey> : IBasicRepository<TEntity>, IReadOnlyBasicRepository<TEntity, TKey>
@ -57,5 +93,17 @@ namespace Volo.Abp.Domain.Repositories
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default); //TODO: Return true if deleted
/// <summary>
/// Deletes multiple entities by primary keys.
/// </summary>
/// <param name="ids">Primary keys of the each entity.</param>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default);
}
}

26
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs

@ -1,4 +1,5 @@
using System;
using JetBrains.Annotations;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -16,14 +17,6 @@ namespace Volo.Abp.Domain.Repositories
public abstract class RepositoryBase<TEntity> : BasicRepositoryBase<TEntity>, IRepository<TEntity>, IUnitOfWorkManagerAccessor
where TEntity : class, IEntity
{
public IDataFilter DataFilter { get; set; }
public ICurrentTenant CurrentTenant { get; set; }
public IAsyncQueryableExecuter AsyncExecuter { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public virtual Type ElementType => GetQueryable().ElementType;
public virtual Expression Expression => GetQueryable().Expression;
@ -109,5 +102,20 @@ namespace Volo.Abp.Domain.Repositories
await DeleteAsync(entity, autoSave, cancellationToken);
}
public async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var id in ids)
{
await DeleteAsync(id, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
}
}

95
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs

@ -1,13 +1,15 @@
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
@ -32,6 +34,8 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
public virtual IGuidGenerator GuidGenerator { get; set; }
public IEfCoreBulkOperationProvider BulkOperationProvider { get; set; }
public EfCoreRepository(IDbContextProvider<TDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
@ -59,6 +63,32 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
return savedEntity;
}
public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
CheckAndSetId(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.InsertManyAsync<TDbContext, TEntity>(
this,
entities,
autoSave,
cancellationToken
);
return;
}
await DbSet.AddRangeAsync(entities);
if (autoSave)
{
await DbContext.SaveChangesAsync();
}
}
public async override Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
DbContext.Attach(entity);
@ -73,6 +103,28 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
return updatedEntity;
}
public override async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
if (BulkOperationProvider != null)
{
await BulkOperationProvider.UpdateManyAsync<TDbContext, TEntity>(
this,
entities,
autoSave,
cancellationToken
);
return;
}
DbSet.UpdateRange(entities);
if (autoSave)
{
await DbContext.SaveChangesAsync();
}
}
public async override Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
DbSet.Remove(entity);
@ -83,6 +135,27 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
}
}
public override async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
if (BulkOperationProvider != null)
{
await BulkOperationProvider.DeleteManyAsync<TDbContext, TEntity>(
this,
entities,
autoSave,
cancellationToken);
return;
}
DbSet.RemoveRange(entities);
if (autoSave)
{
await DbContext.SaveChangesAsync();
}
}
public async override Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
{
return includeDetails
@ -115,6 +188,11 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
return DbSet.AsQueryable();
}
protected override Task SaveChangesAsync(CancellationToken cancellationToken)
{
return DbContext.SaveChangesAsync(cancellationToken);
}
public async override Task<TEntity> FindAsync(
Expression<Func<TEntity, bool>> predicate,
bool includeDetails = true,
@ -252,7 +330,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
{
return includeDetails
? await WithDetails().FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken))
: await DbSet.FindAsync(new object[] {id}, GetCancellationToken(cancellationToken));
: await DbSet.FindAsync(new object[] { id }, GetCancellationToken(cancellationToken));
}
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
@ -265,5 +343,12 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
await DeleteAsync(entity, autoSave, cancellationToken);
}
public async virtual Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
var entities = await DbSet.Where(x => ids.Contains(x.Id)).ToListAsync();
await DeleteManyAsync(entities, autoSave, cancellationToken);
}
}
}

40
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/IEfCoreBulkOperationProvider.cs

@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
{
public interface IEfCoreBulkOperationProvider
{
Task InsertManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken
)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity;
Task UpdateManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken
)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity;
Task DeleteManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken
)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity;
}
}

7
framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs

@ -1,3 +1,4 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
@ -309,5 +310,11 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
{
await DeleteAsync(x => x.Id.Equals(id), autoSave, cancellationToken);
}
public virtual async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
var entities = await AsyncExecuter.ToListAsync(GetQueryable().Where(x => ids.Contains(x.Id)));
DeleteManyAsync(entities, autoSave, cancellationToken);
}
}
}

39
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbBulkOperationProvider.cs

@ -0,0 +1,39 @@
using MongoDB.Driver;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.MongoDB;
namespace Volo.Abp.MongoDB.Volo.Abp.Domain.Repositories.MongoDB
{
public interface IMongoDbBulkOperationProvider
{
Task InsertManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken
)
where TEntity : class, IEntity;
Task UpdateManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken
)
where TEntity : class, IEntity;
Task DeleteManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken
)
where TEntity : class, IEntity;
}
}

21
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepositoryFilterer.cs

@ -14,5 +14,26 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
FilterDefinition<TEntity> CreateEntityFilter(TKey id, bool applyFilters = false);
FilterDefinition<TEntity> CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null);
/// <summary>
/// Creates filter for given entities.
/// </summary>
/// <remarks>
/// Visit https://docs.mongodb.com/manual/reference/operator/query/in/ to get more information about 'in' operator.
/// </remarks>
/// <param name="entities">Entities to be filtered.</param>
/// <param name="applyFilters">Set true to use GlobalFilters. Default is false.</param>
/// <returns>Created <see cref="FilterDefinition{TDocument}"/>.</returns>
FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool applyFilters = false);
/// <summary>
/// Creates filter for given ids.
/// </summary>
/// <remarks>
/// Visit https://docs.mongodb.com/manual/reference/operator/query/in/ to get more information about 'in' operator.
/// </remarks>
/// <param name="ids">Entity Ids to be filtered.</param>
/// <param name="applyFilters">Set true to use GlobalFilters. Default is false.</param>
FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TKey> ids, bool applyFilters = false);
}
}

173
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs

@ -1,3 +1,4 @@
using JetBrains.Annotations;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using System;
@ -15,6 +16,7 @@ using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Guids;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.Volo.Abp.Domain.Repositories.MongoDB;
namespace Volo.Abp.Domain.Repositories.MongoDB
{
@ -45,6 +47,8 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
public IAuditPropertySetter AuditPropertySetter { get; set; }
public IMongoDbBulkOperationProvider BulkOperationProvider { get; set; }
public MongoDbRepository(IMongoDbContextProvider<TMongoDbContext> dbContextProvider)
{
DbContextProvider = dbContextProvider;
@ -81,6 +85,34 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return entity;
}
public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await ApplyAbpConceptsForAddedEntityAsync(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.InsertManyAsync(this, entities, SessionHandle, autoSave, cancellationToken);
return;
}
if (SessionHandle != null)
{
await Collection.InsertManyAsync(
SessionHandle,
entities,
cancellationToken: cancellationToken);
}
else
{
await Collection.InsertManyAsync(
entities,
cancellationToken: cancellationToken);
}
}
public async override Task<TEntity> UpdateAsync(
TEntity entity,
bool autoSave = false,
@ -131,6 +163,59 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return entity;
}
public override async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
var isSoftDeleteEntity = typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity));
foreach (var entity in entities)
{
SetModificationAuditProperties(entity);
if (isSoftDeleteEntity)
{
SetDeletionAuditProperties(entity);
await TriggerEntityDeleteEventsAsync(entity);
}
else
{
await TriggerEntityUpdateEventsAsync(entity);
}
await TriggerDomainEventsAsync(entity);
SetNewConcurrencyStamp(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.UpdateManyAsync(this, entities, SessionHandle, autoSave, cancellationToken);
return;
}
var entitiesCount = entities.Count();
BulkWriteResult result;
List<WriteModel<TEntity>> replaceRequests = new List<WriteModel<TEntity>>();
foreach (var entity in entities)
{
replaceRequests.Add(new ReplaceOneModel<TEntity>(CreateEntityFilter(entity), entity));
}
if (SessionHandle != null)
{
result = await Collection.BulkWriteAsync(SessionHandle, replaceRequests);
}
else
{
result = await Collection.BulkWriteAsync(replaceRequests);
}
if (result.MatchedCount < entitiesCount)
{
ThrowOptimisticConcurrencyException();
}
}
public async override Task DeleteAsync(
TEntity entity,
bool autoSave = false,
@ -194,6 +279,73 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
}
}
public override async Task DeleteManyAsync(
IEnumerable<TEntity> entities,
bool autoSave = false,
CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await ApplyAbpConceptsForDeletedEntityAsync(entity);
var oldConcurrencyStamp = SetNewConcurrencyStamp(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.DeleteManyAsync(this, entities, SessionHandle, autoSave, cancellationToken);
return;
}
var entitiesCount = entities.Count();
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
UpdateResult updateResult;
if (SessionHandle != null)
{
updateResult = await Collection.UpdateManyAsync(
SessionHandle,
CreateEntitiesFilter(entities),
Builders<TEntity>.Update.Set(x => ((ISoftDelete)x).IsDeleted, true)
);
}
else
{
updateResult = await Collection.UpdateManyAsync(
CreateEntitiesFilter(entities),
Builders<TEntity>.Update.Set(x => ((ISoftDelete)x).IsDeleted, true)
);
}
if (updateResult.MatchedCount < entitiesCount)
{
ThrowOptimisticConcurrencyException();
}
}
else
{
DeleteResult deleteResult;
if (SessionHandle != null)
{
deleteResult = await Collection.DeleteManyAsync(
SessionHandle,
CreateEntitiesFilter(entities)
);
}
else
{
deleteResult = await Collection.DeleteManyAsync(
CreateEntitiesFilter(entities)
);
}
if (deleteResult.DeletedCount < entitiesCount)
{
ThrowOptimisticConcurrencyException();
}
}
}
public async override Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
{
return await GetMongoQueryable().ToListAsync(GetCancellationToken(cancellationToken));
@ -270,6 +422,13 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
);
}
protected virtual FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool withConcurrencyStamp = false)
{
throw new NotImplementedException(
$"{nameof(CreateEntitiesFilter)} is not implemented for MongoDB by default. It should be overriden and implemented by the deriving class!"
);
}
protected virtual async Task ApplyAbpConceptsForAddedEntityAsync(TEntity entity)
{
CheckAndSetId(entity);
@ -477,9 +636,23 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return DeleteAsync(x => x.Id.Equals(id), autoSave, cancellationToken);
}
public virtual async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
var entities = await GetMongoQueryable()
.Where(x => ids.Contains(x.Id))
.ToListAsync(GetCancellationToken(cancellationToken));
await DeleteManyAsync(entities, autoSave, cancellationToken);
}
protected override FilterDefinition<TEntity> CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null)
{
return RepositoryFilterer.CreateEntityFilter(entity, withConcurrencyStamp, concurrencyStamp);
}
protected override FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool withConcurrencyStamp = false)
{
return RepositoryFilterer.CreateEntitiesFilter(entities, withConcurrencyStamp);
}
}
}

27
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepositoryFilterer.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
@ -23,13 +24,13 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)) && DataFilter.IsEnabled<ISoftDelete>())
{
filters.Add(Builders<TEntity>.Filter.Eq(e => ((ISoftDelete) e).IsDeleted, false));
filters.Add(Builders<TEntity>.Filter.Eq(e => ((ISoftDelete)e).IsDeleted, false));
}
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
var tenantId = CurrentTenant.Id;
filters.Add(Builders<TEntity>.Filter.Eq(e => ((IMultiTenant) e).TenantId, tenantId));
filters.Add(Builders<TEntity>.Filter.Eq(e => ((IMultiTenant)e).TenantId, tenantId));
}
}
}
@ -72,8 +73,28 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return Builders<TEntity>.Filter.And(
Builders<TEntity>.Filter.Eq(e => e.Id, entity.Id),
Builders<TEntity>.Filter.Eq(e => ((IHasConcurrencyStamp) e).ConcurrencyStamp, concurrencyStamp)
Builders<TEntity>.Filter.Eq(e => ((IHasConcurrencyStamp)e).ConcurrencyStamp, concurrencyStamp)
);
}
public FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool applyFilters = false)
{
return CreateEntitiesFilter(entities.Select(s => s.Id), applyFilters);
}
public FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TKey> ids, bool applyFilters = false)
{
var filters = new List<FilterDefinition<TEntity>>()
{
Builders<TEntity>.Filter.In(e => e.Id, ids),
};
if (applyFilters)
{
AddGlobalFilters(filters);
}
return Builders<TEntity>.Filter.And(filters);
}
}
}

6
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
@ -305,6 +306,11 @@ namespace Volo.Abp.Domain.Repositories
{
throw new NotImplementedException();
}
public Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
public class MyTestCustomBaseRepository<TEntity> : MyTestDefaultRepository<TEntity>

69
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Repositories;
@ -115,5 +117,72 @@ namespace Volo.Abp.TestApp.Testing
person.Id.ShouldNotBe(Guid.Empty);
}
[Fact]
public async Task InserManyAsync()
{
var entities = new List<Person>
{
new Person(Guid.NewGuid(), "Person 1", 30),
new Person(Guid.NewGuid(), "Person 2", 31),
new Person(Guid.NewGuid(), "Person 3", 32),
new Person(Guid.NewGuid(), "Person 4", 33),
};
await PersonRepository.InsertManyAsync(entities);
foreach (var entity in entities)
{
var person = await PersonRepository.FindAsync(entity.Id);
person.ShouldNotBeNull();
}
}
[Fact]
public async Task UpdateManyAsync()
{
var entities = await PersonRepository.GetListAsync();
var random = new Random();
entities.ForEach(f => f.Age = random.Next());
await PersonRepository.UpdateManyAsync(entities);
foreach (var entity in entities)
{
var person = await PersonRepository.FindAsync(entity.Id);
person.ShouldNotBeNull();
person.Age.ShouldBe(entity.Age);
}
}
[Fact]
public async Task DeleteManyAsync()
{
var entities = await PersonRepository.GetListAsync();
await PersonRepository.DeleteManyAsync(entities);
foreach (var entity in entities)
{
var person = await PersonRepository.FindAsync(entity.Id);
person.ShouldBeNull();
}
}
[Fact]
public async Task DeleteManyAsync_WithId()
{
var entities = await PersonRepository.GetListAsync();
var ids = entities.Select(s => s.Id).ToArray();
await PersonRepository.DeleteManyAsync(ids);
foreach (var id in ids)
{
var person = await PersonRepository.FindAsync(id);
person.ShouldBeNull();
}
}
}
}

Loading…
Cancel
Save