ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

《Entity Framework Core in Action》--- 读书随记(1)

2022-07-17 17:05:12  阅读:151  来源: 互联网

标签:Core 数据库 EF Entity book 加载 public 随记


Part 1 Getting started

《Entity Framework Core in Action》
-- SECOND EDITION

Author: JON P SMITH

如果需要电子书的小伙伴,可以留下邮箱,看到了会发送的

1 Introduction to Entity Framework Core

1.8 Setting up the MyFirstEfCoreApp application

在创建任何数据库访问代码之前,您需要编写两个基本部分:

  • 您希望 EF Core 映射到数据库中的表的类
  • 应用程序的 DbContext,这是用于配置和访问数据库的主要类
public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public DateTime PublishedOn { get; set; }
    public int AuthorId { get; set; }
    public Author Author { get; set; }
}

这个实体类的主键,是BookId,它是根据EF Core 的命名约定<ClassName>Id,它还有一个导航属性Author,以及一个AuthorId指向Author的主键,这两个属性告诉 EF Core,您需要从 Book 类到 Author 类的链接,并且应该使用 AuthorId 属性作为外键来链接数据库中的两个表

public class AppDbContext : DbContext
{
    private const string ConnectionString = "Server=(localdb)\mssqllocaldb;Database=MyFirstEfCoreDb;Trusted_Connection=True”;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(connectionString);
    }

    public DbSet<Book> Books { get; set; }
}

这个类包含 EF Core 配置数据库映射所需的信息,也是您在代码中用于访问数据库的类

1.9 Looking under the hood of EF Core

1.9.1 Modeling the database

上图显示了 EF Core 在 AppDbContext 上使用的建模步骤,这在您第一次创建 AppDbContext 的实例时发生。(之后,缓存模型,以便快速创建后续实例。)下面是更加详细的步骤:

  • EF Core 查看应用程序的 DbContext 并找到所有 public DbSet < T > 属性。从这些数据中,它定义所找到的一个表的初始名称: Books
  • EF Core 查看 DbSet < T > 中引用的所有类,并查看其属性以计算列名、类型等。它还查找类和/或属性上提供额外建模信息的特殊属性
  • EF Core 查找 DbSet < T > 类所引用的任何类。在我们的示例中,Book 类有一个对 Author 类的引用,因此 EF Core 也会扫描该类。它对 Author 类的属性执行与步骤2中的 Book 类相同的搜索。它还采用类名 Author 作为表名
  • 对于建模过程的最后一个输入,EF Core 在应用程序的 DbContext 中运行 virtual 方法 OnModelCreate。在这个简单的应用程序中,没有覆盖 OnModelCreate 方法,但是如果覆盖了,您可以通过一个流畅的 API 提供额外的信息来进行更多的建模配置
  • EF Core 根据收集到的所有信息创建数据库的内部模型。这个数据库模型是缓存的,这样以后的访问会更快。然后使用此模型执行所有数据库访问

可能已经注意到上图没有显示数据库。这是因为当 EFCore 构建其内部模型时,它不会查看数据库。我强调这个事实是为了说明建立一个好的数据库模型有多么重要; 否则,如果 EF Core 认为数据库的外观和实际数据库的外观之间存在不匹配,就可能出现问题

在您的应用程序中,您可以使用 EF Core 来创建数据库,在这种情况下不会出现不匹配的情况。即便如此,如果您想要一个好的、高效的数据库,那么在代码中构建您想要的数据库的良好表示是值得注意的,这样创建的数据库就可以执行得很好

1.9.2 Reading data from the database

因为代码包含 AsNoTrace 命令,所以 EF Core 知道禁止创建跟踪快照。跟踪快照用于发现对数据的更改,由于此查询是只读的,因此取消跟踪快照会使命令更快

1.9.3 Updating the database

2 Querying the database

2.1 Setting the scene: Our book-selling site

2.1.1 The Book App’s relational database

ONE-TO-ONE RELATIONSHIP

在这个示例中,我使用了一个主键和一个外键,以使关系更容易理解。但是对于一对一关系,您也可以将外键作为主键。在图中所示的 PriceOffer 表中,有一个主键,名为 BookId,它也是外键。因此,将丢失 PriceOfferId 列,这使得表在数据库方面的效率略有提高

ONE-TO-MANY RELATIONSHIP

MANY-TO-MANY RELATIONSHIP: MANUALLY CONFIGURED

MANY-TO-MANY RELATIONSHIP: AUTOCONFIGURED BY EF CORE

和上面的我们自己写的中间表不一样,因为手动的那张除了双方的主键之外,还有order列这种额外信息,而自动的表是没有任何业务需求的

2.1.2 Other relationship types not covered in this chapter

除了上面说的三种主要的关系,EF Core 还支持其他的关系:

  • Owned Type class
  • Table splitting
  • Table per hierarchy (TPH)
  • Table per type (TPT)

2.1.4 The classes that EF Core maps to the database

数据库中的表关系总览

但是对应到entity中的时候,会有一点点不同,因为我们需要在entity中表达表之间的关系

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public DateTime PublishedOn { get; set; }
    public string Publisher { get; set; }
    public decimal Price { get; set; }
    public string ImageUrl { get; set; }

    //-----------------------------------------------
    //relationships

    public PriceOffer Promotion { get; set; }   // one-to-one
    public ICollection<Review> Reviews { get; set; }    // one-to-many-or-zero
    public ICollection<Tag> Tags { get; set; }  // EF Core 自动 many-to-many
    public ICollection<BookAuthor> AuthorsLink { get; set; }   // 手动指定 many-to-many
}

为了简单起见,我们使用 EF Core 的按照约定的配置方法对数据库进行建模。对于每个实体类中包含主键和外键的属性,我们使用按约定命名。此外,。NET 类型的导航属性,例如 ICollection < Review > Reviews,定义了我们想要的关系类型

2.2 Creating the application’s DbContext

2.2.2 Creating an instance of the application’s DbContext

前面展示了如何通过重写 OnConconfiguration 方法来设置应用程序的 DbContext。这种方法的缺点是连接字符串是固定的。下面,您将使用另一种方法,因为您希望使用不同的数据库进行开发和单元测试。您将使用一个通过应用程序的 DbContext 构造函数提供该数据库的方法

public EfCoreContext : DbContext
{
    public DbSet<Book> Books { get; set; }
    public DbSet<Author> Authors { get; set; }
    public DbSet<Tag> Tags { get; set; }
    public DbSet<PriceOffer> PriceOffers { get; set; }

    public EfCoreContext(DbContextOptions<EfCoreContext> options) : base(options)
    {}

    ...
}
const string connection = "";
var optionsBuilder = new DbContextOptionsBuilder<EfCoreContext>();
optionsBuilder.UseSqlServer(connection);
var options = optionsBuilder.Options;
using (var context = new EfCoreContext(options))
{
    var bookCount = context.Books.Count();
    // ...etc
}

2.2.3 Creating a database for your own application

有几种方法可以使用 EF Core 创建数据库,但通常的方法是使用 EF Core 的迁移特性。该特性使用应用程序的 DbContext 和 entity 类作为数据库结构的模型。“Add-Migration”命令首先对数据库建模,然后使用该模型构建命令以创建符合该模型的数据库

除了处理数据库创建之外,迁移的伟大之处在于,它们可以使用您在软件中所做的任何更改来更新数据库。如果您更改了实体类或应用程序的任何 DbContext 配置,则“Add-Migration”命令将构建一组命令来更新现有数据库。下面是添加迁移和创建或迁移数据库所需的步骤

  • 包含 DbContext 的项目需要 NuGet 包 Microsoft.EntityFrameworkCore.SqlServer 或其他数据库提供程序(如果使用的是其他数据库)
  • ASP.NET Core 还有需要 Microsoft.EntityFrameworkCore.Tools 包
  • (因为我是在Linux环境下,所以我直接使用dotnet命令行工具)然后执行命令 dotnet ef migrations add MyMigrationName
  • 执行指令 dotnet ef database update ,将生成的迁移指令应用到数据库,如果不存在数据库,指令将创建一个。如果存在数据库,该命令将检查该数据库是否已将此迁移应用于该数据库,如果缺少任何数据库迁移,则该命令将它们应用于该数据库

2.3 Understanding database queries

2.3.1 Application’s DbContext property access

命令的第一部分通过 EF Corea 连接到数据库。引用数据库表的最常见方法是通过应用程序的 DbContext 中的 DbSet < T > 属性

基本思想是相同的: 您需要从通过 EF Core 连接到数据库的东西开始

2.3.2 A series of LINQ/EF Core commands

命令的主要部分是一组 LINQ 和/或 EF Core 方法,它们可以创建您需要的查询类型。LINQ 查询可以非常简单,也可以非常复杂

2.3.3 The execute command

命令的最后一部分揭示了一些关于 LINQ 的东西。在 LINQ 命令序列的末尾应用了最终的执行命令之前,LINQ 被保存为表达式树中的一系列命令,这意味着它还没有在数据上执行。EF Core 可以将表达式树转换为您正在使用的数据库的正确命令

现在我想解释一下,作为一个开发人员,您如何访问这些关系背后的数据。可以通过四种方式加载数据: 即时加载、显式加载、选择加载和延迟加载。然而,在我介绍这些方法之前,您需要知道 EF Core 不会加载实体类中的任何关系,除非您要求它这样做。如果加载 Book 类,则 Book 实体类中的每个关系属性在默认情况下都将为空

2.4.1 Eager loading: Loading relationships with the primary entity class

Eager loading 通过两种 fluent 方法指定,Include 和 ThenInclude

var firstBook = context.Books
        .Include(book => book.AuthorsLink)
            .ThenInclude(bookAuthor => bookAuthor.Author)
        .Include(book => book.Reviews)
        .Include(book => book.Tags)
        .Include(book => book.Promotion)
        .FirstOrDefault();

SORTING AND FILTERING WHEN USING INCLUDE AND/OR THENINCLUDE

var firstBook = context.Books
    .Include(book => book.AuthorsLink
        .OrderBy(bookAuthor => bookAuthor.Order))
        .ThenInclude(bookAuthor => bookAuthor.Author)
    .Include(book => book.Reviews
        .Where(review => review.NumStars == 5))
    .Include(book => book.Promotion)
    .First();

2.4.2 Explicit loading: Loading relationships after the primary entity class

加载主实体类之后,可以显式加载所需的任何其他关系

// 首先查询主实体
var firstBook = context.Books.First();
// 然后是加载关系实体,这个是 手动 many-to-many
context.Entry(firstBook).Collection(book => book.AuthorsLink).Load();
foreach (var authorLink in firstBook.AuthorsLink)
{
    context.Entry(authorLink).Reference(bookAuthor => bookAuthor.Author).Load();
}

context.Entry(firstBook).Collection(book => book.Tags).Load();

context.Entry(firstBook).Reference(book => book.Promotion).Load();

或者,可以使用显式加载来对关系应用查询,而不是加载关系

var firstBook = context.Books.First();
var numReviews = context.Entry(firstBook)
        .Collection(book => book.Reviews)
        .Query().Count();

var starRatings = context.Entry(firstBook)
        .Collection(book => book.Reviews)
        .Query().Select(review => review.NumStars)
        .ToList();

显式加载的缺点是更多的数据库往返,这可能是低效的。如果您事先知道需要的数据,那么即时加载数据通常更有效,因为加载关系所需的数据库往返次数更少

2.4.3 Select loading: Loading specific parts of primary entity class and any relationships

加载数据的第三种方法是使用 LINQ Select 方法来选择您想要的数据,我称之为选择加载

var books = context.Books
    .Select(book => new
        {
            book.Title,
            book.Price,
            NumReviews = book.Reviews.Count,
        })
    .ToList();

这种方法的优点是只加载您需要的数据,如果您不需要所有数据,那么加载效率会更高

2.4.4 Lazy loading: Loading relationships as required

延迟加载使编写查询变得容易,但对数据库性能有不良影响。延迟加载确实需要对 DbContext 或实体类进行一些更改,但是在进行这些更改之后,读取就很容易了; 如果访问未加载的导航属性,EF Core 将执行一个数据库查询来加载该导航属性

您可以通过以下两种方式设置延迟加载:

  • 添加 Microsoft.EntityFrameworkCore.Proxies 包,用来配置DbContext
  • 通过构造函数将延迟加载方法注入到实体类中

第一个选项很简单,但是将您锁定在为所有关系设置延迟加载。第二个选项要求您编写更多代码,但允许您选择使用延迟加载的关系

现在只会讲解第一种方式,后面才会讲第二种

第一种方式需要两步:

  • 在每个关系属性前面添加关键字 virtual
  • 在设置 DbContext 时添加 UseLazyLoadingProxy 方法
public class BookLazy
{
    public int BookLazyId { get; set; }
    //… Other properties left out for clarity
    public virtual PriceOffer Promotion { get; set; }
    public virtual ICollection<Review> Reviews { get; set; }
    public virtual ICollection<BookAuthor> AuthorsLink { get; set; }
}
var optionsBuilder = new DbContextOptionsBuilder<EfCoreContext>();
optionsBuilder
    .UseLazyLoadingProxies()
    .UseSqlServer(connection);
var options = optionsBuilder.Options;
using (var context = new EfCoreContext(options))

