首页 > 数据库 >C#上传excel,解析主从表,1W数据快速插入数据库,5s完成

C#上传excel,解析主从表,1W数据快速插入数据库,5s完成

时间:2024-08-19 22:04:13浏览次数:14  
标签:Name 1W C# sqlBulkCopy excel propertyInfos var new public

参考文章
net core天马行空系列-各大数据库快速批量插入数据方法汇总
ExcelMapper

Controller核心代码

[HttpPost]
public async Task<IActionResult> ImportToDoItems(IFormFile file)
{
    if (file == null || file.Length == 0)
    {
        return BadRequest("File is empty");
    }

    using var stream = new MemoryStream();
    using (MiniProfiler.Current.Step("ToStram"))
    {
        await file.CopyToAsync(stream);
        stream.Position = 0;
    }

    IEnumerable<ImportToDoItemModel> importModels;
    using (MiniProfiler.Current.Step("Convert"))
    {
        //解析文件为强类型集合
        importModels = ExcelMapperConvertor.Convert<ImportToDoItemModel, ImportToDoItemModelValidator>(stream);
    }

    //插入数据库
    using (MiniProfiler.Current.Step("DataBase"))
    {
        //导入的主表名称
        var importMasterNames = importModels.Select(x => x.Name).Distinct();
        //数据库中存在的主表
        var existMasters = await _dbContext.Set<ToDoMaster>().Where(x => importMasterNames.Contains(x.Name)).ToListAsync();
        //数据库中存在的主表名称
        var existMasterNames = existMasters.Select(x => x.Name);
        //需要插入的主表名称(数据库中不存在)
        var insertMasterNames = importMasterNames.Where(x => !existMasterNames.Contains(x));

        //插入主表,直接用dbContext插入
        var insertMasters = insertMasterNames
            .Select(name => new ToDoMaster()
            {
                Id = YitIdInitHelper.NextId(),
                Name = name
            });
        await _dbContext.AddRangeAsync(insertMasters);

        //插入从表,从表用SqlBulkCopy
        var creationTime = DateTime.Now;
        var insertToDoItems = importModels
            .Select(x => new ToDoItem()
            {
                Id = YitIdInitHelper.NextId(),
                ToDoMasterId = allMasterNames[x.Name].Id,
                Text = x.Text,
                Count = x.Count,
                IsDeleted = false,
                CreationTime = creationTime,
            });

        var connectionString = "Server=localhost; Database=MyABP7NET6Db; Trusted_Connection=True;TrustServerCertificate=True;Integrated Security=True;";
        using (var dbConnection = new SqlConnection(connectionString))
        {
            dbConnection.Open();

            using var sqlBulkCopy = new SqlBulkCopy(dbConnection, SqlBulkCopyOptions.KeepIdentity, null);
            sqlBulkCopy.BatchSize = 20000;
            //表名
            sqlBulkCopy.DestinationTableName = "ToDoItems_202408";
            //针对列名做一下映射
            sqlBulkCopy.ColumnMappings.Add("Id", "Id");
            sqlBulkCopy.ColumnMappings.Add("ToDoMasterId", "ToDoMasterId");
            sqlBulkCopy.ColumnMappings.Add("Text", "Text");
            sqlBulkCopy.ColumnMappings.Add("Count", "Count");
            sqlBulkCopy.ColumnMappings.Add("IsDeleted", "IsDeleted");
            sqlBulkCopy.ColumnMappings.Add("CreationTime", "CreationTime");
            //将实体类列表转换成dataTable
            var table = insertToDoItems.ToDataTable();
            sqlBulkCopy.WriteToServer(table);
        }
        //await _dbContext.AddRangeAsync(insertToDoItems);

        await _dbContext.SaveChangesAsync();
    }

    return Ok(new object[] { importModels.Count() });
}

MiniProfile监控数据

浏览器监控数据

Model及其校验类

public class ImportToDoItemModel
{
    // 主表字段
    public string Name { get; set; }

    // 从表字段
    public string Text { get; set; }

    // 从表字段
    public int Count { get; set; }
}

