关于EF中批量添加的个人探索

Posted 咕咚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于EF中批量添加的个人探索相关的知识,希望对你有一定的参考价值。

实际的测试代码和数据记录,还有最终的总结都在下面:

        /// <summary>
        /// 这种做法,不用了说了,每次遍历都会打开一次db链接,然后执行insert操作;
        /// </summary>
        static void CreateBluckInsertData0()
        {
            using (var context = new SiteDbContext())
            {
                List<Role> list = new List<Role>();
                var count = 1000;
                for (int i = 0; i < count; i++)
                {
                    var entity = new Role()
                    {
                        RoleName = "普通员工" + i
                    };
                    context.Roles.Add(entity);
                    context.SaveChanges();
                }

            }
        }

        /// <summary>
        /// 初看,觉得,这样做( context.SaveChanges()方在for循环外面)挺好的,
        /// 实际跟踪slq发现,还是执行了1000的插入操作,只不过没有在for循环里面;
        /// 而是在我们的for循环外;总结:不可取;
        /// </summary>
        static void CreateBluckInsertData1()
        {
            using (var context = new SiteDbContext())
            {
                List<Role> list = new List<Role>();
                var count = 1000;
                for (int i = 0; i < count; i++)
                {
                    var entity = new Role()
                    {
                        RoleName = "普通员工" + i
                    };
                    context.Roles.Add(entity);
                }
                context.SaveChanges();//会增加与数据库的交互次数
                //EF的一个上下文在提交时会打开一个数据连接,然后把转换成的SQL语句一条一条的发到数据库端,然后去提交
            }
        }

        /// <summary>
        /// 拼接字符串;组装后一次性操作;确定,传递的字符数量就会很多;网络压力增加;但不管怎样都比上面两张好;
        /// </summary>
        static void CreateBluckInsertData2()
        {
            Stopwatch watch = Stopwatch.StartNew();
            var count = 92000;
            using (var context = new SiteDbContext())
            {
                var bluckString = new StringBuilder();
                for (int i = 0; i < count; i++)
                {

                    bluckString.Append("INSERT INTO ROLES(RoleName) VALUES(\'");
                    bluckString.Append("WORKER");
                    bluckString.Append(i);
                    bluckString.Append("\');");
                }

                var result = bluckString.ToString();
                Console.WriteLine(string.Format("拼接字符串花费的时间:{0} milliseconds.",watch.ElapsedMilliseconds));
                //然后这里再来一次性批量的进行插入操作;
                watch.Restart();
                context.Database.ExecuteSqlCommand(result);
                //这样做的好处就是,可以一次性,全部插入,缺点就是;发送大量的insert 文本信息;
                //1000 customers are created, cost 1777 milliseconds.
                //5000 customers are created, cost 1906 milliseconds.
                //9000 customers are created, cost 2354 milliseconds.
                //9000 customers are created, cost 2023 milliseconds.
                //这样的计算比较草率;
            }
            watch.Stop();
            Console.WriteLine(string.Format("{0} customers are created, cost {1} milliseconds.", count.ToString(), watch.ElapsedMilliseconds));

            //结果:拼接字符串花费的时间:97 milliseconds.
            //12000 customers are created, cost 2028 milliseconds.

            //拼接字符串花费的时间:99 milliseconds.
            //22000 customers are created, cost 2336 milliseconds.

            //拼接字符串花费的时间:118 milliseconds.
            //92000 customers are created, cost 6553 milliseconds.
        }

        /// <summary>
        /// 这里我们使用第三种方法;
        /// 网上提供的插件的方法;
        /// 你以为,插件的方法,就是单纯的封装上面的操作?太年轻了,俺都没监测到一条insert 语句;那么它是怎么做的呢?
        /// 
        /// </summary>
        static void CreateBluckInsertData3()
        {
            Stopwatch watch = Stopwatch.StartNew();
            var count = 92000;
            using (var context = new SiteDbContext())
            {
                List<Role> list = new List<Role>();
                for (int i = 0; i < count; i++)
                {
                    var entity = new Role()
                    {
                        RoleName = "普通员工" + i
                    };
                    list.Add(entity);
                    //context.Roles.Add(entity); //fuck stupid;
                }
                Console.WriteLine(string.Format("拼接对象花费的时间:{0} milliseconds.", watch.ElapsedMilliseconds));
                //然后这里再来一次性批量的进行插入操作;
                watch.Restart();
                context.BulkInsert(list);
                context.BulkSaveChanges();
            }
            watch.Stop();
            Console.WriteLine(string.Format("{0} customers are created, cost {1} milliseconds.", count.ToString(), watch.ElapsedMilliseconds));
            //1000 customers are created, cost 2865 milliseconds
            //5000 customers are created, cost 18207 milliseconds.
            //9000 customers are created, cost 51134 milliseconds. (发现是方法用错了,窝草).不要把 context.Roles.Add(entity); 添加在for循环中;
            //然后结果是这样的:9000 customers are created, cost 2320 milliseconds.
            //效率明显比上面的方法提高了很多;
            //由此可见,我们的批量,效果操作,并不由之前那种方法高呢;

            //拼接对象花费的时间:102 milliseconds.
            //12000 customers are created, cost 2238 milliseconds.

            //拼接对象花费的时间:101 milliseconds.    
            //22000 customers are created, cost 2258 milliseconds.

            ///拼接对象花费的时间:123 milliseconds.
           // 92000 customers are created, cost 3054 milliseconds.

            //这种方式的优势就不断体现出来了;


           //总结,凭借,字段串额效果,要比拼接对象集合的效率要高一些;
           //然后,就是我们的

        }


        //可能涉及到一些批量数据迁移的时候;

        ///总结:
        public void Info()
        {
            //凭借字段串的效率比拼接对象List的效率要高一些;

            //方式2的缺点在于,要传送大量的sql语句到我们的db中去执行,

            //方式3的实现方式和方式一有着本质的区别;是通过;

            //是数据小于五万条的时候,方式2的效率高,随着数据量的增加;方式3的优势就体现出来了

            //如果实际的开发中遇到大数据的批量操作;建议还是是用插件方式,就是我们的的方式3;

            //ps 操作中犯了一个错,是list.add(entity) 而不是 context.Roles.Add(entity); //fuck stupid;


        }


        static void Main(string[] args)
        {

            HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();

            //CreateBluckInsertData1();
            //CreateDB();
            //CreateBluckInsertData3();
            // CreateBluckInsertData2();

            Console.ReadLine();

        }

 

