ABP vNext 批量操作

ABP框架6.0后提供了 InsertManyAsync  UpdateManyAsync DeleteManyAsync  批量操作方法

源码:

public interface IBasicRepository<TEntity> : IReadOnlyBasicRepository<TEntity>, IRepository
   where TEntity : class, IEntity
{
    Task<TEntity> InsertAsync(
      TEntity entity,
      bool autoSave = false,
      CancellationToken cancellationToken = default(CancellationToken));
    
    Task InsertManyAsync(
      IEnumerable<TEntity> entities,
      bool autoSave = false,
      CancellationToken cancellationToken = default(CancellationToken));
    
    Task<TEntity> UpdateAsync(
      TEntity entity,
      bool autoSave = false,
      CancellationToken cancellationToken = default(CancellationToken));
    
    Task UpdateManyAsync(
      IEnumerable<TEntity> entities,
      bool autoSave = false,
      CancellationToken cancellationToken = default(CancellationToken));

    Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken));
    
    Task DeleteManyAsync(
      IEnumerable<TEntity> entities,
      bool autoSave = false,
      CancellationToken cancellationToken = default(CancellationToken));
}

  我们的IRepository接口是继承自IBasicRepository接口的

 public interface IRepository<TEntity> : 
    IReadOnlyRepository<TEntity>,
    IReadOnlyBasicRepository<TEntity>,
    IRepository,
    IBasicRepository<TEntity>
    where TEntity : class, IEntity

我们一般在**EntityFrameworkCoreModule 层注入一个默认的仓储接口实现

 context.Services.AddAbpDbContext<HolidayDbContext>(options =>
        {
                /* Remove "includeAllEntities: true" to create
                 * default repositories only for aggregate roots */
            options.AddDefaultRepositories(includeAllEntities: true);
        });

默认实现类

public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IEfCoreRepository<TEntity>
    where TDbContext : IEfCoreDbContext
    where TEntity : class, IEntity

在此类实现了 InsertManyAsync  UpdateManyAsync DeleteManyAsync 方法

源码

    public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        var entityArray = entities.ToArray();
        var dbContext = await GetDbContextAsync();
        cancellationToken = GetCancellationToken(cancellationToken);

        foreach (var entity in entityArray)
        {
            CheckAndSetId(entity);
        }

        if (BulkOperationProvider != null)
        {
            await BulkOperationProvider.InsertManyAsync<TDbContext, TEntity>(
                this,
                entityArray,
                autoSave,
                GetCancellationToken(cancellationToken)
            );
            return;
        }

        await dbContext.Set<TEntity>().AddRangeAsync(entityArray, cancellationToken);

        if (autoSave)
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
    }
    public override async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        cancellationToken = GetCancellationToken(cancellationToken);

        if (BulkOperationProvider != null)
        {
            await BulkOperationProvider.UpdateManyAsync<TDbContext, TEntity>(
                this,
                entities,
                autoSave,
                GetCancellationToken(cancellationToken)
                );

            return;
        }

        var dbContext = await GetDbContextAsync();

        dbContext.Set<TEntity>().UpdateRange(entities);

        if (autoSave)
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
    }
  public override async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        cancellationToken = GetCancellationToken(cancellationToken);

        if (BulkOperationProvider != null)
        {
            await BulkOperationProvider.DeleteManyAsync<TDbContext, TEntity>(
                this,
                entities,
                autoSave,
                cancellationToken
            );

            return;
        }

        var dbContext = await GetDbContextAsync();

        dbContext.RemoveRange(entities);

        if (autoSave)
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
    }
public IEfCoreBulkOperationProvider BulkOperationProvider => LazyServiceProvider.LazyGetService<IEfCoreBulkOperationProvider>();

代码中的 BulkOperationProvider 就非常关键了 这个就是官方文档上提供的 自定义批量操作 

ABP官方原话

如果你有更好的逻辑或使用外部库实现批量操作, 你可以通过实现 IEfCoreBulkOperationProvider 覆写这个逻辑.

  • 你可以使用下面的示例模板
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.
    }
}

官网文档地址 https://docs.abp.io/zh-Hans/abp/latest/Entity-Framework-Core

