EF Core中带过滤器参数的Include方法

9 篇文章 0 订阅
8 篇文章 0 订阅

概要

本文主要介绍EF Core 5.0中一个新特性,就是Include方法支持带过滤器的委托参数,并且说明一下该特性在实际使用中的一个大坑,希望读者在未来的开发中避免踩坑。

本文使用的是Dotnet 6.0和EF Core 7.0。

代码和实现

案例介绍

本文主要使用一个图书和作者的案例。

  • 一个作者Author有多本自己写的的图书Book
  • 一本图书Book有一个发行商Publisher
  • 一个作者Author是一个系统用户User

我们需要查找写书最多的前两名作家,该作家需要年龄在20岁以上,国籍是法国。需要他们的FirstName, LastName, Email,UserName以及在1900年以前他们发行的图书信息,包括书名Name和发行日期Published。

实体类定义详见附录。

基于新特性的代码实现

 using var dbContext = new AppDbContext();
            var date = new DateTime(1900, 1, 1);
            var authors = dbContext.Authors
                     .AsNoTracking()
                     .Include(x => x.Books.Where(b => b.Published < date))
                     .Include(x => x.User)
                     .Where(x => x.Country == "France" && x.Age >= 20)
                     .OrderByDescending(x => x.BooksCount)
                     .Take(2)
                     .ToList();

执行上述代码,生成的SQL如下:

      SELECT [t].[Id], [t].[Age], [t].[BooksCount], [t].[Country], [t].[NickName
], [t].[UserId], [u].[Id], [t0].[Id], [t0].[AuthorId], [t0].[ISBN], [t0].[Name],
 [t0].[Published], [t0].[PublisherId], [u].[Created], [u].[Email], [u].[EmailCon
firmed], [u].[FirstName], [u].[LastActivity], [u].[LastName], [u].[UserName]
      FROM (
          SELECT TOP(@__p_1) [a].[Id], [a].[Age], [a].[BooksCount], [a].[Country
], [a].[NickName], [a].[UserId]
          FROM [Authors] AS [a]
          WHERE [a].[Country] = N'France' AND [a].[Age] >= 20
          ORDER BY [a].[BooksCount] DESC
      ) AS [t]
      INNER JOIN [Users] AS [u] ON [t].[UserId] = [u].[Id]
      LEFT JOIN (
          SELECT [b].[Id], [b].[AuthorId], [b].[ISBN], [b].[Name], [b].[Published], [b].[PublisherId]
          FROM [Books] AS [b]
          WHERE [b].[Published] < @__date_0
      ) AS [t0] ON [t].[Id] = [t0].[AuthorId]
      ORDER BY [t].[BooksCount] DESC, [t].[Id], [u].[Id]

从执行结果来看Include(x => x.Books.Where(b => b.Published < date))生效了,LEFT JOIN 在左联[Books]表时候,确实先进行了过滤。

需求变更,过滤特性失效

上例返回的是Author对象,但是实际需要将其转换成AuthorWeb对象,再返回给前端,代码我们调整如下:

using var dbContext = new AppDbContext();
            var date = new DateTime(1900, 1, 1);
            var authors = dbContext.Authors
                   .AsNoTracking()
                   .Include(x => x.Books.Where(b => b.Published < date))
                   .Include(x => x.User)
                   .Where(x => x.Country == "France" && x.Age >= 20)
                   .OrderByDescending(x => x.BooksCount)
                   .Select(x => new AuthorWeb
                   {                                     
                       UserFirstName = x.User.FirstName,
                       UserLastName = x.User.LastName,
                       UserEmail = x.User.Email,
                       UserName = x.User.UserName,
                       BooksCount = x.BooksCount,
                       AllBooks = x.Books
                           .Select(y => new BookWeb
                           {
                               Name = y.Name,
                               Published = y.Published,
                           }).ToList(),
                       AuthorAge = x.Age,
                       AuthorCountry = x.Country,
                   }).ToList()
                   .Take(2)
                   .ToList();
            return authors;

执行上述代码,生成的SQL如下:

      SELECT [u].[FirstName], [u].[LastName], [u].[Email], [u].[UserName], [a].[
BooksCount], [a].[Id], [u].[Id], [b].[Name], [b].[Published], [b].[Id], [a].[Age
], [a].[Country]
      FROM [Authors] AS [a]
      INNER JOIN [Users] AS [u] ON [a].[UserId] = [u].[Id]
      LEFT JOIN [Books] AS [b] ON [a].[Id] = [b].[AuthorId]
      WHERE [a].[Country] = N'France' AND [a].[Age] >= 20
      ORDER BY [a].[BooksCount] DESC, [a].[Id], [u].[Id]

