ASP.NET Core 使用 Entity Framework 查询数据
本章节讲解 ASP.NET Core 使用 Entity Framework 查询数据的方法,将通过实例代码与解释来详细讲解。
查询生命周期
首先,我们来了解一下 Entity Framework Core 查询的生命周期。
LINQ 查询会由 Entity Framework Core 处理并生成给数据库提供程序可处理的表示形式(就是生成给数据库可识别数据形式)。发送的查询结果(查询表示形式)会被缓存,以便每次执行查询时无需进行 LINQ 中处理。
查询结果(查询表示形式)会传递到数据库提供程序
- 数据库提供程序会识别出查询的哪些部分可以在数据库中求值。
- 查询的这些部分会转换为特定数据库的查询语言(例如,关系数据库的 T-SQL)。
- 一个或多个查询会发送到数据库并返回结果集(返回的是数据库中的值,而不是实体实例中的)。
如果这是跟踪查询,Entity Framework 会检查数据是否表示已在上下文实例的更改跟踪器中的实体中。
- 如果是,则会返回现有实体。
- 如果不是,则会创建新实体、设置更改跟踪并返回该新实体。
如果这是非跟踪查询,Entity Framework 会检查数据是否表示已在此查询的结果集中的实体中。
- 如果是,则会返回现有实体。非跟踪查询使用弱引用跟踪已返回的实体。如果具有相同标识的上一个结果超出范围,并运行垃圾回收,则可能会获得新的实体实例。
- 如果不是,则会创建新实体并返回该新实体。
执行查询时,当调用 LINQ 运算符时,只会生成查询的内存中表示形式。当我们使用查询结果(查询表示形式)时才会发送到数据库。导致查询发送到数据库的最常见操作如下:
在 for
循环中循环访问结果:
var employees = from b in _context.Employees
select new
{
b.EmployeeName,
b.ID
};
//触发数据库查询
foreach (var a in employees)
{
var id = a.ID;
}
当我们执行完 LINQ 运算符的时候,从 SQL Server Profiler 监控里面可以看到,并没有执行的 SQL 语句,也就是说查询结果 employees 并没有立即发送给数据库获取返回数据结果集。
而当我们调试进去 for
循环时候,SQL Server Profiler 监控里面可以看到出现了执行 SQL 语句。也就是说这时候查询结果 employees 才执行发送给数据库返回结果集。
另外,使用 ToList
、ToArray
、Single
、Count
等运算符。执行这种形式运算符也会立即发送到数据库获取结果集。
_context.Employees.ToList();
_context.Employees.ToArray();
_context.Employees.Count();
_context.Employees.Single();
_context.Employees.First();
跟踪查询与非跟踪查询
跟踪查询
返回实体类型的查询是默认会被跟踪的,这表示如果这些实体实例有更改行为,会通过 SaveChanges()
持久化将更改的值更新到数据库中,但是如果更改的值跟实体实例的值相同,则不会持久化提交数据到数据库,这就是跟踪查询。在以下代码中,将检测到对博客链接所做的更改,并在 SaveChanges()
期间将这些更改持久化到数据库中。
//返回实体类型的查询是默认会被跟踪
var employee = _context.Employees.SingleOrDefault(b => b.ID == 1);
//检测对员工名称所做的更改
employee.EmployeeName = "韩梅梅1";
//持久化保存到数据库中
_context.SaveChanges();
非跟踪查询
如果不需要更新从数据库中检索到的实体,则应使用非跟踪查询。可以将单个查询替换为非跟踪查询。
var employee = context.Employees
//不用跟踪查询
.AsNoTracking()
.ToList();
//或者在上下文实例级别更改默认跟踪行为
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var employees = context.Employees.ToList();
当加上非跟踪查询标识后,无论怎么更改值,都不会持久化保存数据到数据库中的。
var employee = _context.Employees
//不用跟踪查询
.AsNoTracking()
.SingleOrDefault(m => m.ID == 1);
employee.EmployeeName = "韩梅梅2";
_context.SaveChanges();
跟踪和自定义投影
即使查询的结果类型不是实体类型,默认情况下 EF Core 也会跟踪结果中包含的实体类型。在以下返回匿名类型的查询中,结果集中的实例会被跟踪。
var employee = context.Employees
.Select(b =>
new
{
Employee = b,
EmployeeCount = b.EmployeeName.Count()
});
如果结果集包含来自 LINQ 组合的实体类型,EF Core 将跟踪它们。
var employee = context.Employees
.Select(b =>
new
{
Employee = b,
EmployeeLogs = b.EmployeeLog.OrderBy(p => p.EmployeeID).LastOrDefault()
});
如果结果集不包含任何实体类型,则不会执行跟踪。在以下查询中,我们返回匿名类型(具有实体中的某些值,但没有实际实体类型的实例)。查询中没有任何被跟踪的实体。
var employee = context.Employees
.Select(b =>
new
{
Id = b.ID,
Name = b.EmployeeName
});
EF Core 支持执行顶级投影中的客户端评估。如果 EF Core 具体化实体实例以进行客户端评估,则会跟踪该实体实例。此处,由于我们要将 employee 实体传递到客户端方法 StandardizeURL
,因此 EF Core 也会跟踪该实例。