第二十五节:再探IQueryable和IEnumerable、EFCore调用SQL语句、增删改翻译后的SQL剖析(是否批处理)

一. 再探IQueryable 和 IEnumerable

1. 二者生成SQL的区别

   普通集合的版本(IEnumerable)是在内存中过滤(客户端评估),而IQueryable版本则是把查询操作翻译成SQL语句,在DB中操作。

代码分享:

{
    using var db = new EFCore6xDBContext();
    //在DB中操作
    IQueryable<UserInfo> userInfo = db.UserInfo.Where(u => u.userName.Contains("ypf"));
    foreach (var item in userInfo)
    {
        Console.WriteLine($"{item.userName},{item.userGender},{item.addTime}");
    }
    //在内存中操作
    IEnumerable<UserInfo> userInfo2 = db.UserInfo.Where(u => u.userName.Contains("ypf"));
    foreach (var item2 in userInfo2)
    {
        Console.WriteLine($"{item2.userName},{item2.userGender},{item2.addTime}");
    }

}

2. IQueryable的延迟执行

 (1). IQueryable只是代表一个“可以放到数据库服务器去执行的查询”,它没有立即执行,只是“可以被执行”而已。

 (2). 对于IQueryable接口调用非终结方法的时候不会执行查询,而调用终结方法的时候则会立即执行查询。

 (3). 终结方法:遍历、ToArray()、ToList()、Min()、Max()、Count()等;

      非终结方法:GroupBy()、OrderBy()、Include()、Skip()、Take()等。

 (4). 简单判断:一个方法的返回值类型如果是IQueryable类型,那么这个方法一般就是非终结方法,否则就是终结方法。

 (5). 作用:用于拼接动态查询 

代码分享:

{
    using var db = new EFCore6xDBContext();
    //在DB中操作
    IQueryable<UserInfo> u1 = db.UserInfo.Where(u => u.userName.Contains("ypf"));
    IQueryable<UserInfo> u2 = u1.Where(u => u.addTime < DateTime.Now);
    //最终执行sql语句
    var data = u2.ToList();
}

3. 分页

  这里使用LongCount,输出总页数和总条数

代码分享:

{
    static void PrintMsg(int pageIndex, int pageSize)
    {
        using var db = new EFCore6xDBContext();
        var data = db.UserInfo.Skip((pageIndex - 1) * pageSize).Take(pageSize);
        foreach (var item in data)
        {
            Console.WriteLine(item.userName, item.userPwd);
        }
        long count = data.LongCount();
        long pageCount = (long)Math.Ceiling(count * 1.0 / pageSize);
        Console.WriteLine($"总条数:{count},总页数{pageCount}");
    }
    //调用
    PrintMsg(1, 2);
}

4. 使用场景

(1).DataReader 和 DataTable

  DataReader:分批从数据库服务器读取数据。内存占用小、 DB连接占用时间长;

  DataTable:把所有数据都一次性从数据库服务器都加载到客户端内存中。内存占用大,节省DB连接。

(2).IQueryable的使用方式

  IQueryable内部就是在调用DataReader。

  优点:节省客户端内存。缺点:如果处理的慢,会长时间占用连接

(3). 如何一次性加载数据到内存?

  一次性加载数据到内存:用IQueryable的ToArray()、ToArrayAsync()、ToList()、ToListAsync()等方法。

(4). 什么情况下需要一次性加载到内存中?

  场景1:遍历IQueryable并且进行数据处理的过程很耗时。

  场景2:如果方法需要返回查询结果,并且在方法里销毁DbContext的话,是不能返回IQueryable的。必须一次性加载返回。

  场景3:多个IQueryable的遍历嵌套。很多数据库的ADO.NET Core Provider是不支持多个DataReader同时执行的。把连接字符串中的MultipleActiveResultSets=true删掉,其他数据库不支持这个。

 

二. EFCore调用SQL语句