public class ImportToDoItemModelValidator : AbstractValidator<ImportToDoItemModel>
{
    public ImportToDoItemModelValidator()
    {
        RuleFor(x => x.Name).NotEmpty();
        RuleFor(x => x.Count).ExclusiveBetween(0, 10001).WithMessage("Count 不符合要求");
    }
}

ExcelMapperConvertor封装

public class ExcelMapperConvertor
{
    /// <summary>
    /// ExcelMapper 将文件流(内存流)转为强类型集合
    /// FluentValidation校验转换后的数据是否符合业务要求
    /// 如果校验失败直接报错
    /// </summary>
    public static IEnumerable<T> Convert<T, TValidator>(Stream stream) where TValidator : AbstractValidator<T>, new()
    {
        var importer = new ExcelMapper(stream);
        var validator = new TValidator();

        try
        {
            //此处如果转换出错,会继续执行,直到遍历到错误一行时,才会报错
            var results = importer.Fetch<T>();
            // 遍历到错误一行时,才会报错
            foreach (var result in results)
            {
                var validationResult = validator.Validate(result);
                if (!validationResult.IsValid)
                {
                    foreach (var error in validationResult.Errors)
                    {
                        throw new Exception($"{error.PropertyName}:{error.AttemptedValue} {error.ErrorMessage}");
                    }
                }
            }

            return results;
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message);
        }
    }
}

DataTableHelper封装

public static class DataTableHelper
{
    public static ConcurrentDictionary<string, object> CacheDictionary = new ConcurrentDictionary<string, object>();

    /// <summary>
    /// 构建一个object数据转换成一维数组数据的委托
    /// </summary>
    /// <param name="objType"></param>
    /// <param name="propertyInfos"></param>
    /// <returns></returns>
    public static Func<T, object[]> BuildObjectGetValuesDelegate<T>(List<PropertyInfo> propertyInfos) where T : class
    {
        var objParameter = Expression.Parameter(typeof(T), "model");
        var selectExpressions = propertyInfos.Select(it => BuildObjectGetValueExpression(objParameter, it));
        var arrayExpression = Expression.NewArrayInit(typeof(object), selectExpressions);
        var result = Expression.Lambda<Func<T, object[]>>(arrayExpression, objParameter).Compile();
        return result;
    }

    /// <summary>
    /// 构建对象获取单个值得
    /// </summary>
    /// <param name="modelExpression"></param>
    /// <param name="propertyInfo"></param>
    /// <returns></returns>
    public static Expression BuildObjectGetValueExpression(ParameterExpression modelExpression, PropertyInfo propertyInfo)
    {
        var propertyExpression = Expression.Property(modelExpression, propertyInfo);
        var convertExpression = Expression.Convert(propertyExpression, typeof(object));
        return convertExpression;
    }

    public static DataTable ToDataTable<T>(this IEnumerable<T> source, List<PropertyInfo> propertyInfos = null, bool useColumnAttribute = false) where T : class
    {
        var table = new DataTable("template");
        if (propertyInfos == null || propertyInfos.Count == 0)
        {
            propertyInfos = typeof(T).GetProperties().Where(it => it.CanRead).ToList();
        }
        foreach (var propertyInfo in propertyInfos)
        {
            var columnName = useColumnAttribute ? (propertyInfo.GetCustomAttribute<ColumnAttribute>()?.Name ?? propertyInfo.Name) : propertyInfo.Name;
            table.Columns.Add(columnName, ChangeType(propertyInfo.PropertyType));
        }

        Func<T, object[]> func;
        var key = typeof(T).FullName + string.Join("", propertyInfos.Select(it => it.Name).ToList());//propertyInfos.Select(it => it.Name).ToList().StringJoin(); 
        if (CacheDictionary.TryGetValue(key, out var cacheFunc))
        {
            func = (Func<T, object[]>)cacheFunc;
        }
        else
        {
            func = BuildObjectGetValuesDelegate<T>(propertyInfos);
            CacheDictionary.TryAdd(key, func);
        }

        foreach (var model in source)
        {
            var rowData = func(model);
            table.Rows.Add(rowData);
        }

        return table;
    }

