第二十六节:再探批处理框架[Zack.EFCore.Batch]、[EFCore.BulkExtensions]、[Z.EntityFramework.Plus.EFCore]

一. 批处理-[Zack.EFCore.Batch]

(一). 说明

1. 简介

  (详细用法见GitHub:https://github.com/yangzhongke/Zack.EFCore.Batch) 【开源免费】

  使用这个开发包, EFCore用户可以使用LINQ语句删除或者更新多条数据库记录,操作只执行一条SQL语句并且不需要首先把实体对象加载到内存中;通过该程序包基于SQLBulkCopy实现了批量插入。这个开发包支持 Entity Framework Core 5/6。 

  支持的数据库有:SQLServer、MySQL、Postgresql、Sqlite、Oracle、Dm(达梦)、InMemory(内存数据库)

2. 安装

  下面以SQLServer+.EFCore6为例,其它DB的操作参考github

  (1). EFCore必备程序集的安装,【Microsoft.EntityFrameworkCore】【Microsoft.EntityFrameworkCore.SqlServer】【Microsoft.EntityFrameworkCore.Tools】

  (2). 安装【Zack.EFCore.Batch.MSSQL_NET6   6.0.15】,然后在OnConfiguring中添加 optionsBuilder.UseBatchEF_MSSQL();

  (3). 在OnConfiguring中配置日志,用来输出翻译后的sql语句, 这里只能用系统默认的日志才能输出来

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            //方式2:使用系统日志
            optionsBuilder.LogTo(msg =>
            {
                if (msg.Contains("CommandExecuting")||msg.Contains("Sql"))
                {
                    Console.WriteLine(msg);
                }
            });

            optionsBuilder.UseSqlServer("Server=localhost;Database=EFCore6xDB;User ID=sa;Password=123456;");
            optionsBuilder.UseBatchEF_MSSQL();// MSSQL Server 用户用这个
        }

二. 各种用法

1. 批量插入

    调用BulkInsertAsync方法进行批量插入,对于SQLServer内部采用的事输出日志无法捕获到翻译的sql.

代码分享

{
    using var db = new EFCore6xDBContext();
    List<UserInfo> userList = new List<UserInfo>();
    for (int i = 1; i <= 40; i++)
    {
        UserInfo user = new UserInfo()
        {
            id = Guid.NewGuid().ToString("N"),
            userName = "ypf1_" + i,
            userPwd = "123456",
            userAge = 19,
            userGender = "男",
            addTime = DateTime.Now,
            delflag = 0
        };
        userList.Add(user);
    }
    await db.BulkInsertAsync(userList);
    Console.WriteLine("新增成功");
}

2. 批量删除.

  此处有两种写法都是调用DeleteRangeAsync方法,如下代码,二者翻译的SQL语句相同

代码分享:

{
    using var db = new EFCore6xDBContext();
    //写法1:
    int count = await db.DeleteRangeAsync<UserInfo>(u => u.delflag == 1 && u.userName.Contains("ypf"));
    //写法2
    //int count = await db.Set<UserInfo>().Where(u => u.delflag == 1 && u.userName.Contains("ypf")).DeleteRangeAsync<UserInfo>(db);
    Console.WriteLine($"删除成功,删除条数为{count}");
}

翻译的sql语句为:

写法1:

   Delete FROM [UserInfo] WHERE [id] IN
    (SELECT [id] FROM
      (SELECT [u].[id], [u].[addTime], [u].[delflag], [u].[userAge], [u].[userGender], [u].[userName], [u].[userPwd]
        FROM [UserInfo] AS [u]
        WHERE ([u].[delflag] = 1) AND ([u].[userName] LIKE '%ypf%'))  AS  temp1 )

写法2:

 Delete FROM [UserInfo] WHERE [id] IN
   (SELECT [id] FROM
     (SELECT [u].[id], [u].[addTime], [u].[delflag], [u].[userAge], [u].[userGender], [u].[userName], [u].[userPwd]
      FROM [UserInfo] AS [u]
      WHERE ([u].[delflag] = 1) AND ([u].[userName] LIKE '%ypf%'))  AS  temp1 )

3. 批量修改

  调用BatchUpdate+Set+ExecuteAsync方法,支持基于原值修改,也支持直接改为全新的值

代码分享:

{
    using var db = new EFCore6xDBContext();
    int count = await db.BatchUpdate<UserInfo>()
                .Set(u => u.userName, u => u.userName + "hh")
                .Set(u => u.userGender, "女")             //此处可以写成 "女", 也可以写成  u=>"女"
                .Set(u => u.addTime, u => DateTime.Now)   //此处必须写成 u=>DateTime.Now, 单独写一个 DateTime.Now报错
                .Where(u => u.delflag == 1)
                .ExecuteAsync();
    Console.WriteLine($"修改成功,修改条数为{count}");
}