从生成的SQL,我们可以看到,在左联[Books]表时候,没有进行过滤,Include方法的过滤器特性失效。

失效的原因带过滤器参数的Include和Select两个方法无法共存,Select会让Include的过滤器失效。上面的例子如果我们一定要用Include的新特性,请使用下面的代码:

 using var dbContext = new AppDbContext();
	var date = new DateTime(1900, 1, 1);
	var authors = dbContext.Authors
	             .AsNoTracking()
	             .Include(x => x.Books.Where(b => b.Published < date))
	             .Include(x => x.User)
	             .Where(x => x.Country == "France" && x.Age >= 20)
	             .OrderByDescending(x => x.BooksCount)
	             .Take(2)                 
	             .AsEnumerable().Select(x => new AuthorWeb
	              {
	                  UserFirstName = x.User.FirstName,
	                  UserLastName = x.User.LastName,
	                  UserEmail = x.User.Email,
	                  UserName = x.User.UserName,
	                  BooksCount = x.BooksCount,
	                  AllBooks = x.Books.Select(y => new BookWeb                                                            
                      {
                          Name = y.Name,
                          Published = y.Published,
                      }).ToList(),
                     AuthorAge = x.Age,
                     AuthorCountry = x.Country,
                 }).ToList();

在从数据库获取到Author和Book的数据后,将序列转换成IEnumerable,EF将不会为返回IEnumerable的LINQ生成SQL。
所以最后生成的SQL如下:

      SELECT [t].[Id], [t].[Age], [t].[BooksCount], [t].[Country], [t].[NickNam
], [t].[UserId], [u].[Id], [t0].[Id], [t0].[AuthorId], [t0].[ISBN], [t0].[Name]
 [t0].[Published], [t0].[PublisherId], [u].[Created], [u].[Email], [u].[EmailCo
firmed], [u].[FirstName], [u].[LastActivity], [u].[LastName], [u].[UserName]
      FROM (
          SELECT TOP(@__p_1) [a].[Id], [a].[Age], [a].[BooksCount], [a].[Countr
], [a].[NickName], [a].[UserId]
          FROM [Authors] AS [a]
          WHERE [a].[Country] = N'France' AND [a].[Age] >= 20
          ORDER BY [a].[BooksCount] DESC
      ) AS [t]
      INNER JOIN [Users] AS [u] ON [t].[UserId] = [u].[Id]
      LEFT JOIN (
          SELECT [b].[Id], [b].[AuthorId], [b].[ISBN], [b].[Name], [b].[Publish
d], [b].[PublisherId]
          FROM [Books] AS [b]
          WHERE [b].[Published] < @__date_0
      ) AS [t0] ON [t].[Id] = [t0].[AuthorId]
      ORDER BY [t].[BooksCount] DESC, [t].[Id], [u].[Id]

从SQL语句来看,Include的过滤器特性重新生效。

虽然上面的解决方案可以解决Include的过滤器参数无法和Select共存的问题,但是毕竟需要转换成IEnumerable再进行处理,比较繁琐。

如果我们放弃Include方法,使用Select方法并传入过滤器参数,我们查看一下效果。

			using var dbContext = new AppDbContext();
            var date = new DateTime(1900, 1, 1);
            var authors = dbContext.Authors
                   .AsNoTracking()
                   .Include(x => x.User)
                   .Where(x => x.Country == "France" && x.Age >= 20)
                   .OrderByDescending(x => x.BooksCount)
                   .Take(2)                 
                   .Select(x => new AuthorWeb
                    {
                        UserFirstName = x.User.FirstName,
                        UserLastName = x.User.LastName,
                        UserEmail = x.User.Email,
                        UserName = x.User.UserName,
                        BooksCount = x.BooksCount,
                        AllBooks = x.Books
                        	.Where(x => x.Published < date)                                               																									      .Select(y => new BookWeb
                            {
                                 Name = y.Name,
                                 Published = y.Published,
                             }).ToList(),
                        AuthorAge = x.Age,
                        AuthorCountry = x.Country,
                    }).ToList();

