Browse Source

feat: mysql批量插入

batch
zzzwangjun@gmail.com 6 months ago
parent
commit
3581ce8708
  1. 7
      aspnet-core/Lion.AbpPro.sln
  2. 1
      aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/GlobalUsings.cs
  3. 8
      aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion.AbpPro.EntityFrameworkCore.Mysql.csproj
  4. 5
      aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/AbpProEntityFrameworkCoreMysqlModule.cs
  5. 15
      aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/EfCoreBulkOperationProvider.cs
  6. 196
      aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/System/Linq/EfDapperBulkExtensions.cs
  7. 45
      aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/System/Linq/MySQLBulkInsertExtensions.cs
  8. 24
      aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Domain/DataDictionaries/DataDictionaryManager.cs
  9. 5
      aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/AbpProHttpApiHostModule.cs
  10. 1
      aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/Lion.AbpPro.HttpApi.Host.csproj

7
aspnet-core/Lion.AbpPro.sln

@ -255,6 +255,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.Hangfire", "fra
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.SignalR", "Lion.AbpPro.SignalR\Lion.AbpPro.SignalR.csproj", "{66B3D9E0-CB3F-464A-9813-F2DC1426A37A}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.SignalR", "Lion.AbpPro.SignalR\Lion.AbpPro.SignalR.csproj", "{66B3D9E0-CB3F-464A-9813-F2DC1426A37A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lion.AbpPro.EntityFrameworkCore.Mysql", "frameworks\src\Lion.AbpPro.EntityFrameworkCore.Mysql\Lion.AbpPro.EntityFrameworkCore.Mysql.csproj", "{AF650F62-0447-4739-B73D-44E0120E0275}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -629,6 +631,10 @@ Global
{66B3D9E0-CB3F-464A-9813-F2DC1426A37A}.Debug|Any CPU.Build.0 = Debug|Any CPU {66B3D9E0-CB3F-464A-9813-F2DC1426A37A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66B3D9E0-CB3F-464A-9813-F2DC1426A37A}.Release|Any CPU.ActiveCfg = Release|Any CPU {66B3D9E0-CB3F-464A-9813-F2DC1426A37A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66B3D9E0-CB3F-464A-9813-F2DC1426A37A}.Release|Any CPU.Build.0 = Release|Any CPU {66B3D9E0-CB3F-464A-9813-F2DC1426A37A}.Release|Any CPU.Build.0 = Release|Any CPU
{AF650F62-0447-4739-B73D-44E0120E0275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF650F62-0447-4739-B73D-44E0120E0275}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF650F62-0447-4739-B73D-44E0120E0275}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF650F62-0447-4739-B73D-44E0120E0275}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -751,6 +757,7 @@ Global
{89CCAEA6-8176-4E4B-8D84-A2ACE2715F88} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3} {89CCAEA6-8176-4E4B-8D84-A2ACE2715F88} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3}
{6C2FDD3D-F711-46B0-A2F2-B94BC33F136B} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3} {6C2FDD3D-F711-46B0-A2F2-B94BC33F136B} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3}
{66B3D9E0-CB3F-464A-9813-F2DC1426A37A} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3} {66B3D9E0-CB3F-464A-9813-F2DC1426A37A} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3}
{AF650F62-0447-4739-B73D-44E0120E0275} = {7BE85EBC-99AD-4CDE-957E-4BDD087FC4E3}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F} SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F}

1
aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/GlobalUsings.cs

@ -3,7 +3,6 @@
global using Lion.AbpPro.EntityFrameworkCore; global using Lion.AbpPro.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore.Storage; global using Microsoft.EntityFrameworkCore.Storage;
global using MySqlConnector;
global using Volo.Abp.DependencyInjection; global using Volo.Abp.DependencyInjection;
global using Volo.Abp.Domain.Entities; global using Volo.Abp.Domain.Entities;
global using Volo.Abp.Domain.Repositories.EntityFrameworkCore; global using Volo.Abp.Domain.Repositories.EntityFrameworkCore;

