EFCore学习笔记(9)——加载关联数据

19 篇文章 1 订阅

一、引言

如果完全跟着微软文档来学EF Core,那等学完,项目都凉透了。
光一个创建模型都有十几二十节。
所以应根据实际需要,对重点章节进行学习,一些知识点比较细的、深入的等后续要用到再学。
对我来说,除了基本的CRUD,还有一个很重要的功能就是访问相关联的表。就是点击表上的某个字段,可以跳转查看该字段相关联的表的详细信息。
而我在Query data章节中,发现了Load related data这一节,节中内容与这个功能有很大相关性。

二、 加载关联数据

1. 概述

EF Core允许你在model中使用导航属性来加载关联的实体。下面是三种常用的O/RM模式,用于加载关联数据。

  • 贪婪加载,Eager Loading,也叫预先加载,表示相关数据作为初始查询的一部分从数据库加载。
  • 显式加载,Explicit Loading,表示稍后从数据库显式加载相关数据。
  • 懒汉加载,Lazy Loading,也叫延迟加载,当访问导航属性时,相关数据才从数据库中显示加载。

2. 贪婪加载相关数据

2.1. 贪婪加载

你可以使用Include方法来指定相关数据包含在查询结果中。下面示例,结果中返回的blogs将会使其Posts属性用关联的posts填充。

Include方法的作用可以理解为让查询结果中包含xx数据

using (var context = new BloggingContext())
{
	var blogs = context.Blogs
		.Include(blog => blog.Posts)
		.ToList();
}

提示:
EF Core会自动地根据导航属性转到任何其他实体,这些实体会预先加载到上下文实例中。
因此,即使你没有显式地包含导航属性的数据,如果之前加载了一些或所有相关实体,该属性仍然可能被填充。

你可以在单个查询中包含多个关系的关联数据。

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .Include(blog => blog.Owner)
        .ToList();
}

警告:
在单个查询中贪婪加载一个集合导航属性可能会带来性能问题。

2.2. 包含多级

你可以使用ThenInclude方法向下挖掘关系,来包含多级的关联数据。

using (var context = new BloggingContext())
{
	// 相当于找到更远的关联数据
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ToList();
}

还可以链接多级调用到ThenInclude上,来继续包含更多级的关联数据:

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ThenInclude(author => author.Photo)
        .ToList();
}

你还可以合并所有调用,以在同一个查询中包含来自多个层级和多个根的相关数据。

using (var context = new BloggingContext())
{
	// 合并多个多级查询
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ThenInclude(author => author.Photo)
        .Include(blog => blog.Owner)
        .ThenInclude(owner => owner.Photo)
        .ToList();
}

你也许希望为包含的一个实体包含多个相关实体。
例如,在查询Blogs时,你需要包含Posts,并希望同时包含Posts的Author和Tags。
要使这两者都包含在内,你需要指定从根开始的包含路径。例如,Blog -> Posts -> AuthorBlog -> Posts -> Tags。这并不意味着你会得到多余的连接;大多数情况下,EF会在生成SQL时合并这些连接。

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Tags)
        .ToList();
}

提示:
你还能使用单个Include方法来加载多个导航。导航指的是a.b这种引用
对于所有引用的导航“链”,或当它们以单个集合结束时,这是可能的。

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Owner.AuthoredPosts)
        .ThenInclude(post => post.Blog.Owner.Photo)
        .ToList();
}

2.3. 带过滤的包含

注意:
该特性是EF Core 5.0引入的。

当Include应用于加载关联数据时,你可以将某些可枚举的操作添加到所包含的集合导航中,从而允许对结果进行筛选和排序。

支持的操作有:Where、OrderBy、OrderByDescending、ThenBy、ThenByDescending、Skip和Take。

这样的操作应该应用于传给Include方法的lambda中的集合导航,如下面例子:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(
            blog => blog.Posts
                .Where(post => post.BlogId == 1)
                .OrderByDescending(post => post.Title)
                .Take(5))
        .ToList();
}

每个包含的导航只允许一组唯一的筛选器操作。对于给定的集合导航(下例中的blog.Posts)应用多个Include操作,过滤器操作只能指定其中一个:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToList();
}

相反的是,为每个导航使用同样的操作是可以多次的:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToList();
}

警告:
在跟踪查询的情况下,由于导航修正,Filtered Include的结果可能是意料之外的。
所有之前查询过并存储在Change Tracker中的相关实体将会呈现在Filtered Include查询的结果中,即使它们不满足过滤器的要求。当在这些情况下使用Filtered Include时,考虑使用NoTracking查询或重新创建上下文。

var orders = context.Orders.Where(o => o.Id > 1000).ToList();

// customer entities will have references to all orders where Id > 1000, rather than > 5000
var filtered = context.Customers.Include(c => c.Orders.Where(o => o.Id > 5000)).ToList();

2.4. 派生类中的Include

你可以使用IncludeThenInclude来包含只定义在派生类中的相关数据。
给定一个model:

