EntityFrameworkCore Like

本文探讨了Entity Framework Core中Like查询的使用,与StartsWith、Contains方法的区别,以及在通配符查询中的索引优化策略。重点揭示了EF6中的通配符处理方式和模糊查询背后的逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Entity Framework Core Like 查询揭秘

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

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

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,这样可以直接运行对应的示例,并且也可以直接输出对应的结果。
我们来看一下运行的结果:

EF.Functions.Like

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

字符串匹配模式

在这里,我暂且将StartsWithContainsEndsWith方法称之为字符串匹配模式

您肯定在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”** ,运行的结果如下:

EF Contains

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

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

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

通配符模糊查询

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

通配符说明示例
%包含零个或多个字符的任意字符串。WHERE title LIKE '%computer%' 将查找在书名中任意位置包含单词 “computer” 的所有书名。
_(下划线)任何单个字符。WHERE au_fname LIKE '_ean' 将查找以 ean 结尾的所有 4 个字母的名字(Dean、Sean 等)。
[ ]指定范围 ([a-f]) 或集合 ([abcdef]) 中的任何单个字符。WHERE au_lname LIKE '[C-P]arsen' 将查找以 arsen 结尾并且以介于 C 与 P 之间的任何单个字符开始的作者姓氏, 例如 Carsen、Larsen、Karsen 等。
[^]不属于指定范围 ([a-f]) 或集合 ([abcdef]) 的任何单个字符。WHERE au_lname LIKE 'de[^l]%' 将查找以 de 开始并且其后的字母不为 l 的所有作者的姓氏。

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

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

EF.Functions.Like 查询示例:

EF.Functions.Like

Contains 查询示例:
EF 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] 的字符串。

提示: StartsWithEndsWith分别会被解析成为LeftRight函数,测试结果在这里不再做重复演示。
结论: 在EF Core中提供EF.Functions.Like()方法的根本原因是在 TSQL 语句中 Like 关键字支持通配符,而在.Net中StartsWithContainsEndsWith方法是不支持通配符的;
在EF Core中StartsWithContainsEndsWith模糊查询实际分别被解析成为LeftCharIndexRight,而不是Like

其它要点

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

EF Core StartsWith 优化

如果使用StartWith方法来实现模糊查询,解析后的SQL语句会包括一个Like查询,您可能要说,刚才不是已经讲过吗,StartsWithContainsEndsWith方法解析后的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函数,这是为什么呢?

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

结论: StartsWith模糊查询解析后的SQL用到Like,这是因为Like在百分号后置的是情况下会利用到索引,这样查询速度会更快。ContainsEndsWith模糊查询解析后的SQL不包括Like查询,因为在分百号前置的情况无法引用到索引。
关于ContainsEndsWith模糊查询的测试,在这里不再重复,您可以自己测试。

EF 6

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

我们在EF 6中分别捕获StartsWithContainsEndsWith解析后的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'~'

StartsWithContainsEndsWith方法均会被解析为Like查询,但是是传递的参数由:[a-c]变为了~[a-c],前面多了一个特殊符号**~,并且查询子句的后面还多了一部分 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中StartsWithContainsEndsWith方法均会被解析为Like查询,但是如果出现了通配符,框架会结合ESCAPE以及自身过滤功能将参数进行转义。

总结

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

  • 在EF Core中StartsWithContainsEndsWith模糊查询分别被解析成为LeftCharIndexRight,而不是Like

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

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

  • 在EF 6中,模糊查询不支持通配符,这一点是因为我没有找到对应的解决方案

例子:

using Microsoft.EntityFrameworkCore;