    private static Type ChangeType(Type type)
    {
        if (type.IsNullable())
        {
            type = Nullable.GetUnderlyingType(type);
        }

        return type;
    }

    public static bool IsNullable(this Type type)
    {
        // 检查类型是否是System.Nullable<T>的实例
        return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
    }
}

标签:Name,1W,C#,sqlBulkCopy,excel,propertyInfos,var,new,public
From: https://www.cnblogs.com/cnblogsName/p/18368189

相关文章

  • AGC002 题解
    目录A-RangeProductB-BoxandBallC-KnotPuzzleA-RangeProduct分情况讨论:\(a\le0\leb\)时,乘积一定为\(0\);否则:\(0<a\leb\)时,乘积一定为正;否则,负数的个数有\(b-a+1\)个,判断这个数是否为奇数,若是,乘积为负,否则为正。#include<bits/stdc++.h......
  • c语言中用%lf输出double型和float型变量
     001、 测试%lf输出double型和int型变量的差异 [root@PC1test]#lstest.c[root@PC1test]#cattest.c##测试c程序#include<stdio.h>intmain(void){doublei;/*声明doble型和float型变量*/floatj;......
  • Linux scp 文件传输
    scp将本服务器的文件传输到远程服务器基本语法scp`[源路径]``[目标服务器]`:`[目标路径]`样例将本服务器123.txt文件传输到远程服务器并重命名为456.txtscp123.txtuser@remote_server:/home/tabu/456.txt使用-r选项复制整个目录scp-rtabu/*user@remote_server:/ho......
  • 章节七:215 +个 ChatGPT优秀提示以及如何编写自己的提示
    关注的人一天可以看到10篇文章!章节一:章节一:215+个ChatGPT优秀提示以及如何编写自己的提示-CSDN博客章节二:章节二:215+个ChatGPT优秀提示以及如何编写自己的提示-CSDN博客章节三:章节三:215+个ChatGPT优秀提示以及如何编写自己的提示-CSDN博客章节四:章节四:215+个Chat......
  • CSP24
    学了些DP学校题库有\(BUG\)首先要满足条件\(x,y\)的二进制有1的位必然包含\(a\),然后让\(s-2a\),也就是除去二进制包含\(a\)有1的位,然后\(<0\)肯定无解,其次是如果有与\(a\)同一级的含\(1\)二进制位也不合法点击查看代码#include<bits/stdc++.h>#definespeed()ios::sync......
  • Android开发 - DisplayMetrics 类控制布局图形的缩放显示解析
    DisplayMetrics是什么DisplayMetrics类在Android中用于获取设备的显示属性(像素等)DisplayMetrics的主要属性metrics.density:屏幕密度,用于决定屏幕上每英寸的像素数DisplayMetricsmetrics=newDisplayMetrics();density=metrics.density;常见值:0.75(低密度)、1.0......
  • rsync概述详解
    一、rsync基础概念:rsync是实时数据备份的作用1、rsync数据备份传输的方式本地模式:类似cp命令,与其不同的是,rsync属于增量备份远程方式模式:不区分服务端和客户端,实现两台主机时间到数据拷贝,可以直接进行数据的上传/下载进行备份/脚本打包守护进程模式:这种模式采用虚拟用户的......
  • c语言中读入整型数据和浮点型数据
     001、读入整型数据[root@PC1test]#lstest.c[root@PC1test]#cattest.c##测试脚本#include<stdio.h>intmain(void){inti;//声明整型变量puts("pleaseinputaninteger.");print......
  • docker部署gitlab
    gitlab拉取镜像dockerpull创建挂载目录mkdirgitlabcdgitlabmkdir-pdata/logmkdir-pdata/optmkdir-pdata/etc启动容器dockerrun-itd-p8443:443-p8090:80-p8022:22--namegitlab-v$PWD/data/etc:/etc/gitlab-v$PWD/data/log:/var/log/gitlab-v......
  • csharpierrc.json 配置
    CSharpier配置.csharpierrc.json{"printWidth":100,"useTabs":false,"tabWidth":4,"endOfLine":"auto"}参数说明PrintWidth​Specifyatwhatpointtheprinterwillwrapcontent.Thisisnotahardlimit.......