翻译的SQL语句为:

Update [UserInfo] SET [userName] = COALESCE([userName], '') + 'hh', [userGender] = N'女', [addTime] = GETDATE()
   WHERE [id] IN(SELECT [id] FROM (SELECT [u].[id], [u].[addTime], [u].[delflag], [u].[userAge], [u].[userGender], [u].[userName], [u].[userPwd]
   FROM [UserInfo] AS [u]
   WHERE [u].[delflag] = 1)  AS  temp1 )

4. Take和Skip用法

   和正常用法一样

代码分享:

{
    using var db = new EFCore6xDBContext();
    int count = await db.Set<UserInfo>().Where(u => u.delflag == 1 && u.userName.Contains("ypf"))
                      .Skip(1).Take(1)
                      .DeleteRangeAsync<UserInfo>(db);
    Console.WriteLine($"删除成功,删除条数为{count}");

}

SQL语句

Delete FROM [UserInfo] WHERE [id] IN
  (SELECT [id] FROM (SELECT [u].[id], [u].[addTime], [u].[delflag], [u].[userAge], [u].[userGender], [u].[userName], [u].[userPwd]
      FROM [UserInfo] AS [u]
      WHERE ([u].[delflag] = 1) AND ([u].[userName] LIKE '%ypf%')
      ORDER BY (SELECT 1)
      OFFSET @__p_0 ROWS FETCH NEXT @__p_0 ROWS ONLY)  AS  temp1 )

5. 事务内操作

  经测试:批量插入不能放到事务里批量删除 和 批量修改可以放到事务里

代码分享:

查看代码

{
    using var db = new EFCore6xDBContext();
    using (var transaction = db.Database.BeginTransaction())
    {
        try
        {
            //1. 批量插入【不能放到事务里】
            //List<UserInfo> userList = new List<UserInfo>();
            //for (int i = 1; i <= 4; i++)
            //{
            //    UserInfo user = new UserInfo()
            //    {
            //        id = Guid.NewGuid().ToString("N"),
            //        userName = "ypf1_" + i,
            //        userPwd = "123456",
            //        userAge = 19,
            //        userGender = "男",
            //        addTime = DateTime.Now,
            //        delflag = 0
            //    };
            //    userList.Add(user);
            //}
            //await db.BulkInsertAsync(userList);


            //2-批量删除【可以放到事务里】
            int count1 = await db.DeleteRangeAsync<UserInfo>(u => u.delflag == 0);


            //3. 批量修改 【可以放到事务里】
            int count2 = await db.BatchUpdate<UserInfo>()
                        .Set(u => u.userName, u => u.userName + "hh")
                        .Set(u => u.userGender, "女")             //此处可以写成 "女", 也可以写成  u=>"女"
                        .Set(u => u.addTime, u => DateTime.Now)   //此处必须写成 u=>DateTime.Now, 单独写一个 DateTime.Now报错
                        .Where(u => u.delflag == 0)
                        .ExecuteAsync();


            //4. 模拟错误
            UserInfo user2 = new UserInfo()
            {
                id = Guid.NewGuid().ToString("N") + "354545",  //模拟错误
                userName = "ypf2_",
                userPwd = "123456",
                userAge = 19,
                userGender = "男",
                addTime = DateTime.Now,
                delflag = 0
            };
            db.Add(user2);
            db.SaveChanges();


            //最终事务提交
            transaction.Commit();
            Console.WriteLine("操作成功");
        }
        catch (Exception ex)
        {
            //using包裹不需要手写rollback
            Console.WriteLine("错误为:" + ex.Message);
        }
    }


}

 

二.  批处理-[EFCore.BulkExtensions]

(一). 说明

1. 简介

  详见:https://github.com/borisdj/EFCore.BulkExtensions 【开源免费】

  该框架实现了实现了批量插入、更新、删除、读取等核心功能,支持SQLServer(2012+)、PostgreSQL、SQLite数据库(不支持MySQL)

(1).SQLServer 底层使用SqlBulkCopy进行插入,更新/删除将 BulkInsert 与原始 Sql MERGE结合。

(2).PostgreSQL 使用COPY BINARY 结合 ON CONFLICT进行更新(从 v6+ 开始支持)。

(3).对于SQLite,没有Copy工具,而是库使用结合了UPSERT的普通 SQL 。

PS:最新版支持MySQL了? 尚未测试,待补充

 

2. 安装

下面以SQLServer+.EFCore6为例