当然,这里还有我们的另外一种做法;

SqlBulkCopy 接口描述

Microsoft SQL Server 提供一个称为 bcp 的流行的命令提示符实用工具,用于将数据从一个表移动到另一个表(表既可以在同一个服务器上,也可以在不同服务器上)。 SqlBulkCopy 类允许编写提供类似功能的托管代码解决方案

似乎这种效率更高一些(不过,俺没有去测)

 

阅读资料后,发现,z的扩展插件使用的就是我们的sqlbulkcopy接口滴呀;

大致的流程如下:

  • 在SQL Server中创建一张临时表;
  • 使用.NET SqlBulkCopy将数据批量插入临时表;
  • 在临时表和目标表之间执行一条SQL语句;
  • 从SQL Server删除临时表。

参考文献:

http://www.cnblogs.com/gaochundong/p/entity_framework_bulk_insert_extension.html 

这里有一偏使用心得:

https://www.cnblogs.com/mobydick/archive/2011/08/28/2155983.html

还有这个:

https://www.cnblogs.com/zfanlong1314/archive/2013/02/05/2892998.html

 

以上是关于关于EF中批量添加的个人探索的主要内容,如果未能解决你的问题,请参考以下文章

EF6基础系列(12)--- EF进行批量添加/删除

几个关于js数组方法reduce的经典片段

几个关于js数组方法reduce的经典片段

EF大数据批量处理----BulkInsert

python 用于数据探索的Python代码片段(例如,在数据科学项目中)

EF中的批量操作