8
aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion.AbpPro.EntityFrameworkCore.Mysql.csproj

@ -1,16 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace /> <RootNamespace />
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Lion.AbpPro.EntityFrameworkCore\Lion.AbpPro.EntityFrameworkCore.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Volo.Abp.EntityFrameworkCore.MySQL" /> <PackageReference Include="Volo.Abp.EntityFrameworkCore.MySQL" />
<PackageReference Include="Volo.Abp.Dapper" />
</ItemGroup> </ItemGroup>
</Project> </Project>

5
aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/AbpProEntityFrameworkCoreMysqlModule.cs

@ -1,6 +1,9 @@
namespace Lion.AbpPro.EntityFrameworkCore.Mysql; using Volo.Abp.Dapper;
namespace Lion.AbpPro.EntityFrameworkCore.Mysql;
[DependsOn(typeof(AbpEntityFrameworkCoreMySQLModule))] [DependsOn(typeof(AbpEntityFrameworkCoreMySQLModule))]
[DependsOn(typeof(AbpDapperModule))]
public class AbpProEntityFrameworkCoreMysqlModule : AbpModule public class AbpProEntityFrameworkCoreMysqlModule : AbpModule
{ {
} }

15
aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/Lion/AbpPro/EntityFrameworkCore/Mysql/EfCoreBulkOperationProvider.cs