public void GetUserinfo()
{
   _dbContext.UserInfos.Where(c => EF.Functions.Like(c.UserName, "%风%")).ToList();
}
<think>嗯,用户想了解Entity Framework的常用操作。首先,我得确定用户对EF的熟悉程度。可能他们刚开始用,需要基础的操作指南。Entity Framework主要有Database First、Model First和Code First三种模式,现在Code First比较流行,应该重点介绍这个。 然后,常用操作包括数据库连接、模型定义、CRUD、查询、事务、迁移等。得一步步来,先讲安装,用NuGet包管理器安装EntityFramework。接着是创建DbContext,这个很重要,是操作数据库的入口。 模型定义部分,需要用例子说明,比如Student和Course类,配置关系和数据注解或者Fluent API。用户可能需要知道如何定义主键、外键,以及配置字段属性,比如长度、是否可空。 CRUD操作是基础,得分别给出示例代码,比如如何添加、查询、更新和删除记录。注意要提到SaveChanges()方法的作用,以及异步方法的使用,比如AddAsync和SaveChangesAsync,这对性能有帮助。 查询部分要区分LINQ和原生SQL。LINQ更安全,但有时候复杂查询可能需要原生SQL。要提醒用户注意SQL注入的问题,建议使用参数化查询。 事务处理也是关键,尤其是在需要多个操作原子性时。用TransactionScope的例子,或者DbContext自带的BeginTransaction方法,确保用户知道如何保持数据一致性。 数据迁移是Code First的核心功能,要说明如何启用迁移,添加迁移记录,更新数据库。可能用户会遇到模型变更后的迁移问题,需要指导他们如何正确操作。 性能优化方面,延迟加载和立即加载的区别很重要。Include方法的使用场景,以及AsNoTracking在只读查询中的优势。这些都是实际开发中常见的优化点。 最后,常见问题比如并发冲突的处理,如何用ConcurrencyCheck特性或者RowVersion。连接字符串的配置,可能在Web.config或appsettings.json里,提醒用户根据项目类型调整。 需要检查是否有遗漏的部分,比如关联查询、事务的更多细节,或者异步操作的全面性。要确保示例代码正确,符合C#语法,并且使用中文注释,方便用户理解。可能用户需要实际代码片段,所以每个步骤都配上对应的代码示例会更好。同时,注意提到EF Core和EF6的区别,不过用户没有特别说明,可能默认是EF Core,但最好还是提一下适用的版本。 总结一下,应该分步骤,从安装配置到各种操作,再到高级功能,结构清晰,用例子说明,确保用户能一步步跟着操作,解决问题。同时,避免使用复杂术语,保持解释简洁明了。</think>以下是用结构化的方式介绍 Entity Framework 常用操作的指南,包含基础到进阶的操作示例: $$ \text{Entity Framework 核心操作} = \left( \text{CRUD}, \ \text{查询}, \ \text{迁移}, \ \text{关系映射} \right) $$ --- ### 一、环境准备 1. **安装 NuGet 包** ```csharp // 包管理器控制台 Install-Package EntityFramework // 或 .NET CLI dotnet add package Microsoft.EntityFrameworkCore.SqlServer ``` 2. **创建 DbContext** ```csharp public class SchoolContext : DbContext { public DbSet<Student> Students { get; set; } public DbSet<Course> Courses { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Server=myServer;Database=SchoolDB;Trusted_Connection=True;"); } } ``` --- ### 二、模型定义 1. **数据注解** ```csharp public class Student { [Key] public int StudentId { get; set; } [Required, MaxLength(50)] public string Name { get; set; } [ForeignKey("CourseId")] public Course EnrolledCourse { get; set; } } ``` 2. **Fluent API 配置** ```csharp protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Student>() .HasOne(s => s.EnrolledCourse) .WithMany(c => c.Students) .HasForeignKey(s => s.CourseId); } ``` --- ### 三、CRUD 操作 $$ \text{CRUD 公式} \equiv \text{Create} + \text{Read} + \text{Update} + \text{Delete} $$ 1. **新增数据** ```csharp using (var context = new SchoolContext()) { var student = new Student { Name = "张三" }; context.Students.Add(student); context.SaveChanges(); } ``` 2. **查询数据** ```csharp // LINQ 查询 var students = context.Students .Where(s => s.Name.Contains("张")) .OrderBy(s => s.StudentId) .ToList(); ``` 3. **更新数据** ```csharp var student = context.Students.Find(1); student.Name = "李四"; context.SaveChanges(); ``` 4. **删除数据** ```csharp var student = context.Students.Find(1); context.Students.Remove(student); context.SaveChanges(); ``` --- ### 四、高级操作 1. **事务处理** ```csharp using (var transaction = context.Database.BeginTransaction()) { try { context.Students.Add(new Student { Name = "王五" }); context.Courses.Add(new Course { Title = "数学" }); context.SaveChanges(); transaction.Commit(); } catch { transaction.Rollback(); } } ``` 2. **执行原生 SQL** ```csharp var students = context.Students .FromSqlRaw("SELECT * FROM Students WHERE Name LIKE '张%'") .ToList(); ``` 3. **延迟加载 vs 立即加载** ```csharp // 立即加载(Eager Loading) var courses = context.Courses.Include(c => c.Students).ToList(); // 延迟加载(需要配置代理) var course = context.Courses.Find(1); var students = course.Students; // 首次访问时加载 ``` --- ### 五、数据迁移 $$ \text{迁移流程} \Rightarrow \text{Add-Migration} \rightarrow \text{Update-Database} $$ 1. 创建迁移 ```bash dotnet ef migrations add InitialCreate ``` 2. 应用迁移 ```bash dotnet ef database update ``` --- ### 六、性能优化 1. **AsNoTracking 查询** ```csharp var readOnlyStudents = context.Students .AsNoTracking() .ToList(); ``` 2. **批量操作** ```csharp // EF Core 6+ 新增批量操作 context.Students.AddRange(studentList); context.SaveChanges(); // 单次提交 ``` --- ### 常见问题 1. **并发冲突** 使用 `[ConcurrencyCheck]` 特性或 `RowVersion` 字段 2. **连接字符串管理** 推荐在 `appsettings.json` 中配置: ```json { "ConnectionStrings": { "SchoolDB": "Server=myServer;Database=SchoolDB;Trusted_Connection=True;" } } ``` --- 以上操作覆盖了 Entity Framework 的核心使用场景,建议通过实际项目练习加深理解。注意不同版本(EF6 和 EF Core)的 API 差异,示例基于 EF Core 编写。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值