Find()只能通过主键查询,更复杂的查询需要通过Linq才能完成。
查出姓王的学生:
Student students = context.Students .Where(s => s.Name.StartsWith("王")) .ToList();
演示:查看log中的SQL语句,
WHERE [s].[Name] IS NOT NULL AND ([s].[Name] LIKE N'王%')
#体会#:IS NOT NULL的严谨!
也可以使用Linq表达式:
from s in context.Students where s.Name.StartsWith("王")
Linq中其他方法也可以使用,比如排序:
Student student = context.Students .Where(s => s.Name.StartsWith("王")) .OrderByDescending(s=>s.Age) .FirstOrDefault();
最终的结果可以是集合,也可以是单个。
但这里的Where()方法和我们之前的不一样:
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate);
首先,DbSet<T>实现了IQueryable接口;
所以,它的Where()方法定义的参数类型是Expression,
返回值还是IQueryable:
public static IQueryable<TSource> Where<TSource>( this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
#常见面试题:IQueryable/IEnumerable/IList的区别?#
在Linq to EF中,IQueryable和IEnumerable的区别,就类似于Linq to Object中IEnumerable和IList的区别。
断点log演示:
IQueryable<Student> query = context.Students .Where(s => s.Name.StartsWith("王")); query = query.OrderByDescending(s => s.Score); Student student = query.FirstOrDefault();
有时候DbSet<T>没有被声明为DbContext的属性(是在OnModelCreating()中声明的),但我们又需要使用它(开始Linq查询),怎么办?
可以调用DbContext的Set<T>()方法:
DbSet<Student> students = context.Set<Student>();
不同的数据库需要不同的provider,“即插即用”
演示:改动由DbContext里定义的Database Provider,生成增删改查的SQL语句并执行
optionsBuilder .UseMySql("server=localhost;Uid=root;database=17bang;Charset=utf8", ServerVersion.Parse("'8.0.25'"))
mysql版本号获取方法:
select version();
并不是所有的Linq语句都能够通过provider正确的生成。
比如,查出每个老师教了多少门课程。
这样的Linq在to Object的时候是没有问题的:
var result = context.Majors .GroupBy(m => m.Teacher) .ToDictionary(g => g.Key, g => g.Count()) ;
但是to EF的时候,就会报错:
The LINQ expression 'DbSet<Major>()……elementSelector: m => m.Outer)' could not be translated.
就是没办法将上述Linq表达式翻译/转换(translate)成SQL的意思。并且给出了两种解决方案:
var result = context.Majors .GroupBy(m => m.TeacherId /* m.Teacher无法translate */) //只有Select()才会被translate,不能省略 .Select(g => new { g.Key, count = g.Count() }) .ToDictionary(g => g.Key, g => g.count) ;
var result = context.Majors .Include(m => m.Teacher).AsEnumerable() //或者:.ToList()
#理解:AsEnumerable()和ToList()的区别#
他们都能促成数据库查询,但是:
演示:没有Include()的AsEnumerable()在lazyload Teacher时抛异常:
There is already an open DataReader associated with this Connection……
在3.0以前,如果无法translate成SQL语句,EF会自动调用AsEnumerable(),要当心由此带来的性能问题。
在7.0以后,GroupBy()可以直接作为最终语句,但是会生成order by语句,(*/ω\*),坑!建议:始终在group之后Select。
允许Sum()等聚合函数返回null:
.Sum(m =>(double?)m.Score) //可空类型强转转换
当SQL语句无法由Linq语句自动生成(translate),或者
生成的SQL语句无法满足要求(一般是性能)的时候,
我们可以自己写出SQL语句,然后交由EF执行:
如果要避免SQL注入,ExecuteSqlRaw()需要使用@age并传入SqlParameter等(使用参数化查询)。
但ExecuteSqlInterpolated()会自动完成这些工作,超赞!(log演示)
int rows = context.Database.ExecuteSqlInterpolated(
$"UPDATE Students SET Score = {score} WHERE Score = 0;");
Console.WriteLine(rows);
ExecuteSqlX()只能返回受影响的行数。所以
仍从DbSet开始,调用:
根据两方法返回的IQueryable还可以进一步的被Linq过滤:
var result = context.Students.FromSqlInterpolated( $"SELECT * FROM Students WHERE Score > {score}") .OrderBy(s => s.Score);log演示:EF使用表表达式生成OrderBy()的SQL语句
如果只是单条语句禁用,可以在DbSet上调用AsNoTracking():
IList<Student> students = context.Students .AsNoTracking() .Where(x => x.Score > 80) .ToList();
如果要对DbContext中所有操作都不予追踪:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
在查询之后添加两行代码:
//id=3的studentScore>80,id=1的<80 Student s3 = context.Students.Find(3); Student s1 = context.Students.Find(1);
对比演示:
PS:EF目前还没有提供官方的/内置的二级缓存方案。
ORM概述第9/10/11/12题。
多快好省!前端后端,线上线下,名师精讲
更多了解 加: