C# EF 批量操作

作者: 诸葛_小亮 | 来源:发表于2016-12-09 23:41 被阅读2136次

    背景

    源代码下载地址在最后
    知识要求:ef code first
    ef 批量操作是最近遇到的一个新问题,ef这个orm为我们解决了大量的curd操作,但是,对于批量操作,其性能一直没有很好的方案,不管是 foreach 方式,还是 addorupdate(这个扩展内部实现原理还是一个一个add),当数据量很大的时候,其性能简直是不能容忍,差不多1万多的数据,需要等半个小时左右!
    于是开始着手寻找一个可以使用ef进行批量操作的的类库,开始用的是 zzz projects 的类库,但是其免费版,只有更新和删除,没有插入,使用非免费版,如果时间过期,会导致程序出问题(血的教训),于是有找啊找,皇天不负有心人,这里需要吐槽下百度,在百度上,基本上搜索到的答案,均是 zzz projects 的类库 或者 ef extend ,后者也没有 批量插入。
    不得已,我们翻出去看看,不会翻墙的程序员不是好的搬砖工,在google一搜,搜到了很漂亮,很好用的一个类库:EntityFramework.Utilities,地址:https://github.com/MikaelEliasson/EntityFramework.Utilities, 关键这货还是免费开源的……
    我们的故事就从这里开始!


    创建项目

    使用vs工具,创建一个控制台程序,并引入nuget 包:

    图片.png

    实体类如下:

    using System.ComponentModel.DataAnnotations;
    
        public class TestEntity
        {
            [Key]
            public int Id { get; set; }
    
            public int RandomVlue { get; set; }
        }
    

    上下文如下:

    public class BatchDemoContext
            : DbContext
        {
            public BatchDemoContext() : 
                base("Default")
            {
            }
    
            public IDbSet<TestEntity> TestEntities { get; set; }
            
        }
    

    有了以上代码,我们就可以使用code first 命令创建数据库,前提是,在配置文件里,添加了数据库连接字符串


    创建数据源

    有了以上内容,接下来,我们需要生产多条数据,比如,生产100000条数据,具体的代码如下

    
            /// <summary>
            ///     产生需要生产的数据
            /// </summary>
            /// <returns></returns>
            private static IEnumerable<TestEntity> GetInsertDatas()
            {
                // 线程安全的list
                ConcurrentBag<TestEntity> datas=new ConcurrentBag<TestEntity>();
    
                Parallel.For(0, 100000,
                    (index, state) =>
                        {
                            Random rand = new Random();
                            var newData = new TestEntity { RandomVlue = rand.Next(1, 100) };
                            datas.Add(newData);
                        });
    
                return datas;
            }
    
    

    为了生产数据快,使用了Parallel,不懂的可以自行 google,如果不能翻墙你就bing一下,或者是看《 C# 高级编程》 关于异步 的这章,这里,只需要知道,生产100000条数据,用parallel会很快的产生数据即可!


    批量插入

    我们获取了数据源,那么,如何对这些数据进行插入呢,看如下的测试代码:

    
            /// <summary>
            ///     批量插入
            /// </summary>
            private static void BatchInster()
            {
                var datas = GetInsertDatas();
                var testEntities
                    = datas as IList<TestEntity> ?? datas.ToList();
    
                Stopwatch watch =new Stopwatch();
                
                Console.WriteLine("开始插入计时,总共数据:{0}条",testEntities.Count());
                watch.Start();
    
                using (var context=new BatchDemoContext())
                {
                    EFBatchOperation.For(context,context.TestEntities)
                        .InsertAll(testEntities);
                }
    
                watch.Stop();
                Console.WriteLine("结束插入计时,工用时:{0}ms",watch.ElapsedMilliseconds);
    
    
                using (var context = new BatchDemoContext())
                {
                    var count = context.TestEntities.Count();
                    Console.WriteLine("数据库总共数据:{0}条",count);
    
                    var minId = context.TestEntities.Min(c => c.Id);
    
                    // 随机取十条数据进行验证
    
                    for (int i = 1; i <= 10; i++)
                    {
                        Random rand = new Random();
                        var id = rand.Next(minId, minId+ 100000);
                        
                        var testdata = context.TestEntities.FirstOrDefault(c => c.Id == id);
                        
                        Console.WriteLine("插入的数据 id:{0} randomvalue:{1}",testdata.Id,testdata.RandomVlue);
    
                    }
                    
                }
    
                Console.WriteLine("-----------------华丽的分割线   插入-------------------------");
            }
    
    

    用 Stopwatch 监测 插入所执行的时间
    通过使用代码

    using (var context=new BatchDemoContext()) { EFBatchOperation.For(context,context.TestEntities) .InsertAll(testEntities); }
    

    即可实现批量插入,然后,针对这10调数据,随机随十条进行验证,我们在生产数据的时候,默认的随机值不会超过100,如果这10条随机值,都是小于100的,可以认为插入成功。
    使用
    var minId = context.TestEntities.Min(c => c.Id);
    是为了保证,id条件最小,不然第二次运行程序,会出错,因为id是自增的,删除数据,id也不会从1开始!
    插入的运行结果如下:

    图片.png

    通过上图可以看出
    需要插入的数据有 100000条,插入仅用了 3070ms,这比原生态的ef要快了不知道多少倍……
    通过插入数据的id和randomvalue,可以看出,我们的数据,也的确是正确的插入到了数据库!


    批量更新

    通过上面的步骤,我们数据库里已经有100000条数据里,现在,我们将数据库里的数据,randomvalue 全部设置为 1000,于是我们需要,获取全部数据,然后并行运算,改randomvalue的值为100000,在然后批量更新修改后的数据
    代码如下

    
            /// <summary>
            ///     批量更新
            /// </summary>
            private static void BatchUpdate()
            {
                IEnumerable<TestEntity> toUpdates=new List<TestEntity>();
    
                // 获取所有数据
                using (var context = new BatchDemoContext())
                {
                    toUpdates = context.TestEntities.ToList();
                }
    
                // 所有的值 都为 1000
                Parallel.ForEach(toUpdates,
                    (entity, state) =>
                        { entity.RandomVlue = 1000; });
    
    
                Stopwatch watch = new Stopwatch();
    
                Console.WriteLine("开始更新计时,总共数据:{0}条", toUpdates.Count());
                watch.Start();
    
                using (var context = new BatchDemoContext())
                {
                    EFBatchOperation.For(context, context.TestEntities).UpdateAll(toUpdates, x => x.ColumnsToUpdate(c => c.RandomVlue));
                }
    
                watch.Stop();
                Console.WriteLine("结束更新计时,工用时:{0}ms", watch.ElapsedMilliseconds);
    
    
                using (var context = new BatchDemoContext())
                {
                    var count = context.TestEntities.Count();
                    Console.WriteLine("数据库总共数据:{0}条", count);
    
                    var minId = context.TestEntities.Min(c => c.Id);
    
                    // 随机取十条数据进行验证
    
                    for (int i = 1; i <= 10; i++)
                    {
                        Random Rand = new Random();
                        var id = Rand.Next(minId, minId+100000);
    
                        var testdata = context.TestEntities.FirstOrDefault(c => c.Id == id);
                        Console.WriteLine("更新的数据 id:{0} randomvalue:{1}", testdata.Id, testdata.RandomVlue);
    
                    }
    
                }
    
    
                Console.WriteLine("-----------------华丽的分割线   更新-------------------------");
    
            }
    
    

    通过

    using (var context = new BatchDemoContext()) { EFBatchOperation.For(context, context.TestEntities).UpdateAll(toUpdates, x => x.ColumnsToUpdate(c => c.RandomVlue)); }
    
    

    这条语句,告诉ef,需要更新哪个集合的randomvalue!
    然后随机取10条数据,发现,randomvalue的值全部都是 1000,说明我们批量更新成功!
    运行结果如下:

    图片.png

    查询更新

    什么是查询更新呢?就是当我们满足什么条件的时候,对属性进行什么操作,类似于 update table set col=value where id=1 这样的 sql 语句,和批量更新有什么区别的?我这边的批量更新,是指从数据库中加载的多个实体到内存,在内存中改变了属性值,在将这一批数据,更新到数据库,而查询更新,无需查询到内存!
    这里,我们将id大于等于 minid+10000的数据和 id 小于等于 minid+50000的数据进行改值,修改ran 的值为 500,
    代码如下

    
            /// <summary>
            ///     将id >= 1w  小于 5w 的随机值等于 500
            /// </summary>
            private static void BatchUpdateQuery()
            {
    
                Stopwatch watch = new Stopwatch();
    
                Console.WriteLine("开始查询更新计时");
                watch.Start();
    
                using (var context = new BatchDemoContext())
                {
    
                    var minId = context.TestEntities.Min(c => c.Id);
    
                    EFBatchOperation.For(context, context.TestEntities)
                        .Where(c=>c.Id>= minId+10000 && c.Id<= minId+50000)
                        .Update(c=>c.RandomVlue,rv=>500);
                }
    
                watch.Stop();
                Console.WriteLine("结束查询更新计时,工用时:{0}ms", watch.ElapsedMilliseconds);
    
                using (var context = new BatchDemoContext())
                {
                    var count = context.TestEntities.Count();
                    Console.WriteLine("数据库总共数据:{0}条", count);
    
                    var minId = context.TestEntities.Min(c => c.Id);
                    // 随机取十条数据进行验证
                    for (int i = 1; i <= 10; i++)
                    {
                        Random rand = new Random();
                        var id = rand.Next(minId+10000, minId+ 50000);
    
                        var testdata = context.TestEntities.FirstOrDefault(c => c.Id == id);
                        Console.WriteLine("查询更新的数据 id:{0} randomvalue:{1}", testdata.Id, testdata.RandomVlue);
    
                    }
    
                }
    
    
                Console.WriteLine("-----------------华丽的分割线  查询更新-------------------------");
    
    
            }
    
    
    

    通过代码

    
                    var minId = context.TestEntities.Min(c => c.Id);
    
                    EFBatchOperation.For(context, context.TestEntities)
                        .Where(c=>c.Id>= minId+10000 && c.Id<= minId+50000)
                        .Update(c=>c.RandomVlue,rv=>500);
    

    进行查询更新
    然后取十条数据进行验证,具体的运行结果如下:

    图片.png

    通过结果,我们可以看出,查询更新也执行成功了


    批量删除

    类似的sql语句是 : delete from table where id=1
    在ef里,删除只能是先获取,在remove,我们如何使用efUtilities进行批量删除呢?
    看代码

    
            /// <summary>
            ///     删除所有数据
            /// </summary>
            private static void BatchDelete()
            {
    
                Stopwatch watch = new Stopwatch();
    
                Console.WriteLine("开始删除计时");
                watch.Start();
    
                using (var context = new BatchDemoContext())
                {
                    EFBatchOperation.For(context, context.TestEntities)
                        .Where(c=>c.Id>=1).Delete();
                }
    
                watch.Stop();
                Console.WriteLine("结束删除计时,工用时:{0}ms", watch.ElapsedMilliseconds);
    
                using (var context = new BatchDemoContext())
                {
                    var count = context.TestEntities.Count();
                    Console.WriteLine("数据库总共数据:{0}条", count);
                    
                }
    
                Console.WriteLine("-----------------华丽的分割线  删除-------------------------");
    
            }
    
    
    

    使用代码

    
                using (var context = new BatchDemoContext())
                {
                    EFBatchOperation.For(context, context.TestEntities)
                        .Where(c=>c.Id>=1).Delete();
                }
    
    

    进行批量删除,当我们删除之后,数据库数据应该为空,即条目为0,为了验证是否删除,我们只需获取条目即可,运行结果如下

    图片.png

    实践证明,批量删除是成功的


    总结

    efUtilities地址:https://github.com/MikaelEliasson/EntityFramework.Utilities ,也可以从这里看到文档

    相比 zzz projects ,其提供的功能还算是很全的,批量插入,批量更新,查询更新和批量删除,但是, ef utilities 是 免费开源的,免费开源的,免费开源的,重要的事情说五遍,开源的代码,我们可以学习甚至是改造,打造符合自己的代码!

    相比 ef extend , ef utilities 提供的功能全面,基本上是 extend有的,utilities 有,extend 没有的,utilities 也有,(只针对批量操作,查询方面,还是extend 强大)

    什么情况下,会用到批量操作?
    我遇到的有:导入数据、录入多条数据、批量计算然后保存每一条数据等等………………


    qq:1260825783
    源代码:https://git.oschina.net/zhaord/EfBatchDemo.git
    转载注明:http://www.jianshu.com/p/dff3c684a0e4


    我的公总号

    相关文章

      网友评论

      本文标题:C# EF 批量操作

      本文链接:https://www.haomeiwen.com/subject/jpeymttx.html