(1). 安装EFCore必备程序集,【Microsoft.EntityFrameworkCore】【Microsoft.EntityFrameworkCore.SqlServer】【Microsoft.EntityFrameworkCore.Tools】

(2). 安装程序集【EFCore.BulkExtensions6.5.1】

(3). 配置系统日志进行监测生成的SQL语句

注:如果使用 Windows 身份验证,则在 ConnectionString 中应该有Trusted_Connection=True; 因为需要 Sql 凭据才能保持连接,否则会报错【证书链是由不受信任的颁发机构颁发的】

【optionsBuilder.UseSqlServer("Server=localhost;Database=EFCore6xDB;User ID=sa;Password=123456;TrustServerCertificate=true");】

代码分享:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            //方式2:使用系统日志
            optionsBuilder.LogTo(msg =>
            {
               if (msg.Contains("CommandExecuting")||msg.Contains("Sql"))
                {
                    Console.WriteLine(msg);
                }
            });
            optionsBuilder.UseSqlServer("Server=localhost;Database=EFCore6xDB;User ID=sa;Password=123456;TrustServerCertificate=true");
        }

(二).  各种用法

1. 批量插入

  调用BulkInsertAsync方法,传入List集合即可实现批量插入, 无法捕获SQL语句

代码分享:

{
    using var db = new EFCore6xDBContext();
    List<UserInfo> userList = new List<UserInfo>();
    for (int i = 0; i < 100; i++)
    {
        UserInfo user = new UserInfo()
        {
            id = Guid.NewGuid().ToString("N"),
            userName = "ypf" + i,
            userPwd = "123456",
            userAge = 19,
            userGender = "男",
            addTime = DateTime.Now,
            delflag = 0
        };
        userList.Add(user);
    }
    await db.BulkInsertAsync(userList);
    Console.WriteLine("插入成功");
}

2. 批量删除.

 写法1:调用BulkDelete方法,直接传入要删除的实体,必须包含主键值

 写法2:配合where+BulkDelete方法,进行条件删除 【推荐_常用】

 写法3:全部删除,支持原生truncate方法

代码分享: 

{
    using var db = new EFCore6xDBContext();

    //写法1
    //{
    //    List<UserInfo> userList = new List<UserInfo>();
    //    userList.Add(new UserInfo() { id = "00163a74ee27434286e6503a6c87eaeb" });
    //    userList.Add(new UserInfo() { id = "00c1839adaf94620af7f1418e013a1d0" });
    //    await db.BulkDeleteAsync(userList);
    //}

    //写法2
    //{
    //    int count = await db.Set<UserInfo>().Where(u => u.delflag == 2 && u.userName.Contains("ypf")).BatchDeleteAsync();
    //    Console.WriteLine($"删除的条数为:{count}");
    //}

    //写法3-truncate全部删除
    //{
    //    await db.TruncateAsync<UserInfo>();
    //}

    Console.WriteLine("删除成功");

}

写法2翻译的SQL语句:【直接翻译成了delete  xxx  where xxxx,性能高】

 DELETE [u]
    FROM [UserInfo] AS [u]
    WHERE ([u].[delflag] = 2) AND ([u].[userName] LIKE '%ypf%')

写法3翻译的SQL语句为:

 TRUNCATE TABLE [dbo].[UserInfo];

3. 批量修改

   写法1:实现条件修改, where中查询条件,BatchUpdateAsync中使用表达式的形式可以基于原值的基础上进行修改,也可以直接修改为一个新的值

   写法2:实现条件修改, where中查询条件,BatchUpdateAsync中传入一个简单实体,可以给他赋一个新值(注:这种写法不能基于原值修改)

   写法3:更新的同时,赋默认值,通过传入一个新的参数来实现

代码分享: 

{
    using var db = new EFCore6xDBContext();

    //写法1--Expression表达式
    {
        int count = await db.Set<UserInfo>().Where(u => u.delflag == 0).BatchUpdateAsync(u => new UserInfo
        {
            userAge = u.userAge + 2,
            userName = u.userName + "_test",
            userGender = "女"
        });
        Console.WriteLine(count);
    }

    //写法2-简单实体
    //{
    //    int count = await db.Set<UserInfo>().Where(u => u.delflag == 0).BatchUpdateAsync(new UserInfo
    //    {
    //        userGender = "男男",
    //        userAge = 25
    //    });
    //    Console.WriteLine(count);
    //}

    //写法3--更新的同时,赋默认值
    //{
    //    var updateCols = new List<string>() { nameof(UserInfo.userGender) }; //结果userGender字段会都被更新为Null
    //    int count = await db.UserInfo.Where(u => u.delflag == 0).BatchUpdateAsync(new UserInfo { userAge = 18 }, updateCols);
    //    Console.WriteLine(count);
    //}

    Console.WriteLine("更新成功");

}

