Entity Framework Core 读取数据

EF Core通过DbContext对象从数据库中读取记录,例如:我们能获取所有的员工记录从数据库中通过使用下面代码:

var emp = context.Employee;

这里的context是一个DbContext对象类,employee是从数据库中读取的记录.

我们能从数据库中获取到指定的员工,例如下面查询姓名是Matt员工

var emp = await context.Employee.Where(e => e.Name == "Matt").FirstOrDefaultAsync();
1 EF Core 读取关联数据
EF Core读取关联数据有三种常用的方式

预先加载(Eager Loading)模式: 读取该实体时,会同时查询关联表数据,通常会出现表连接查询,查询所有必需数据,可使用 Include 和 ThenInclude 方法实现预先加载

显式加载(Explicit Loading)模式:首次读取实体时,不查询关联数据,如有需要,可编写代码查询关联数据,就像使用单独查询进行预先加载一样,显式加载时会向数据库发送多个查询

懒加载(Lazy Loading)模式:首次读取实体时,不查询关联数据,然而,首次尝试访问导航属性时,会自动查询导航属性所需的数据,每次首次从导航属性获取数据时,都向数据库发送查询

1.1 预先加载(Eager Loading)模式

在EF Core常规的读取数据,关联的数据是不被加载的,但是使用Eager Loading我们能使用实体导航属性加载相关联的数据,下面 Employee 实体引用一个Department实体类
public class Employee{    public int Id { get; set; }    public int DepartmentId { get; set; }    public string Name { get; set; }    public string Designation { get; set; }    public Department Department { get; set; }}
下面代码查找名字是Matt的对象
Employee emp = await context.Employee.Where(e => e.Name == "Matt").FirstOrDefaultAsync();

运行应用程序,通过断点来检查Department值,我们发现他的值为null,如下图所示:  

Entity Framework Core 读取数据

在Eager Loading模式中,我们使用Include()方法加载引用实体数据,引用的实体类通过导航属性加载,我们在Employee类中包含一个导航属性叫Department
public Department Department { get; set; }
类似,Department实体引用了Employee集合属性
public class Department{    public int Id { get; set; }    public string Name { get; set; }    public ICollection<Employee> Employee { get; set; }}
Employee实体关联Department实体,因此使用Include() 方法 加载关联的Department数据,代码如下:
Employee emp = await context.Employee.Where(e => e.Name == "Matt")                            .Include(s => s.Department)                            .FirstOrDefaultAsync();

我们通过断点查看一下上面代码,我们发现Department属性有值,如下图显示:          

Entity Framework Core 读取数据

read,create,delete,和update数据EF Core 在背后会替我们执行相应的SQL

Include()方法将执行如下SQL连接查询从数据库中获取数据:
SELECT [e].[Id], [e].[Designation], [e].[Name], [e.Department].[Id], [e.Department].[Name]FROM [Employee] AS [e]LEFT JOIN [Department] AS [e.Department] ON [e].[DepartmentId] = [e.Department].[Id]WHERE [e].[Name] = N'Matt'
1.1.1 多个Include() 方法

我们能使用多个Include() 方法加载多个引用实体,例如:假如Employee实体有另一个关联实体Project,下面Include代码加载Employee类Department和Project

var emp = await context.Employee.Where(e => e.Name == "Matt")                       .Include(s=>s.Department)                       .Include(s=>s.Project)                       .FirstOrDefaultAsync();

1.1.2 ThenInclude()方法

EF Core 有ThenInclude()方法用来加载多级别关联数据,例如:假设Department实体有一个名字为Report的导航属性

public class Department{    public int Id { get; set; }    public string Name { get; set; }    public ICollection<Employee> Employee { get; set; }    public Report Report { get; set; }}
代码如下:
var emp = await context.Employee.Where(e => e.Name == "Matt")                       .Include(s => s.Department)                       .ThenInclude(r => r.Report)                       .FirstOrDefaultAsync();
.Include(s => s.Department) 加载Employee实体引用的Department实体 
.ThenInclude(r => r.Report) 加载Department实体引用的Report实体

1.2 显式加载(Explicit Loading)模式

