.net core 导入大批量数据到数据库中的一种方式

因为addrange()+savechanges()的插入数据的效率对大批量数据而言十分低,所以在寻求一种可以更快速将数据插入到数据库中的方式。

在网上查了一下,发现有人说通过生成insert的sql语句执行的效率很高,所以写了一个通用的方法,此方法要求模型中需要导入的字段必须有column属性,因为我们用的是mysql,命名要求词间用“_”分隔,所以模型中的需要映射到数据库的字段上基本都有column属性,以此也排除了导航属性和[NotMapped]的字段。

14个字段的表插入速度平均大概为1min20s 5W条数据。

适用场景:单表大批量数据导入,不需要使用事务(需要事务的话,改造一下应该也可以)

需要优化但我不会优化的地方:生成sql的时候会花费50s左右,我觉得对于5W条数据来说太长了,我本来以为最多超不过10s,没想到竟然花费了这么久,不知哪位可以帮忙给看看哪里有优化的空间。

直接上代码了

        #region 导入大批量数据到数据库中
        /// <summary>
        /// 导入大批量数据到数据库中 (模型中需要进行导入的字段必须有column属性)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="datas">数据</param>
        /// <param name="length">每次导入的数量</param>
        /// <returns></returns>
        public async Task<int> ImportBigDatas<T>(List<T> datas, int length = 50000) where T : class
        {
            var tableName = dbStoreReadWrite.GetTableName(typeof(T)).TableName;
            if (tableName.IsNullOrEmpty())
            {
                throw new ArgumentNullException($"“{nameof(T)}”未进行模型配置");
            }
            var sum = 0;
            for (int i = 0; i <= datas.Count / length; i++)
            {
                var sql = HistoryExtension.GenerateInsertStr(tableName, datas.Skip(i * length).Take(length).ToList());
                sum += await dbStoreReadWrite.Database.ExecuteSqlRawAsync(sql);
            }
            return sum;
        }
        #endregion

        /// <summary>
        /// 生成插入数据库的sql语句
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tableName"></param>
        /// <param name="sources"></param>
        /// <returns></returns>
        public static string GenerateInsertStr<T>(string tableName, List<T> sources) where T : class
        {
            var properties = typeof(T).GetProperties().ToList();
            var sql = new StringBuilder($"insert into {tableName}(");
            foreach (var e in properties)
            {
                if (e.CustomAttributes.Count() == 0 || e.CustomAttributes.Where(e => e.AttributeType == typeof(ColumnAttribute)).FirstOrDefault() == null || e.CustomAttributes.Where(e => e.AttributeType == typeof(ColumnAttribute)).FirstOrDefault().ConstructorArguments.Count == 0 || e.CustomAttributes.Where(e => e.AttributeType == typeof(ColumnAttribute)).FirstOrDefault().ConstructorArguments.FirstOrDefault() == null)
                { continue; }

                sql.Append(e.CustomAttributes.Where(e => e.AttributeType == typeof(ColumnAttribute)).FirstOrDefault().ConstructorArguments.FirstOrDefault().Value.ToString());
                
                if (e == properties[^1])
                {
                    sql.Append(")");
                }
                else
                {
                    sql.Append(",");
                }
            }
            sql.Append(" values");
            sources.ForEach(a =>
            {
                sql.Append('(');
                foreach (var e in properties)
                {
                    object value = null;
                    
                    if (e.CustomAttributes.Count() == 0 || e.CustomAttributes.Where(e => e.AttributeType == typeof(ColumnAttribute)).FirstOrDefault() == null || e.CustomAttributes.Where(e => e.AttributeType == typeof(ColumnAttribute)).FirstOrDefault().ConstructorArguments.Count == 0 || e.CustomAttributes.Where(e => e.AttributeType == typeof(ColumnAttribute)).FirstOrDefault().ConstructorArguments.FirstOrDefault() == null)
                    { continue; }

                    value = e.GetValue(a);

                    if (e.PropertyType.IsEnum)
                    {
                        if (value != null)
                        {
                            value = ((int)e.GetValue(a)).ToString();
                        }
                    }
                    else if (e.PropertyType.Name.Contains("Boolean"))
                    {
                        if (value.ToString() == "True")
                        {
                            value = "1";
                        }
                        else if (value.ToString() == "False")
                        {
                            value = "0";
                        }
                    }

                    if (value == null)
                    {
                        if (e == properties[^1])
                        {
                            sql.Append("null");
                        }
                        else
                        {
                            sql.Append("null,");
                        }
                        continue;
                    }
                    // 如果有最大长度的限制,则需要对字符串进行截取。
                    int? maxLength = null;
                    var maxLengthAttr = e.CustomAttributes.Where(a => IsHaveMaxLength(a.AttributeType)).FirstOrDefault();
                    if (maxLengthAttr != null && maxLengthAttr.ConstructorArguments.Where(e => e.ArgumentType == typeof(int)).Select(e => e.Value).FirstOrDefault() != null)
                    {
                        maxLength = (int)maxLengthAttr.ConstructorArguments.Where(e => e.ArgumentType == typeof(int)).Select(e => e.Value).FirstOrDefault();
                    }

                    sql.Append("'");

                    sql.Append((maxLength.HasValue && value?.ToString().Replace("'", "").Replace("\\", "").Trim().Length > maxLength.Value) ? value?.ToString().Replace("'", "").Replace("\\", "").Trim()[..maxLength.Value] : value?.ToString().Replace("'", "").Replace("\\", "").Trim());
                    
                    if (e == properties[^1])
                    {
                        sql.Append("'");
                    }
                    else
                    {
                        sql.Append("',");
                    }
                }
                if (a != sources[^1])
                {
                    sql.Append("),");
                }
                else
                {
                    sql.Append(')');
                }
            });
            return sql.ToString();
        }
        /// <summary>
        /// 判断是否存在maxlength特性
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private static bool IsHaveMaxLength(Type type)
        {
            while (type.BaseType != typeof(object))
            {
                if (type == typeof(MaxLengthAttribute))
                {
                    return true;
                }
                return IsHaveMaxLength(type.BaseType);
            }
            return false;
        }

需要优化但我不会优化的地方:生成sql的时候会花费50s左右,我觉得对于5W条数据来说太长了,我本来以为最多超不过10s,没想到竟然花费了这么久,不知哪位可以帮忙给看看哪里有优化的空间。

posted @ 2022-04-02 17:15  嬴の政  阅读(584)  评论(0)    收藏  举报