1. 非查询类语句(增删改)

 (1).用法:推荐使用ExecuteSqlInterpolated()方法来执行原生的非查询SQL语句

 (2).剖析: 使用内插法传入参数,ExecuteSqlInterpolated()方法会自动进行参数化处理,防止SQL注入,详见截图

 (3).补充:除此之外,还有ExecuteSqlRaw()方法可以使用,但是需要手动SqlParameter 处理(详见 https://www.cnblogs.com/yaopengfei/p/11459170.html)

代码分享:

{
    using var db = new EFCore6xDBContext();

    int count = db.Database.ExecuteSqlInterpolated(@$"insert into UserInfo (id,userName,userPwd,userGender,userAge,addTime,delflag)
                                        values({Guid.NewGuid().ToString("N")},'ypf','12345','男',20,{DateTime.Now},0)");

    FormattableString sql1 = @$"insert into UserInfo (id,userName,userPwd,userGender,userAge,addTime,delflag)
                                        values({Guid.NewGuid().ToString("N")},'ypf','12345','男',20,{DateTime.Now},0)";
    int count2 = db.Database.ExecuteSqlInterpolated(sql1);

    Console.WriteLine($"新增成功,{count}");
}

SQL截图:

2. 实体查询

 (1).用法:推荐使用FromSqlInterpolated()方法来执行一个查询SQL语句,同样使用字符串内插来传递参数防止SQL注入

 (2).补充: 除此之外,还可以使用FromSqlRaw,但需要手动SqlParameter 处理 (详见 https://www.cnblogs.com/yaopengfei/p/11459170.html)

代码分享

{
    using var db = new EFCore6xDBContext();
    var data = db.Set<UserInfo>().FromSqlInterpolated(@$"select * from UserInfo where addTime < {DateTime.Now}").ToList();
    Console.WriteLine($"查询成功,{data}");
}

SQL截图

 

3. 任意查询

   使用EFCore可以声明个实体来接收,建议用ADO.Net  或 Dapper

 

三. 增删改查翻译后的SQL剖析(是否批处理)

1. 新增

  结论:使用Add或AddRange方法,当数量<=3,生成的多条insert,当数量>3, 则合并生成1条insert,详见截图

代码分享:

{

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


    //using var db = new EFCore6xDBContext();
    //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.Set<UserInfo>().AddRangeAsync(userList);
    //await db.SaveChangesAsync();
    //Console.WriteLine("新增成功");

}

SQL截图-小于3

SQL截图-大于3 

 2. 修改

  在EFCore中必须先查询出来,然后执行修改操作,并不能直接 update xxxx where xxx

  结论:当数量<=3,生成的多条update,当数量>3, 则合并生成1次请求了,但仍然是多个update,详见截图

代码分享:

{
    using var db = new EFCore6xDBContext();
    var userList = db.Set<UserInfo>().Take(4).ToList();
    foreach (var item in userList)
    {
        item.addTime = DateTime.Now;
        item.delflag++;
    }
    await db.SaveChangesAsync();
    Console.WriteLine("修改成功");
}

SQL截图-小于3

SQL截图-大于3

 

3. 删除

  在EFCore中必须先查询出来,然后执行修改操作,并不能直接 delete xxxx where xxx

  结论:当数量<=3,生成的多条delete,当数量>3, 则合并生成1次请求了,但仍然是多个delete,详见截图

代码分享:

{
    using var db = new EFCore6xDBContext();
    var userList = db.Set<UserInfo>().Take(4).ToList();
    foreach (var item in userList)
    {
        db.Remove(item);
    }
    await db.SaveChangesAsync();
    Console.WriteLine("删除成功");
}

SQL截图-小于3

SQL截图-大于3 

4. 如何修改批处理条数呢?

  默认的批处理条数非常大,但我们可以自己修改批处理条数,比如:下图将批处理条数设置为10

  optionsBuilder.UseSqlServer("Server=localhost;Database=EFCore6xDB;User ID=sa;Password=123456;", b => b.MaxBatchSize(10));

  经测试:

  (1). <=3条的时候,任何是生成多条insert语句

  (2). >3条 且 小于等于 10条, 则生成1条insert语句

  (3). 比如12条吧,则生成两条insert语句,1个插入10个数据,1个插入2个数据,详见截图

SQL截图

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2022-06-19 22:16  Yaopengfei  阅读(549)  评论(1编辑  收藏  举报