Explicit Loading 是指我们需要的时候,再去查询关联数据,我们来看一段代码
var emp = await context.Employee.Where(e => e.Name == "Matt")                       .FirstOrDefaultAsync();await context.Entry(emp).Reference(s => s.Department).LoadAsync();

context.Entry(emp).Reference(s=>s.Department).LoadAsync()加载Employee实体数据,Department,Reference获取引用关联的数据,LoadAsync()方法加载对应数据

Entity Framework Core 读取数据

Entity Framework Core 读取数据

我们可以在Load数据之前使用Query()方法对数据进行过滤

context.Entry(emp).Reference(s=>s.Department).Query().Where(s => s.Name == "Admin").LoadAsync();
这个代码值查询Name是admin的Department
1.3 懒加载(Lazy Loading)模式
在Lazy Loading 首次读取实体时不会查询引用的属性,但是当我们第一次访问导航属性时,将自动检索该导航属性所需的数据

在使用Lazy Loading之前,我们必须做如下配置:

安装Microsoft.EntityFrameworkCore.Proxies包在DbContext文件的OnConfiguring方法中调用UseLazyLoadingProxies 方法来启用Lazy Loading模式
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){     optionsBuilder.UseLazyLoadingProxies();}
确保引用实体类属性是virtual
public class Employee{    public int Id { get; set; }    public int DepartmentId { get; set; }    public string Name { get; set; }    public string Designation { get; set; }    public virtual Department Department { get; set; }}public class Department{    public int Id { get; set; }    public string Name { get; set; }    public virtual ICollection<Employee> Employee { get; set; }}

Lazy Loading例子:

Employee emp = await context.Employee.Where(e => e.Name == "Matt")                            .FirstOrDefaultAsync();string deptName = emp.Department.Name;

上面代码加载Employee实体的Department属性使用了Lazy Loading,通过断点我们能看到departName变量的值。如下图所示:

Entity Framework Core 读取数据

2 优化EF Core 代码

优化EF Code可以使我们应用程序代码更轻量以及执行更快,主要使用下面3个比较好的方法来优化EF Core
不跟踪实体类
尽可能减少调用数据库
限制结果集的大小
不跟踪实体类

LINQ查询会跟踪所有的实体状态,这将引发额外开销,在只读的场景下不需要跟踪实体,使用AsNoTracking 方法告诉EF Core 不跟踪实体,我们在代码中能使用它:

var emp = context.Employee.AsNoTracking();
尽可能减少数据库访问
当我们使用DbContext访问实体时,EF Core连接数据库并查询结果集,我们可以使用List类型存储结果集,然后从中提取数据,而不是一次又一次的访问数据库,下面代码EF Core将调用数据库两次
var empall = context.Employee;var empmatt = context.Employee.Where(e => e.Name == "Matt").FirstOrDefault();

通过将结果存储到list 类型的对象,我们减少数据库调用次数

var empall = context.Employee.ToList();var empmatt = empall.Where(e => e.Name == "Matt").FirstOrDefault();

限制数据集大小

当我们调用实体时会给我们返回所有的字段,我们仅仅返回我们需要的字段,从而减少结果集中不必要的字段,下面代码中我们仅仅从实体中查询到了Name字段
var empmall = context.Employee.Select(b => b.Name);
类似的我们仅仅需要employee 的name和deignation ,通过下面Linq完成
var empmatt = context.Employee.Where(e => e.Name == "Matt").Select(b => new {b.Name, b.Designation}).FirstOrDefault();
在查询数据期间,使用分页查询,LINQ Skip和Take运算符用于实现此功能,Skip表示跳过多少个元素,Take表示获取多少个元素

下面代码给我们提供了1,2,3页的数据,并且每页数据为10条

var emp_page_One = context.Employee.Skip(0).Take(10); var emp_page_Two = context.Employee.Skip(20).Take(10); var emp_page_Three = context.Employee.Skip(30).Take(10); 
3  EF Core CRUD 操作– 读取数据

我们将指定EF Core CRUD操作从数据库中读取数据,我们使用Employee和Department实体

打开DepartmentController并且添加Index方法,读取所有department数据,将他们返回到View

