【转】https://www.jianshu.com/p/dff3c684a0e4
源代码下载地址在最后
知识要求: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 TestEntities { get; set; }
}
有了以上代码,我们就可以使用code first 命令创建数据库,前提是,在配置文件里,添加了数据库连接字符串
有了以上内容,接下来,我们需要生产多条数据,比如,生产100000条数据,具体的代码如下
///
/// 产生需要生产的数据
///
///
private static IEnumerable GetInsertDatas()
{
// 线程安全的list
ConcurrentBag datas=new ConcurrentBag();
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会很快的产生数据即可!
我们获取了数据源,那么,如何对这些数据进行插入呢,看如下的测试代码:
///
/// 批量插入
///
private static void BatchInster()
{
var datas = GetInsertDatas();
var testEntities
= datas as IList ?? 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,在然后批量更新修改后的数据
代码如下
///
/// 批量更新
///
private static void BatchUpdate()
{
IEnumerable toUpdates=new List();
// 获取所有数据
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,
代码如下
///
/// 将id >= 1w 小于 5w 的随机值等于 500
///
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进行批量删除呢?
看代码
///
/// 删除所有数据
///
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 强大)
什么情况下,会用到批量操作?
我遇到的有:导入数据、录入多条数据、批量计算然后保存每一条数据等等………………
作者:诸葛_小亮
链接:https://www.jianshu.com/p/dff3c684a0e4
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。