在实际项目中,不可避免会遇到数据导入的需求,如果是大量数据导入,就必须引用批量处理的功能;efcore本身不提供批量处理功能,而abp框架虽然提供InsertMany和UpdateMany方法,但本质上仍然是分解成单条insert和update操作,在处理稍大量的数据时,耗时就会明显增加;如果你使用的是SqlServer、PostgreSQL或SQLite,可以使用官方推荐的方法实现批处理,你只需要引用borisdj / EFCore.BulkExtensions并编写IEfCoreBulkOperationProvider的提供程序即可

Install-Package EFCore.BulkExtensions
    public class BulkExtensionsEfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency
    {
        public BulkExtensionsEfCoreBulkOperationProvider()
        {
            
        }

        public async Task DeleteManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository, IEnumerable<TEntity> entities, bool autoSave, CancellationToken cancellationToken)
            where TDbContext : IEfCoreDbContext
            where TEntity : class, IEntity
        {
            var dbContext = await repository.GetDbContextAsync();
            await dbContext.BulkDeleteAsync(entities.ToList(), cancellationToken: cancellationToken);
        }
 
        public async Task InsertManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository, IEnumerable<TEntity> entities, bool autoSave, CancellationToken cancellationToken)
            where TDbContext : IEfCoreDbContext
            where TEntity : class, IEntity
        {
            var dbContext = await repository.GetDbContextAsync();
            await dbContext.BulkInsertAsync(entities.ToList(), cancellationToken: cancellationToken);
        }
 
        public async Task UpdateManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository, IEnumerable<TEntity> entities, bool autoSave, CancellationToken cancellationToken)
            where TDbContext : IEfCoreDbContext
            where TEntity : class, IEntity
        {
            var dbContext = await repository.GetDbContextAsync();
            await dbContext.BulkUpdateAsync(entities.ToList(), cancellationToken: cancellationToken);
        }
    }

这样常规调用InsertManyAsync或UpdateManyAsync时就可以实现批量操作。但是borisdj / EFCore.BulkExtensions不支持Mysql数据(注意:在6.0以后的版本已经支持Mysql),因此不能使用这种方法,这里我使用的是另一个扩展包yang-er/efcore-ext,跟EFCore.BulkExtensions算是一脉相承,并且添加了对mysql的支持,理论上也可以跟上面一样编写提供程序来实现,但是由于实现原理有点不一样,这样写生成不了Sql语句,还是只能换一个思路来实现了。

    public class BulkExtensionsEfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency
    {
        public BulkExtensionsEfCoreBulkOperationProvider()
        {
            
        }

        public async Task DeleteManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository, IEnumerable<TEntity> entities, bool autoSave, CancellationToken cancellationToken)
            where TDbContext : IEfCoreDbContext
            where TEntity : class, IEntity
        {
            var dbContext = await repository.GetDbContextAsync();
            await dbContext.BulkDeleteAsync(entities.ToList(), cancellationToken: cancellationToken);
        }
 
        public async Task InsertManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository, IEnumerable<TEntity> entities, bool autoSave, CancellationToken cancellationToken)
            where TDbContext : IEfCoreDbContext
            where TEntity : class, IEntity
        {
            var dbContext = await repository.GetDbContextAsync();
            await dbContext.BulkInsertAsync(entities.ToList(), cancellationToken: cancellationToken);
        }
 
        public async Task UpdateManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository, IEnumerable<TEntity> entities, bool autoSave, CancellationToken cancellationToken)
            where TDbContext : IEfCoreDbContext
            where TEntity : class, IEntity
        {
            var dbContext = await repository.GetDbContextAsync();
            await dbContext.BulkUpdateAsync(entities.ToList(), cancellationToken: cancellationToken);
        }
    }

如果是使用Mysql : 需要在客户端数据库连接地址添加 AllowLoadLocalInfile=true; 配置

To use MySqlBulkLoader.Local=true, set AllowLoadLocalInfile=true in the connection string

 See https://fl.vu/mysql-load-data

开始实例演示如何在abp上使用efcore-ext扩展包:

参考文章 https://blog.csdn.net/duanzilin/article/details/123253444

posted @ 2022-10-21 11:21  刘小吉  阅读(1415)  评论(0编辑  收藏  举报