.Where(x => x.Published < date)放到了Select中,同时删掉了Include User的语句,因为Select本身就能自动加载导航属性,生成联表语句。生成的SQL代码如下:

      SELECT [u].[FirstName], [u].[LastName], [u].[Email], [u].[UserName], [t].[
BooksCount], [t].[Id], [u].[Id], [t0].[Name], [t0].[Published], [t0].[Id], [t].[
Age], [t].[Country]
      FROM (
          SELECT TOP(@__p_0) [a].[Id], [a].[Age], [a].[BooksCount], [a].[Country
], [a].[UserId]
          FROM [Authors] AS [a]
          WHERE [a].[Country] = N'France' AND [a].[Age] >= 20
          ORDER BY [a].[BooksCount] DESC
      ) AS [t]
      INNER JOIN [Users] AS [u] ON [t].[UserId] = [u].[Id]
      LEFT JOIN (
          SELECT [b].[Name], [b].[Published], [b].[Id], [b].[AuthorId]
          FROM [Books] AS [b]
          WHERE [b].[Published] < @__date_1
      ) AS [t0] ON [t].[Id] = [t0].[AuthorId]
      ORDER BY [t].[BooksCount] DESC, [t].[Id], [u].[Id]

从最后生成的代码上看,Select方法加Where过滤器参数和Include方法加Where过滤器参数效果是一样的。

结论

Include方法新增的过滤器特性确实在某些情况下可以使我们的代码更加简便,但是受到不能和Select语句共存的问题影响,而且Select语句本身也能接收Where过滤器参数,再加上Select方法可以兼容EF Core5.0之前的版本,因此还是推荐使用Select。

附录

 public class Author
    {
        public int Id { get; set; }
        public int Age { get; set; }
        public string Country { get; set; }
        public int BooksCount { get; set; }
        public string NickName { get; set; }

        [ForeignKey("UserId")]
        public User User { get; set; }
        public int UserId { get; set; }
        public virtual List<Book> Books { get; set; } = new List<Book>();
    }
 public class Book
    {
        public int Id { get; set; }
        public string Name { get; set; }
        [ForeignKey("AuthorId")]
        public Author Author { get; set; }
        public int AuthorId { get; set; }
        public DateTime Published { get; set; }
        public string ISBN { get; set; }
        [ForeignKey("PublisherId")]
        public Publisher Publisher { get; set; }
        public int PublisherId { get; set; }
    }
public class Publisher
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime Established { get; set; }
    }
     public class User
    {
        public int Id { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string UserName { get; set; }
        public string Email { get; set; }
        public virtual List<UserRole> UserRoles { get; set; } = new List<UserRole>();
        public DateTime Created { get; set; }
        public bool EmailConfirmed { get; set; }
        public DateTime LastActivity { get; set; }
    }
    public class Role
    {
        public int Id { get; set; }
        public virtual List<UserRole> UserRoles { get; set; } = new List<UserRole>();
        public string Name { get; set; }
    }

public  class AuthorWeb
 {
     public DateTime UserCreated { get; set; }
     public bool UserEmailConfirmed { get; set; }
     public string UserFirstName { get; set; }
     public DateTime UserLastActivity { get; set; }
     public string UserLastName { get; set; }
     public string UserEmail { get; set; }
     public string UserName { get; set; }
     public int UserId { get; set; }
     public int AuthorId { get; set; }
     public int Id { get; set; }
     public int RoleId { get; set; }
     public int BooksCount { get; set; }
     public List<BookWeb> AllBooks { get; set; }
     public int AuthorAge { get; set; }
     public string AuthorCountry { get; set; }
     public string AuthorNickName { get; set; }
 }
 public class BookWeb
 {
         public int Id { get; set; }
         public string Name { get; set; }
         public DateTime Published { get; set; }
         public int PublishedYear { get; set; }
         public string PublisherName { get; set; }
         public string ISBN { get; set; }
     
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Summary Entity Framework Core in Action teaches you how to access and update relational data from .NET applications. Following the crystal-clear explanations, real-world examples, and around 100 diagrams, you'll discover time-saving patterns and best practices for security, performance tuning, and unit testing. Purchase of the print book includes a free eBook in PDF, Kindle, and ePub formats from Manning Publications. About the Technology There's a mismatch in the way OO programs and relational databases represent data. Enti ty Framework is an object-relational mapper (ORM) that bridges this gap, making it radically easier to query and write to databases from a .NET application. EF creates a data model that matches the structure of your OO code so you can query and write to your database using standard LINQ commands. It will even automatically generate the model from your database schema. About the Book Using crystal-clear explanations, real-world examples, and around 100 diagrams, Entity Framework Core in Action teaches you how to access and update relational data from .NET applications. You'l start with a clear breakdown of Entity Framework, long with the mental model behind ORM. Then you'll discover time-saving patterns and best practices for security, performance tuning, and even unit testing. As you go, you'll address common data access challenges and learn how to handle them with Entity Framework. What's Inside Querying a relational database with LINQ Using EF Core in business logic Integrating EF with existing C# applications Applying domain-driven design to EF Core Getting the best performance out of EF Core Covers EF Core 2.0 and 2.1

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值