Entity Framework Core Like 查询揭秘

2017 年 9 月 17 日 DotNet

(点击上方蓝字,可快速关注我们)


来源:Sweet-Tang

cnblogs.com/tdfblog/p/entity-framework-core 


在Entity Framework Core 2.0中增加一个很酷的功能:EF.Functions.Like(),最终解析为SQL中的Like语句,以便于在 LINQ 查询中直接调用。


不过Entity Framework 中默认提供了StartsWith、Contains和EndsWith方法用于解决模糊查询,那么为什么还要提供EF.Functions.Like,今天我们来重点说说它们之间的区别。


表结构定义


在具体内容开始之前,我们先简单说明一下要使用的表结构。


public class Category

{

        public int CategoryID { get; set; }

        public string CategoryName { get; set; }

        

        public override string ToString()

        {

            return $"{nameof(CategoryID)}: {CategoryID}, {nameof(CategoryName)}: {CategoryName}";

        }

}


在 Category 类型定义了两个字段:CategoryID、CategoryName。


public class SampleDbContext : DbContext

 {

        public virtual DbSet<Category> Categories { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

        {

            optionsBuilder.UseSqlServer("数据库连接字符串");

            base.OnConfiguring(optionsBuilder);

        }


        protected override void OnModelCreating(ModelBuilder modelBuilder)

        {

            base.OnModelCreating(modelBuilder);

            EntityTypeBuilder<Category> entityTypeBuilder = modelBuilder.Entity<Category>();

            entityTypeBuilder.ToTable("Category");

            entityTypeBuilder.HasKey(e => e.CategoryID);

            entityTypeBuilder.Property(e => e.CategoryID).UseSqlServerIdentityColumn();

        }

    }


我们使用 SampleDbContext 来访问数据库。



在数据库的 Category 表中插入上面三行记录。


EF.Functions.Like 使用示例


我们来看一个EF.Functions.Like()查询示例,查询 CategoryName 字段中包括字符串 “t” 的数据,传递的参数是 “%t%”:


 [Fact]

 public void Like()

{

            using (var dataContext = new SampleDbContext()) {

            var result= dataContext.Categories.Where(item => EF.Functions.Like(item.CategoryName, "%t%")).ToList();

                foreach (var item in result) {

                    _testOutputHelper.WriteLine(item.CategoryName);

                }

            }

        }


提示:在做一些示例演示时,个人喜欢会用 Xunit + Resharper,这样可以直接运行对应的示例,并且也可以直接输出对应的结果。


我们来看一下运行的结果:



查询的结果包含两条数据,这与我们预期结果一致。


字符串匹配模式


在这里,我暂且将StartsWith、Contains和EndsWith方法称之为字符串匹配模式。


您肯定在Entity Framework中使用过这些方式,我们还是简单说明一下这三个方法的作用:


  • StartsWith:表示字符串的开头是否与指定的字符串匹配;


  • Contains:表示指定的子串是否出现在此字符串中;


  • EndsWith:表示字符串的结尾是否与指定的字符串匹配;


我们可以通过Contains方法实现与前一个示例一致的功能:


        [Fact]

        public void Contains()

        {

            using (var dataContext = new SampleDbContext())

            {

                var result = dataContext.Categories.Where(item => item.CategoryName.Contains("t")).ToList();


                foreach (var item in result)

                {

                    _testOutputHelper.WriteLine(item.CategoryName);

                }


            }

        }


我们在Contains方法转入参数“t” ,运行的结果如下:



运行结果与 Like 函数示例的结果是一致的。


在这里我只列举了Contains的示例,StartsWith和EndsWith的功能非常相似,我就不重复列举了。


这两个示例的运行结果是一致的,那么微软为什么要提供EF.Functions.Like()方法呢?


通配符模糊查询


我们知道在 T-SQL 语句中 Like 关键字支持 通配符 ,下面简单介绍支持的通配符:



关于 Like 和通配符更多的知识请直接到MSDN中了解,链接地址:https://msdn.microsoft.com/zh-cn/library/ms179859(v=sql.110).aspx。


我们的将查询关键字由 “t” 改为 “[a-c]”,再来看上面两个示例分别运行的结果:


EF.Functions.Like 查询示例:



Contains 查询示例:



上面运行的结果,Like 查询的结果返回三条记录,而 Contains 查询的结果无任何数据返回。


我们借助 SQL Server Profiler 分别捕获这两个示例实际生成的SQL查询。


EF.Functions.Like 查询生成的SQL语句:


SELECT [item].[CategoryID], [item].[CategoryName]

FROM [Category] AS [item]

WHERE [item].[CategoryName] LIKE N'%[a-c]%'


Contains 查询生成的SQL语句:


SELECT [item].[CategoryID], [item].[CategoryName]

FROM [Category] AS [item]

WHERE CHARINDEX(N'[a-c]', [item].[CategoryName]) > 0


通过上面示例以及捕获的SQL,我们可以得知,EF.Functions.Like() 查询会被解释成为 Like,实际上是查询字符串中包括 “a”、“b”、“c” 这三个字符中任何一个字符的数据,而使用 Contains 查询会被解析成为 CharIndex 函数,实际是指查询字符串中包括 “[a-c]” 的字符串。


提示: StartsWith和EndsWith分别会被解析成为Left和Right函数,测试结果在这里不再做重复演示。


结论: 在EF Core中提供EF.Functions.Like()方法的根本原因是在 TSQL 语句中 Like 关键字支持通配符,而在.Net中StartsWith、Contains和EndsWith方法是不支持通配符的;


在EF Core中StartsWith、Contains和EndsWith模糊查询实际分别被解析成为Left、CharIndex和Right,而不是Like。


其它要点


通过上面的示例我们已经说清楚了EF.Functions.Like()方法和StartsWith、Contains和EndsWith方法之间的区别,但是还有以下两点需要说明。


EF Core StartsWith 优化


如果使用StartWith方法来实现模糊查询,解析后的SQL语句会包括一个Like查询,您可能要说,刚才不是已经讲过吗,StartsWith、Contains和EndsWith方法解析后的SQL不是通过 Like 来查询!先不要着急,我下面来说清楚这个问题。


StartsWith 查询示例:


        [Fact]

        public void StartsWith()

        {

            using (var dataContext = new SampleDbContext())

            {

                var result = dataContext.Categories.Where(item => item.CategoryName.StartsWith("Clo")).ToList();


                foreach (var item in result)

                {

                    _testOutputHelper.WriteLine(item.CategoryName);

                }

            }

        }


借助 SQL Server Profiler 捕获实际生成的SQL查询:


    SELECT [item].[CategoryID], [item].[CategoryName]

    FROM [Category] AS [item]

    WHERE [item].[CategoryName] LIKE N'Clo' + N'%' AND (LEFT([item].[CategoryName], LEN(N'Clo')) = N'Clo')


在SQL语句中,即用到了Like,也用到Left函数,这是为什么呢?


您可能知道在数据库查询时,如果在某一个字段上使用函数是无法利用到索引的;在使用Left,CharIndex和Right时是无法利用到索引的;而Like查询在百分号后置的情况下会利用到索引。关于数据库的这些知识,在博客园上有很多文章,我就不重复说明了。


结论: StartsWith模糊查询解析后的SQL用到Like,这是因为Like在百分号后置的是情况下会利用到索引,这样查询速度会更快。Contains和EndsWith模糊查询解析后的SQL不包括Like查询,因为在分百号前置的情况无法引用到索引。


关于Contains和EndsWith模糊查询的测试,在这里不再重复,您可以自己测试。


EF 6


在EF 6中,模糊查询解析后的SQL语句与EF Core中略有不同,但是执行的结果没有区别。


我们在EF 6中分别捕获StartsWith、Contains和EndsWith解析后的SQL语句,不过我们搜索的关键字是:“[a-c]”,包含通配符。


StartsWith 查询生成的SQL语句:


SELECT 

    [Extent1].[CategoryID] AS [CategoryID], 

    [Extent1].[CategoryName] AS [CategoryName]

    FROM [dbo].[Category] AS [Extent1]

    WHERE [Extent1].[CategoryName] LIKE N'~[a-c]%' ESCAPE N'~'


Contains 查询生成的SQL语句:


SELECT 

    [Extent1].[CategoryID] AS [CategoryID], 

    [Extent1].[CategoryName] AS [CategoryName]

    FROM [dbo].[Category] AS [Extent1]

    WHERE [Extent1].[CategoryName] LIKE N'%~[a-c]%' ESCAPE N'~'


EndsWith 查询生成的SQL语句:


SELECT 

    [Extent1].[CategoryID] AS [CategoryID], 

    [Extent1].[CategoryName] AS [CategoryName]

    FROM [dbo].[Category] AS [Extent1]

    WHERE [Extent1].[CategoryName] LIKE N'%~[a-c]' ESCAPE N'~'


StartsWith、Contains和EndsWith方法均会被解析为Like查询,但是是传递的参数由:“[a-c]”变为了“~[a-b]”,前面多了一个特殊符号“~”,并且查询子句的后面还多了一部分 ESCAPE N'~'。


在MSDN上面有关ESCAPE关键字的解释,我们摘取其中一部分来说明:


使用 ESCAPE 子句的模式匹配


搜索包含一个或多个特殊通配符的字符串。 例如,customers 数据库中的 discounts 表可能存储含百分号 (%) 的折扣值。 若要搜索作为字符而不是通配符的百分号,必须提供 ESCAPE 关键字和转义符。


例如,一个样本数据库包含名为 comment 的列,该列含文本 30%。 若要搜索在 comment 列中的任何位置包含字符串 30% 的任何行,请指定 WHERE comment LIKE '%30!%%' ESCAPE '!' 之类的 WHERE 子句。


 如果未指定 ESCAPE 和转义符,则数据库引擎将返回包含字符串 30 的所有行。


如果您想了解EF 6是如果过滤这些通配符的,可以在Github上面了解,链接地址https://github.com/aspnet/EntityFramework6/blob/6.1.3/src/EntityFramework.SqlServer/SqlProviderManifest.cs#L164-L189。


结论:在EF 6中StartsWith、Contains和EndsWith方法均会被解析为Like查询,但是如果出现了通配符,框架会结合ESCAPE以及自身过滤功能将参数进行转义。


总结


通过上面的叙述,我们可以得到如下一些结论:


  • 在EF Core中提供EF.Functions.Like()方法的根本原因是在 TSQL 语句中 Like 关键字支持通配符,而在.Net中StartsWith、Contains和EndsWith方法是不支持通配符的;


  • 在EF Core中StartsWith、Contains和EndsWith模糊查询分别被解析成为Left、CharIndex和Right,而不是Like。


  • 在EF Core中StartsWith模糊查询解析后的SQL用到Like,这是因为Like在百分号后置的是情况下会利用到索引,这样查询速度会更快;


  • 在EF 6中,StartsWith、Contains和EndsWith方法均会被解析为Like查询,但是如果出现了通配符,框架会结合ESCAPE以及自身过滤功能将参数进行转义;、


  • 在EF 6中,模糊查询不支持通配符,这一点是因为我没有找到对应的解决方案,如果您知道,请留言,谢谢!


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

登录查看更多
0

相关内容

COVID-19文献知识图谱构建,UIUC-哥伦比亚大学
专知会员服务
42+阅读 · 2020年7月2日
Stabilizing Transformers for Reinforcement Learning
专知会员服务
59+阅读 · 2019年10月17日
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
Hierarchical Imitation - Reinforcement Learning
CreateAMind
19+阅读 · 2018年5月25日
lightgbm algorithm case of kaggle(上)
R语言中文社区
8+阅读 · 2018年3月20日
优化哈希策略
ImportNew
5+阅读 · 2018年1月17日
Neo4j 和图数据库起步
Linux中国
8+阅读 · 2017年12月20日
Arxiv
3+阅读 · 2018年11月12日
Arxiv
5+阅读 · 2018年5月1日
Arxiv
5+阅读 · 2018年3月6日
VIP会员
相关资讯
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
Hierarchical Imitation - Reinforcement Learning
CreateAMind
19+阅读 · 2018年5月25日
lightgbm algorithm case of kaggle(上)
R语言中文社区
8+阅读 · 2018年3月20日
优化哈希策略
ImportNew
5+阅读 · 2018年1月17日
Neo4j 和图数据库起步
Linux中国
8+阅读 · 2017年12月20日
Top
微信扫码咨询专知VIP会员