public class SchoolContext : DbContext
{
    public DbSet<Person> People { get; set; }
    public DbSet<School> Schools { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<School>().HasMany(s => s.Students).WithOne(s => s.School);
    }
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Student : Person
{
    public School School { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Student> Students { get; set; }
}

所有学生的School导航的内容都可以使用多种模式贪婪记载:

  • 使用投射:
context.People.Include(person => ((Student)person).School).ToList()
  • 使用as操作符:
context.People.Include(person => (person as Student).School).ToList()
  • 使用Include的重载来接受一个string类型的参数:
context.People.Include("School").ToList()

2.5. 自动包含导航的模型配置

注意:
该特性是EF Core 6.0引入的。

你可以在模型中配置一个导航,以便每次使用AutoInclude方法从数据库中加载实体时包含该导航。在结果中返回的实体类型

3. 显式加载关联数据

3.1. 显式加载

通过DbContext.Entry(…) API可以显式地加载一个导航属性。

using (var context = new BloggingContext())
{
    var blog = context.Blogs		// 得到上下文中的Blog实体集合
        .Single(b => b.BlogId == 1);// 获取BlogId为1的Blog实体

    context.Entry(blog)				// 加载blog实体中的导航属性
        .Collection(b => b.Posts)	// 获取集合导航属性Posts
        .Load();

    context.Entry(blog)				// 加载blog实体中的导航属性
        .Reference(b => b.Owner)	// 获取引用导航属性Owner
        .Load();
}

你还可以通过执行返回关联实体的单独查询来显式加载导航属性。如果启用了变更跟踪(change tracking),则当查询具体化一个实体时,EF Core会自动设置新加载实体的导航属性指向任何已经加载的实体,并设置已经加载的实体的导航属性指向新加载的实体。

3.2. 查询关联实体

你还可以获得一个表示导航属性内容的LINQ查询。
它允许你使用查询上的其他操作符。例如,对关联的实体使用合计操作符,而不需要将它们加载到内存中。

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var postCount = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Count();
}

还可以筛选哪些关联实体加载到内存中,

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var goodPosts = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Where(p => p.Rating > 3)
        .ToList();
}

4. 懒汉(延迟)加载关联数据

4.1. 使用代理(proxies)懒汉加载

懒汉加载最简单的方式是通过安装Microsoft.EntityFrameworkCore.Proxies包并且启用它,然后使用UseLazyLoadingProxies调用。例如:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);

或在使用AddDbContext时:

.AddDbContext<BloggingContext>(
    b => b.UseLazyLoadingProxies()
          .UseSqlServer(myConnectionString));

EF Core将为任何可以被重写的导航属性启用懒汉加载——就是说,它必须是virtual的并且在一个可被继承的类上(非sealed)。例如,在以下实体中,Post.BlogBlog.Posts导航属性是懒汉加载的。

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public virtual Blog Blog { get; set; }
}

警告
懒汉加载可能会导致不必要的额外数据库往返(所谓的N+1问题.,应该避免这种情况)。

4.2. 不使用代理的懒汉加载

没有代理的懒汉加载通过将==ILazyLoader ==服务注入实体中,正如 Entity Type Constructors中所描述的。例如:

public class Blog
{
    private ICollection<Post> _posts;

    public Blog()
    {
    }

    private Blog(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts
    {
        get => LazyLoader.Load(this, ref _posts);
        set => _posts = value;
    }
}

public class Post
{
    private Blog _blog;

    public Post()
    {
    }

    private Post(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog
    {
        get => LazyLoader.Load(this, ref _blog);
        set => _blog = value;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!感谢提问。下面是有关 K210 学习笔记五中串口通信的一些内容: 在 K210 开发板上,串口是一种常见的通信接口,用于将数据从一个设备传输到另一个设备。串口通信通常使用 UART (Universal Asynchronous Receiver/Transmitter) 协议。 要在 K210 上进行串口通信,首先需要使用两个引脚:一个用于接收数据(RX),一个用于发送数据(TX)。这些引脚可以通过 GPIO 端口与外部设备连接。 在 K210 的开发环境中,可以使用 MaixPy 或者 Kendryte Standalone SDK 进行串口编程。 在 MaixPy 中,可以使用 `uart` 模块来进行串口通信。例如,要初始化一个串口对象并设置波特率为 115200,可以使用以下代码: ```python from machine import UART uart = UART(UART.UART1, 115200) ``` 然后,可以使用 `uart.write()` 函数发送数据,使用 `uart.read()` 函数接收数据。例如: ```python uart.write("Hello, world!\n") data = uart.read(10) ``` 在 Kendryte Standalone SDK 中,可以使用 `uart.h` 头文件中的函数来进行串口通信。例如,要初始化一个串口对象并设置波特率为 115200,可以使用以下代码: ```c #include "uart.h" uart_init(UART_DEVICE_1, 115200); ``` 然后,可以使用 `uart_send_data()` 函数发送数据,使用 `uart_receive_data()` 函数接收数据。例如: ```c uart_send_data(UART_DEVICE_1, "Hello, world!\n", 14); char buffer[10]; uart_receive_data(UART_DEVICE_1, buffer, 10); ``` 以上是关于 K210 学习笔记五中串口通信的简要介绍。如果你有更具体的问题,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值