如果在实体类中配置了延迟加载,并按照创建 DbContext 的方式,那么读取关系就很简单; 在查询中不需要额外的 Include 方法,因为当代码访问关系属性时,数据是从数据库中加载的

许多开发人员认为延迟加载是有用的,但我避免它是因为它的性能问题。对数据库服务器的每次访问都有时间开销,因此最佳方法是尽量减少对数据库服务器的调用次数。但是延迟加载(和显式加载)可以创建大量的数据库访问,使查询变慢,并导致数据库服务器工作更加困难

2.6 Building complex queries

public static IQueryable<BookListDto> MapBookToDto(this IQueryable<Book> books)
{
    return books.Select(book => new BookListDto
    {
        BookId = book.BookId,
        Title = book.Title,
        Price = book.Price,
        PublishedOn = book.PublishedOn,
        ActualPrice = book.Promotion == null
            ? book.Price
            : book.Promotion.NewPrice,
        PromotionPromotionalText = book.Promotion == null
            ? null
            : book.Promotion.PromotionalText,
        AuthorsOrdered = string.Join(", ",
            book.AuthorsLink
                .OrderBy(ba => ba.Order)
                .Select(ba => ba.Author.Name)),
        ReviewsCount = book.Reviews.Count,
        ReviewsAverageVotes = book.Reviews
                .Select(review => (double?) review.NumStars).Average(),
        TagStrings = book.Tags.Select(x => x.TagId).ToArray(),
    });
}

2.8 Adding sorting, filtering, and paging

public static IQueryable<BookListDto> OrderBooksBy(this IQueryable<BookListDto> books, OrderByOptions orderByOptions)
{
    switch (orderByOptions)
    {
        case OrderByOptions.SimpleOrder:
            return books.OrderByDescending(x => x.BookId);
        case OrderByOptions.ByVotes:
            return books.OrderByDescending(x => x.ReviewsAverageVotes);
        case OrderByOptions.ByPublicationDate:
            return books.OrderByDescending(x => x.PublishedOn);
        case OrderByOptions.ByPriceLowestFirst:
            return books.OrderBy(x => x.ActualPrice);
        case OrderByOptions.ByPriceHighestFirst:
            return books.OrderByDescending(x => x.ActualPrice);
        default:
            throw new ArgumentOutOfRangeException(nameof(orderByOptions), orderByOptions, null);
    }
}
public static IQueryable<BookListDto> FilterBooksBy(this IQueryable<BookListDto> books,BooksFilterBy filterBy, string filterValue)
{
    if (string.IsNullOrEmpty(filterValue))
        return books;
    switch (filterBy)
    {
        case BooksFilterBy.NoFilter:
            return books;
        case BooksFilterBy.ByVotes:
            var filterVote = int.Parse(filterValue);
            return books.Where(x => x.ReviewsAverageVotes > filterVote);
        case BooksFilterBy.ByTags:
            return books.Where(x => x.TagStrings.Any(y => y == filterValue));
        case BooksFilterBy.ByPublicationYear:
            if (filterValue == AllBooksNotPublishedString)
                return books.Where(x => x.PublishedOn > DateTime.UtcNow);

            var filterYear = int.Parse(filterValue);

            return books.Where(
                x => x.PublishedOn.Year == filterYear
                && x.PublishedOn <= DateTime.UtcNow);
        default:
            throw new ArgumentOutOfRangeException(nameof(filterBy), filterBy, null);
    }
}
public static IQueryable<T> Page<T>(this IQueryable<T> query,int pageNumZeroStart, int pageSize)
{
    if (pageSize == 0)
        throw new ArgumentOutOfRangeException(nameof(pageSize), "pageSize cannot be zero.");

    if (pageNumZeroStart != 0)
        query = query.Skip(pageNumZeroStart * pageSize);

    return query.Take(pageSize);
}

2.9 Putting it all together: Combining Query Objects

public class ListBooksService
{
    private readonly EfCoreContext _context;
    public ListBooksService(EfCoreContext context)
    {
        _context = context;
    }

    public IQueryable<BookListDto> SortFilterPage(SortFilterPageOptions options)
    {
        var booksQuery = _context.Books
                .AsNoTracking()
                .MapBookToDto()
                .OrderBooksBy(options.OrderByOptions)
                .FilterBooksBy(options.FilterBy,options.FilterValue);
        options.SetupRestOfDto(booksQuery);
        return booksQuery.Page(options.PageNum-1,options.PageSize);
    }
}

标签:Core,数据库,EF,Entity,book,加载,public,随记
来源: https://www.cnblogs.com/huangwenhao1024/p/16487716.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有