写法1的SQL语句:

 UPDATE u SET  [u].[userAge] = [u].[userAge] + @param_0 , [u].[userName] = [u].[userName] + @param_1 , [u].[userGender] = @param_2
      FROM [UserInfo] AS [u]
      WHERE [u].[delflag] = 0

写法2的SQL语句:

 UPDATE u SET [userAge] = @userAge, [userGender] = @userGender
      FROM [UserInfo] AS [u]
      WHERE [u].[delflag] = 0

写法3的SQL语句:

  UPDATE u SET [userAge] = @userAge, [userGender] = @userGender
      FROM [UserInfo] AS [u]
      WHERE [u].[delflag] = 0

4. 手动修改配置

   可以手动修改批量操作的配置,详见下图

代码分享: 

{
    using var db = new EFCore6xDBContext();
    var bulkConfig = new BulkConfig { SetOutputIdentity = true, BatchSize = 4000 };
    List<UserInfo> userList = new List<UserInfo>();
    for (int i = 0; i < 10; i++)
    {
        UserInfo user = new UserInfo()
        {
            id = Guid.NewGuid().ToString("N"),
            userName = "ypf" + i,
            userPwd = "123456",
            userAge = 19,
            userGender = "男",
            addTime = DateTime.Now,
            delflag = 0
        };
        userList.Add(user);
    }
    db.BulkInsert(userList, bulkConfig);
}

5. 事务内操作

  上述的批量增加、修改、删除,一条语句都是对应一个事务,直接生效,如果想让多条批量操作作为一个整体生效,需要用事务包裹。【特别注意:批量增加、删除、修改都可以放到事务里生效】

  详见代码测试,模拟错误,上面的批量增加、修改、删除都没有成功

代码分享: 

查看代码

{
    using var db = new EFCore6xDBContext();
    using (var transaction = db.Database.BeginTransaction())
    {
        try
        {
            //1. 批量插入【可以放到事务里】
            List<UserInfo> userList = new List<UserInfo>();
            for (int i = 1; i <= 4; i++)
            {
                UserInfo user = new UserInfo()
                {
                    id = Guid.NewGuid().ToString("N"),
                    userName = "ypf1_" + i,
                    userPwd = "123456",
                    userAge = 19,
                    userGender = "男",
                    addTime = DateTime.Now,
                    delflag = 0
                };
                userList.Add(user);
            }
            await db.BulkInsertAsync(userList);


            //2.批量删除【可以放到事务里】
            await db.Set<UserInfo>().Where(u => u.delflag == 0 && u.userName.EndsWith("3")).BatchDeleteAsync();


            //3.批量修改 【可以放到事务里】

            await db.Set<UserInfo>().Where(u => u.delflag == 0).BatchUpdateAsync(u => new UserInfo
            {
                userAge = u.userAge + 2,
                userName = u.userName + "_test",
                userGender = "女"
            });

            //4. 模拟错误
            UserInfo user2 = new UserInfo()
            {
                id = Guid.NewGuid().ToString("N") + "354545",  //模拟错误
                //id = Guid.NewGuid().ToString("N"),  
                userName = "ypf2_",
                userPwd = "123456",
                userAge = 19,
                userGender = "男",
                addTime = DateTime.Now,
                delflag = 0
            };
            db.Add(user2);
            db.SaveChanges();


            //最终事务提交
            transaction.Commit();
            Console.WriteLine("操作成功");
        }
        catch (Exception ex)
        {
            //using包裹不需要手写rollback
            Console.WriteLine("错误为:" + ex.Message);
        }
    }

}

6. 其它

  (1). BulkSaveChanges

  (2). BulkRead

  (3). BulkInsertOrUpdate

  (4). BulkInsertOrUpdateOrDelete

 

三. 批处理-[Z.EntityFramework.Plus.EFCore]

1. 简介

     Z.EntityFramework.Plus.EFCore 是免费版本的,支持SQLServer和MySQL, 但是功能有限,仅支持批量删除和修改,其它操作需要使用收费版本

 收费版本的程序集名称【Z.EntityFramework.Extensions.EFCore】,这个时候你会发现【Z.EntityFramework.Plus.EFCore】有需要依赖【Z.EntityFramework.Extensions.EFCore】

 太坑了,我是不会用了, 动不动过期了,让下载最新版本,拜拜

 

 

 

 

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @   Yaopengfei  阅读(1839)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2018-06-19 ORM系列之Entity FrameWork详解
点击右上角即可分享
微信分享提示