public class DepartmentController : Controller{    private CompanyContext context;    public DepartmentController(CompanyContext cc)    {        context = cc;    }    public IActionResult Create()    {        return View();    }    [HttpPost]    public async Task<IActionResult> Create(Department dept)    {        context.Add(dept);        await context.SaveChangesAsync();        return View();    }    public IActionResult Index()    {        return View(context.Department.AsNoTracking());    }}
下一步,添加Index.cshtml 视图在Views/Department 文件夹下,显示所有的department
@{    ViewData["Title"] = "部门数据";}@model IEnumerable<Department><div class="container">    <div class="row mb-3">        <div class="col-sm-3">            <a asp-action="Create" class="btn btn-secondary">新增</a>        </div>        <div class="col-sm-3"></div>        <div class="col-sm-3"></div>        <div class="col-sm-3"></div>    </div>    <div class="row mb-3">        <div class="col-sm">            <table class="table table-bordered align-middle">                <thead>                    <tr>                        <th>编号</th>                        <th>名称</th>                    </tr>                </thead>                <tbody>                     @foreach (Department dept in Model)                    {                        <tr>                            <td>@dept.Id</td>                            <td>@dept.Name</td>                        </tr>                    }                                    </tbody>            </table>        </div>    </div></div>

运行应用程序并打开地https://localhost:7234/Department ,我们能看到所有的部门数据显示在表格内:        

Entity Framework Core 读取数据

下一步我们将读取Employee记录并显示在浏览器中,因此添加一个新的Index方法在EmployeeController.cs 文件,代码如下:
public class EmployeeController : Controller{    private CompanyContext context;    public EmployeeController(CompanyContext cc)    {        context = cc;    }    public async Task<IActionResult> Index()    {
        //var employee = context.Employee.Where(emp => emp.Name == "Matt")        //                    .Include(s => s.Department)        //                    .FirstOrDefault();        //var emp = await context.Employee.Where(e => e.Name == "Matt")        //    .FirstOrDefaultAsync();        //await context.Entry(emp).Reference(s => s.Department).LoadAsync();
        //Employee emp = await context.Employee.Where(e => e.Name == "Matt")        //                .FirstOrDefaultAsync();        //string deptName = emp.Department.Name;        var employee = context.Employee.Where(emp => emp.Name == "Matt")                          .Include(s => s.Department);        return View();    }}
我们使用Eager Loading读取Employee 和它关联的Department数据通过使用下面代码:
context.Employee.Include(s => s.Department)
数据将返回到浏览器并显示给用户,下一步添加Index.cshtml视图在Views/Employee文件夹内,代码如下:
@{    ViewData["Title"] = "部门数据";}@model IEnumerable<Employee><div class="container">    <div class="row mb-3">        <div class="col-sm-3">            <a asp-action="Create" class="btn btn-secondary">新增</a>        </div>        <div class="col-sm-3"></div>        <div class="col-sm-3"></div>        <div class="col-sm-3"></div>    </div>    <div class="row mb-3">        <div class="col-sm">            <table class="table table-bordered align-middle">                <thead>                    <tr>                        <th>ID</th>                        <th>Name</th>                        <th>Designation</th>                        <th>Department</th>                    </tr>                </thead>                <tbody>                     @foreach (Employee emp in Model)                    {                        <tr>                            <td>@emp.Id</td>                            <td>@emp.Name</td>                            <td>@emp.Designation</td>                            <td>@emp.Department.Name</td>                        </tr>                    }                                    </tbody>            </table>        </div>    </div></div>
视图有一个IEnumerable<Employee>类型模型,我们读取相关联的department名称 @emp.Department.Name

运行应用程序,输入https://localhost:7234/Employee,我们发现所有的employee显示如下:

Entity Framework Core 读取数据

总结:

我们主要讲解EF Core读取数据的三种方式,也学习了如何包含子集合当读取关联记录时,我们也讨论了在应用程序中如何优化EF Core,在文章最后我们使用了EF Core进行数据查询,下一节我们讲解EF Core 中如何更新数据

源代码地址:
https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/EntityFrameworkCore/EFCoreReadRecords