若要消除可为空引用类型的警告,请从 ContosoUniversity.csproj 项目文件中删除以下行:
<Nullable>enable</Nullable>
ASP.NET Core Web 应用中的异步 EF 方法
异步编程是 ASP.NET Core 和 EF Core 的默认模式。
Web 服务器的可用线程是有限的,而在高负载情况下的可能所有线程都被占用。 当发生这种情况的时候,服务器就无法处理新请求,直到线程被释放。 使用同步代码时,可能会出现多个线程被占用但不能执行操作的情况,因为它们正在等待 I/O 完成。 使用异步代码时,当进程正在等待 I/O 完成,服务器可以将其线程释放用于处理其他请求。 因此,使用异步代码可以更有效地利用服务器资源,并且服务器可以无延迟地处理更多流量。
异步代码会在运行时引入少量开销。 流量较低时,对性能的影响可以忽略不计,但流量较高时,潜在的性能改善非常显著。
在以下代码中,async?关键字和?返回值,await ?关键字和?ToListAsync ?方法让代码异步执行。
public async Task<Student> OnGetAsync()
{
Students = await _context.Students.ToListAsync();
return Students;
}
async ?关键字让编译器执行以下操作:
- 为方法主体的各部分生成回调。
- 创建返回的?Task?对象。
- 返回类型?
Task ?表示正在进行的工作。 await ?关键字让编译器将该方法拆分为两个部分。 第一部分是以异步方式结束已启动的操作。 第二部分是当操作完成时注入调用回调方法的地方。ToListAsync ?是?ToList ?扩展方法的异步版本。
编写使用 EF Core 的异步代码时需要注意的一些事项:
- 只有导致查询或发送数据库命令的语句才能以异步方式执行。 这包括?
ToListAsync 、SingleOrDefaultAsync 、FirstOrDefaultAsync ?和?SaveChangesAsync 。 不包括只会更改?IQueryable ?的语句,例如?var students = context.Students.Where(s => s.LastName == "Davolio") 。 - EF Core 上下文并非线程安全:请勿尝试并行执行多个操作。
- 若要利用异步代码的性能优势,请验证在调用向数据库发送查询的 EF Core 方法时,库程序包(如用于分页)是否使用异步。
创建 PaginatedList 类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(
IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip(
(pageIndex - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}
数据模型
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
以上代码加载Students.Enrollments,以及Students.Enrollments.Course。即实现了,学生选择了那些课程。
Include?和?ThenInclude?方法使上下文加载?导航属性,并在每个注册中加载?导航属性。
对于返回的实体未在当前上下文中更新的情况,AsNoTracking?方法将会提升性能。
实体状态
数据库上下文会随时跟踪内存中的实体是否已与其在数据库中的对应行进行同步。 此跟踪信息可确定调用?SaveChangesAsync?后的行为。 例如,将新实体传递到?AddAsync?方法时,该实体的状态设置为?Added。 调用?SaveChangesAsync ?时,数据库上下文会发出 SQL?INSERT ?命令。
实体可能处于以下状态之一:
-
Added :数据库中尚不存在实体。?SaveChanges ?方法发出?INSERT ?语句。 -
Unchanged :无需保存对该实体所做的任何更改。 从数据库中读取实体时,该实体具有此状态。 -
Modified :已修改实体的部分或全部属性值。?SaveChanges ?方法发出?UPDATE ?语句。 -
Deleted :已标记该实体进行删除。?SaveChanges ?方法发出?DELETE ?语句。 -
Detached :数据库上下文未跟踪该实体。
实体注解类型:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
?RegularExpression?特性可用于向输入应用限制。
例如,以下代码要求第一个字符为大写,其余字符按字母顺序排列:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
|