@ -1,9 +1,18 @@
using Volo.Abp.Auditing; using Microsoft.Extensions.Logging;
using MySql.Data.MySqlClient;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Repositories.Dapper;
namespace Lion.AbpPro.EntityFrameworkCore.Mysql; namespace Lion.AbpPro.EntityFrameworkCore.Mysql;
public class EfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency public class EfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency
{ {
private readonly ILogger<EfCoreBulkOperationProvider> _logger;
public EfCoreBulkOperationProvider(ILogger<EfCoreBulkOperationProvider> logger)
{
_logger = logger;
}
/// <summary> /// <summary>
/// 批量新增 /// 批量新增
/// </summary> /// </summary>
@ -24,7 +33,9 @@ public class EfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransi
{ {
var dbContext = await repository.GetDbContextAsync(); var dbContext = await repository.GetDbContextAsync();
var dbTransaction = dbContext.Database.CurrentTransaction?.GetDbTransaction(); var dbTransaction = dbContext.Database.CurrentTransaction?.GetDbTransaction();
await dbContext.BulkInsertAsync(entities, dbTransaction as MySqlTransaction, cancellationToken); var dbConnection = dbContext.Database.GetDbConnection();
var count = await dbConnection.BulkInsertAsync(dbContext, entities, dbTransaction as MySqlTransaction);
_logger.LogInformation($"批量新增{count}条数据成功");
if (autoSave) if (autoSave)
{ {
await dbContext.SaveChangesAsync(cancellationToken); await dbContext.SaveChangesAsync(cancellationToken);

196
aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/System/Linq/EfDapperBulkExtensions.cs

@ -0,0 +1,196 @@
using System.Data;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Metadata;
using MySql.Data.MySqlClient;
public static class EfDapperBulkExtensions
{
/// <summary>
/// 使用 EF Core metadata 确保表名、字段名和顺序一致性进行批量插入
/// </summary>
public static async Task<int> BulkInsertAsync<T>(
this IDbConnection connection,
DbContext dbContext,
IEnumerable<T> entities,
IDbTransaction transaction = null,
CancellationToken cancellationToken = default) where T : class
{
// 获取 EF Core 实体类型信息
var entityType = dbContext.Model.FindEntityType(typeof(T));
if (entityType == null)
throw new InvalidOperationException($"Entity type {typeof(T).Name} not found in DbContext model.");
// 获取表名
var tableName = entityType.GetTableName();
var schemaName = entityType.GetSchema();
if (string.IsNullOrEmpty(tableName))
throw new InvalidOperationException($"Table name not found for entity {typeof(T).Name}.");
// 获取属性映射信息,确保顺序一致性
var properties = GetPropertiesInOrder<T>(entityType);
// 创建 DataTable
var dataTable = CreateDataTable<T>(entities.ToList(), properties, tableName, schemaName);
// 执行批量插入
return await BulkInsertDataTableAsync(connection, dataTable, cancellationToken);
}
/// <summary>
/// 创建 DataTable 用于批量插入
/// </summary>
private static DataTable CreateDataTable<T>(List<T> entities, List<PropertyMappingInfo> properties, string tableName, string schemaName) where T : class
{
var dataTable = new DataTable();
dataTable.TableName = string.IsNullOrEmpty(schemaName) ? tableName : $"{schemaName}.{tableName}";
// 添加列,按照 EF Core 中定义的顺序
foreach (var property in properties)
{
var columnType = GetDbType(property.Property.ClrType);
var column = new DataColumn(property.ColumnName, columnType);
dataTable.Columns.Add(column);
}
// 添加行
foreach (var entity in entities)
{
var row = dataTable.NewRow();
foreach (var property in properties)
{
var value = property.PropertyInfo.GetValue(entity) ?? DBNull.Value;
row[property.ColumnName] = value;
}
dataTable.Rows.Add(row);
}
return dataTable;
}
/// <summary>
/// 将 DataTable 批量插入到 MySQL
/// </summary>
private static async Task<int> BulkInsertDataTableAsync(IDbConnection connection, DataTable dataTable, CancellationToken cancellationToken)
{
var mySqlConnection = connection as MySqlConnection;
if (mySqlConnection == null)
throw new InvalidOperationException("Connection must be MySqlConnection for bulk insert");
if (dataTable.Rows.Count == 0)
return 0;
// 确保连接打开
if (mySqlConnection.State != ConnectionState.Open)
{
await mySqlConnection.OpenAsync(cancellationToken);
}
// 使用 MySqlBulkLoader 进行批量插入
var bulkLoader = new MySqlBulkLoader(mySqlConnection)
{
TableName = dataTable.TableName,
FieldTerminator = "\t",
LineTerminator = "\n",
NumberOfLinesToSkip = 0
};
// 添加字段映射,确保与 DataTable 列顺序一致
foreach (DataColumn column in dataTable.Columns)
{
bulkLoader.Columns.Add(column.ColumnName);
}
// 写入数据到临时文件
var tempFilePath = Path.GetTempFileName();
try
{
using (var writer = new StreamWriter(tempFilePath, false, System.Text.Encoding.UTF8))
{
foreach (DataRow row in dataTable.Rows)
{
var values = new object[dataTable.Columns.Count];
row.ItemArray.CopyTo(values, 0);
var line = string.Join("\t", values.Select(v => v?.ToString()?.Replace("\t", "\\t").Replace("\n", "\\n") ?? "\\N"));
await writer.WriteLineAsync(line);
}
}
bulkLoader.FileName = tempFilePath;
var result = await bulkLoader.LoadAsync();
return result;
}
finally
{
// 清理临时文件
if (File.Exists(tempFilePath))
{
File.Delete(tempFilePath);
}
}
}
/// <summary>
/// 获取属性信息,确保与 EF Core 中定义的顺序一致
/// </summary>
private static List<PropertyMappingInfo> GetPropertiesInOrder<T>(IEntityType entityType) where T : class
{
var properties = new List<PropertyMappingInfo>();
// 按 EF Core 中属性的顺序获取
foreach (var property in entityType.GetProperties())
{
// 跳过 Shadow Properties(影子属性)
if (property.IsShadowProperty())
continue;
var propertyInfo = typeof(T).GetProperty(property.Name, BindingFlags.Instance | BindingFlags.Public);
if (propertyInfo != null && propertyInfo.CanRead && propertyInfo.CanWrite)
{
properties.Add(new PropertyMappingInfo
{
PropertyInfo = propertyInfo,
ColumnName = property.GetColumnName(),
Property = property
});
}
}
return properties;
}
/// <summary>
/// 将 .NET 类型转换为 DbType
/// </summary>
private static Type GetDbType(Type clrType)
{
// 处理可空类型
if (clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
clrType = Nullable.GetUnderlyingType(clrType);
}
return clrType switch
{
Type t when t == typeof(string) => typeof(string),
Type t when t == typeof(int) => typeof(int),
Type t when t == typeof(long) => typeof(long),
Type t when t == typeof(decimal) => typeof(decimal),
Type t when t == typeof(double) => typeof(double),
Type t when t == typeof(float) => typeof(float),
Type t when t == typeof(bool) => typeof(bool),
Type t when t == typeof(DateTime) => typeof(DateTime),
Type t when t == typeof(Guid) => typeof(Guid),
Type t when t == typeof(byte[]) => typeof(byte[]),
_ => typeof(object)
};
}
private class PropertyMappingInfo
{
public PropertyInfo PropertyInfo { get; set; }
public string ColumnName { get; set; }
public IProperty Property { get; set; }
}
}

45
aspnet-core/frameworks/src/Lion.AbpPro.EntityFrameworkCore.Mysql/System/Linq/MySQLBulkInsertExtensions.cs

@ -1,45 +0,0 @@
namespace System.Linq
{
public static class MySQLBulkInsertExtensions
{
public static async Task BulkInsertAsync<TEntity>(this DbContext dbCtx, IEnumerable<TEntity> items, MySqlTransaction transaction = null, CancellationToken cancellationToken = default) where TEntity : class
{
var conn = dbCtx.Database.GetDbConnection();
await conn.OpenIfNeededAsync(cancellationToken);
var dataTable = BulkInsertUtils.BuildDataTable(dbCtx, dbCtx.Set<TEntity>(), items);
var bulkCopy = BuildSqlBulkCopy<TEntity>((MySqlConnection)conn, dbCtx, transaction);
await bulkCopy.WriteToServerAsync(dataTable, cancellationToken);
}
public static void BulkInsert<TEntity>(this DbContext dbCtx, IEnumerable<TEntity> items, MySqlTransaction transaction = null, CancellationToken cancellationToken = default) where TEntity : class
{
var conn = dbCtx.Database.GetDbConnection();
conn.OpenIfNeeded();
var dataTable = BulkInsertUtils.BuildDataTable(dbCtx, dbCtx.Set<TEntity>(), items);
var bulkCopy = BuildSqlBulkCopy<TEntity>((MySqlConnection)conn, dbCtx, transaction);
bulkCopy.WriteToServer(dataTable);
}
private static MySqlBulkCopy BuildSqlBulkCopy<TEntity>(MySqlConnection conn, DbContext dbCtx, MySqlTransaction transaction = null) where TEntity : class
{
var dbSet = dbCtx.Set<TEntity>();
var entityType = dbSet.EntityType;
var dbProps = BulkInsertUtils.ParseDbProps<TEntity>(dbCtx, entityType);
var bulkCopy = new MySqlBulkCopy(conn, transaction)
{
DestinationTableName = entityType.GetTableName() //Schema is not supported by MySQL
};
var sourceOrdinal = 0;
foreach (var dbProp in dbProps)
{
var columnName = dbProp.ColumnName;
bulkCopy.ColumnMappings.Add(new MySqlBulkCopyColumnMapping(sourceOrdinal, columnName));
sourceOrdinal++;
}
return bulkCopy;
}
}
}

24
aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Domain/DataDictionaries/DataDictionaryManager.cs

@ -203,17 +203,19 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
/// <returns></returns> /// <returns></returns>
public virtual async Task DeleteDataDictionaryTypeAsync(Guid id) public virtual async Task DeleteDataDictionaryTypeAsync(Guid id)
{ {
var entity = await _dataDictionaryRepository.FindByIdAsync(id); var entity = new DataDictionary(GuidGenerator.Create(),GuidGenerator.Create().ToString(),GuidGenerator.Create().ToString(),GuidGenerator.Create().ToString(),CurrentTenant.Id);
if (entity == null) await _dataDictionaryRepository.InsertManyAsync(new List<DataDictionary>(){entity});
throw new DataDictionaryDomainException(DataDictionaryManagementErrorCodes.DataDictionaryNotExist); // var entity = await _dataDictionaryRepository.FindByIdAsync(id);
var detail = entity.Details.FirstOrDefault(e => e.DataDictionaryId == id); // if (entity == null)
if (detail != null) // throw new DataDictionaryDomainException(DataDictionaryManagementErrorCodes.DataDictionaryNotExist);
{ // var detail = entity.Details.FirstOrDefault(e => e.DataDictionaryId == id);
entity.Details.Remove(detail); // if (detail != null)
await _dataDictionaryRepository.UpdateAsync(entity); // {
} // entity.Details.Remove(detail);
// await _dataDictionaryRepository.UpdateAsync(entity);
await _dataDictionaryRepository.DeleteAsync(id); // }
//
// await _dataDictionaryRepository.DeleteAsync(id);
} }
} }
} }

5
aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/AbpProHttpApiHostModule.cs

@ -1,3 +1,5 @@
using Lion.AbpPro.EntityFrameworkCore.Mysql;
namespace Lion.AbpPro; namespace Lion.AbpPro;
[DependsOn( [DependsOn(
@ -16,7 +18,8 @@ namespace Lion.AbpPro;
typeof(AbpDistributedLockingModule), typeof(AbpDistributedLockingModule),
typeof(AbpBlobStoringFileSystemModule), typeof(AbpBlobStoringFileSystemModule),
typeof(AbpProStarterModule), typeof(AbpProStarterModule),
typeof(AbpSwashbuckleModule) typeof(AbpSwashbuckleModule),
typeof(AbpProEntityFrameworkCoreMysqlModule)
//typeof(AbpBackgroundJobsHangfireModule) //typeof(AbpBackgroundJobsHangfireModule)
)] )]
public partial class AbpProHttpApiHostModule : AbpModule public partial class AbpProHttpApiHostModule : AbpModule

1
aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/Lion.AbpPro.HttpApi.Host.csproj

@ -32,6 +32,7 @@
<ProjectReference Include="..\..\..\frameworks\src\Lion.AbpPro.AspNetCore\Lion.AbpPro.AspNetCore.csproj"/> <ProjectReference Include="..\..\..\frameworks\src\Lion.AbpPro.AspNetCore\Lion.AbpPro.AspNetCore.csproj"/>
<ProjectReference Include="..\..\..\frameworks\src\Lion.AbpPro.CAP.EntityFrameworkCore\Lion.AbpPro.CAP.EntityFrameworkCore.csproj"/> <ProjectReference Include="..\..\..\frameworks\src\Lion.AbpPro.CAP.EntityFrameworkCore\Lion.AbpPro.CAP.EntityFrameworkCore.csproj"/>
<ProjectReference Include="..\..\..\frameworks\src\Lion.AbpPro.CAP\Lion.AbpPro.CAP.csproj"/> <ProjectReference Include="..\..\..\frameworks\src\Lion.AbpPro.CAP\Lion.AbpPro.CAP.csproj"/>
<ProjectReference Include="..\..\..\frameworks\src\Lion.AbpPro.EntityFrameworkCore.Mysql\Lion.AbpPro.EntityFrameworkCore.Mysql.csproj" />
<ProjectReference Include="..\..\..\frameworks\src\Lion.AbpPro.Starter\Lion.AbpPro.Starter.csproj"/> <ProjectReference Include="..\..\..\frameworks\src\Lion.AbpPro.Starter\Lion.AbpPro.Starter.csproj"/>
<ProjectReference Include="..\..\src\Lion.AbpPro.Application\Lion.AbpPro.Application.csproj"/> <ProjectReference Include="..\..\src\Lion.AbpPro.Application\Lion.AbpPro.Application.csproj"/>
<ProjectReference Include="..\..\src\Lion.AbpPro.EntityFrameworkCore\Lion.AbpPro.EntityFrameworkCore.csproj"/> <ProjectReference Include="..\..\src\Lion.AbpPro.EntityFrameworkCore\Lion.AbpPro.EntityFrameworkCore.csproj"/>

Loading…
Cancel
Save