欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

6. 关系

程序员文章站 2022-03-23 19:02:25
关系可以定义为一对一或一对多。对于多对多关系,在EF Core 2.0中,需要在关系中指定一个中间类,从而将该关系分割为一对多关系和多对一关系。关系可以使用约定、注释和流利API来指定。下一节将讨论这三种变体。1.使用约定的关系......

关系可以定义为一对一或一对多。对于多对多关系,在EF Core 2.0中,需要在关系中指定一个中间类,从而将该关系分割为一对多关系和多对一关系。

关系可以使用约定、注释和流利API来指定。下一节将讨论这三种变体。

1. 使用约定的关系

定义关系的第一个方法是使用约定。下面看一个使用Book和Chapter类型的例子。一本书可以有多个章节;因此,这是一对多关系。Book类型还定义了与作者的关联。这里,作者由User类表示。稍后,使用注释定义关系时,会解释这个名称的原因。书与作者定义了一对一的关系。(对于又多个作者的书,书中指定的作者是主要作者。)

书是由Book类定义的。这个类有一个主键BookId,它是根据其名称而创建的。关系由Chapters属性定义。Chapters属性为List<Chapter>类型;这就是Book类型定义一对多关系所需的全部内容。书与作者的关系由类型User的Author属性指定。还可以定义该类型的AuthorId属性来指定外键,该属性与User类中的键相同。如果没有这个定义,就会创建一个阴影属性:

    public class Book
    {
        public int BookId { get; set; }
        public string Title { get; set; }
        public List<Chapter> Chapters { get; } = new List<Chapter>();
        public User Author { get; set; }
    }

注意:

Chapter属性的另一种可能实现是定义List<Chapter>类型的读/写属性,而不需要实现创建实例。有了这样的实现,实例将自动从EF Core上下文中创建。

章节由Chapter类定义。使用Book属性定义关系。一对多关系定义了一方的集合(Book定义了Chapter对象的集合),和另一方的简单关系(Chapter定义了一个简单的属性Book)。使用一章的Book属性,可以直接访问相关的图书。对于这种类型,BookId属性指定Book的外键。正如Book类型所述,如果未将BookId指定为类的成员,则通过约定创建阴影属性:

    public class Chapter
    {
        public int ChapterId { get; set; }
        public int Number { get; set; }
        public string Title { get; set; }
        public int BookId { get; set; }
        public Book Book { get; set; }
    }

User类定义了主键的UserId属性、关系的Name属性和AuthoredBooks属性:

    public class User
    {
        public int UserId { get; set; }
        public string Name { get; set; }
        public List<Book> AuthoredBooks { get; set; }
    }

上下文只需要使用DbSet<T>类型的属性为Book、Chapter和User类型指定属性:

    public class BooksContext : DbContext
    {
        private const string ConnectionString =
            @"server=(localdb)\MSSQLLocalDb;database=Books;trusted_connection=true";
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);
            optionsBuilder.UseSqlServer(ConnectionString);
        }
        public DbSet<Book> Books { get; set; }
        public DbSet<Chapter> Chapters { get; set; }
        public DbSet<User> Users { get; set; }
    }

在启动应用程序时,使用按照约定定义的映射创建数据库。下图显示了Books、Chapters和Users表及其关系。Book类没有显示为User类型定义外键属性,而是创建一个AuthorUserId阴影属性来填充:

6. 关系

在介绍定义关系的不同方法(使用注释和流利API)之前,先看看如何使用查询访问相关数据。

2. 显示加载相关数据

如果查询书籍并希望显示相关属性(例如,相关章节和相关作者),则可以使用关系的显示加载。

请看下面的代码片段。查询请求所有具有指定标题的图书,并且只需要一个记录。如果试图在启动查询之后直接访问所得图书实例的Chapters和Author属性,那么这些属性的值为null。EF Core使用上下文的Entry方法来支持显示加载,该方法通过传递一个实体来返回EntityEntry对象。EntityEntry类定义了允许显示加载关系的Collection和Reference方法。对于一对多关系,可以使用Collection方法来指定集合,而一对一关系需要Reference方法来指定关系。使用Load方法进行显示加载:

        private static void ExplicitLoading()
        {
            Console.WriteLine(nameof(ExplicitLoading));
            using (var context = new BooksContext())
            {
                var book = context.Books
                    .Where(b => b.Title.StartsWith("Professional C# 7"))
                    .FirstOrDefault();
                if (book != null)
                {
                    Console.WriteLine($"book result: {book.Title}");
                    context.Entry(book).Collection(b=>b.Chapters).Load();
                    foreach (var chapter in book.Chapters)
                    {
                        Console.WriteLine($"chapters results: {chapter.Number}, {chapter.Title}");
                    }
                    context.Entry(book).Reference(b => b.Author).Load();
                    Console.WriteLine($"author result: {book.Author.Name}");
                }
            }
        }

将数据库表填充一些数据后,运行示例。

实现Load方法的NavigationEntry类也实现了IsLoaded属性,可以在其中检查关系是否已经加载。在调用Load方法之前,不需要检查加载的关系;在调用Load方法时,如果关系已经加载,就不会再次查询数据库。

当对图书的查询运行应用程序时,下面的SELECT语句将在SQL Server上执行。此查询仅访问Books表:

ExplicitLoading
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 2.1.1-rtm-30846 initialized 'BooksContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (31ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [b].[BookId], [b].[AuthorUserId], [b].[Title]
      FROM [Books] AS [b]
      WHERE [b].[Title] LIKE N'Professional C# 7' + N'%' AND (LEFT([b].[Title], LEN(N'Professional C# 7')) = N'Professional C# 7')
book result: Professional C# 7

Professaional C# 7为查询结果的Title。

使用以下Load方法检索书中的章节时,SELECT语句基于图书ID检索章节:

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (25ms) [Parameters=[@__get_Item_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
      SELECT [e].[ChapterId], [e].[BookId], [e].[Number], [e].[Title]
      FROM [Chapters] AS [e]
      WHERE [e].[BookId] = @__get_Item_0
chapters results: 5624, Professional C# 7
chapters results: 5625, Professional C# 7
chapters results: 5626, Professional C# 7

使用第三个查询,从Users表中检索用户信息:

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__get_Item_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
      SELECT [e].[UserId], [e].[Name]
      FROM [Users] AS [e]
      WHERE [e].[UserId] = @__get_Item_0
author result: Wrox

EF Core除了显示地加载相关数据,导致向SQL Server发送多个查询之外,还支持即使加载,如下所示。

3. 即使加载相关数据

当执行查询时,可以通过调用Include方法来指定关系,来立即加载相关数据。下面的代码片段包括成功应用Where表达式的书中的章节和作者:

        private static void EagerLoading()
        {
            Console.WriteLine(nameof(EagerLoading));
            using (var context = new BooksContext())
            {
                var book = context.Books
                    .Include(b => b.Chapters)
                    .Include(b => b.Author)
                    .Where(b => b.Title.StartsWith("Professional C# 7"))
                    .FirstOrDefault();
                if (book != null)
                {
                    Console.WriteLine(book.Title);
                    Console.WriteLine(book.Author.Name);
                    foreach (var chapter in book.Chapters)
                    {
                        Console.WriteLine($"{chapter.Number}, {chapter.Title}");
                    }
                }
            }
        }

使用Include,只需要执行一行SQL语句来访问Books表,并连接Chapters和Users表:

      SELECT [b.Chapters].[ChapterId], [b.Chapters].[BookId], [b.Chapters].[Number], [b.Chapters].[Title]
      FROM [Chapters] AS [b.Chapters]
      INNER JOIN (
          SELECT DISTINCT [t].*
          FROM (
              SELECT TOP(1) [b0].[BookId]
              FROM [Books] AS [b0]
              LEFT JOIN [Users] AS [b.Author0] ON [b0].[AuthorUserId] = [b.Author0].[UserId]
              WHERE [b0].[Title] LIKE N'Professional C# 7' + N'%' AND (LEFT([b0].[Title], LEN(N'Professional C# 7')) = N'Professional C# 7')
              ORDER BY [b0].[BookId]
          ) AS [t]
      ) AS [t0] ON [b.Chapters].[BookId] = [t0].[BookId]
      ORDER BY [t0].[BookId]

运行结果,共执行了两条SQL语句:

EagerLoading
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 2.1.1-rtm-30846 initialized 'BooksContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (29ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [b].[BookId], [b].[AuthorUserId], [b].[Title], [b.Author].[UserId], [b.Author].[Name]
      FROM [Books] AS [b]
      LEFT JOIN [Users] AS [b.Author] ON [b].[AuthorUserId] = [b.Author].[UserId]
      WHERE [b].[Title] LIKE N'Professional C# 7' + N'%' AND (LEFT([b].[Title], LEN(N'Professional C# 7')) = N'Professional C# 7')
      ORDER BY [b].[BookId]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [b.Chapters].[ChapterId], [b.Chapters].[BookId], [b.Chapters].[Number], [b.Chapters].[Title]
      FROM [Chapters] AS [b.Chapters]
      INNER JOIN (
          SELECT DISTINCT [t].*
          FROM (
              SELECT TOP(1) [b0].[BookId]
              FROM [Books] AS [b0]
              LEFT JOIN [Users] AS [b.Author0] ON [b0].[AuthorUserId] = [b.Author0].[UserId]
              WHERE [b0].[Title] LIKE N'Professional C# 7' + N'%' AND (LEFT([b0].[Title], LEN(N'Professional C# 7')) = N'Professional C# 7')
              ORDER BY [b0].[BookId]
          ) AS [t]
      ) AS [t0] ON [b.Chapters].[BookId] = [t0].[BookId]
      ORDER BY [t0].[BookId]
Professional C# 7
Wrox
5624, Professional C# 7
5625, Professional C# 7
5626, Professional C# 7

如果需要包含多个层次的关系,那么方法ThenInclude可以用于Include方法的结果。(并未提供演示示例)

4. 使用注释的关系

与使用约定不同,实体类型可以通过进行注释应用关系信息。下面向关系属性添加ForeignKey属性,来修改先前创建的Book类型,并指定表示外键的属性。在这里,Book不仅与该书的作者有关联,也与审稿人和项目编辑有关联。这些关系映射到User类型。外键属性定义为 int? 类型,让它们变成可选项。使用强制关系,EF Core创建级联删除;删除Book时,相关的作者、编辑和审稿人也被删除:

    public class Book
    {
        public int BookId { get; set; }
        public string Title { get; set; }
        public List<Chapter> Chapters { get; } = new List<Chapter>();

        public int? AuthorId { get; set; }
        [ForeignKey(nameof(AuthorId))]
        public User Author { get; set; }
        public int? ReviewerId { get; set; }
        [ForeignKey(nameof(ReviewerId))]
        public User Reviewer { get; set; }
        public int? ProjectEditorId { get; set; }
        [ForeignKey(nameof(ProjectEditorId))]
        public User ProjectEditor { get; set; }
    }

User类现在与Book类型有多个关联。WrittenBooks属性列出了添加为Author属性的User的所有图书。类似地,ReviewedBooks和EditedBooks属性与Book类型仅在Reviewer和ProjectEditor属性上相关联。如果相同类型之间存在多个关系,则需要使用InverseProperty特性对属性进行注释。使用这个特性,指定关系另一端(这里指Book类)的相关属性:

    public class User
    {
        public int UserId { get; set; }
        public string Name { get; set; }
        [InverseProperty("Author")]
        public List<Book> WrittenBooks { get; set; }
        [InverseProperty("Reviewer")]
        public List<Book> ReviewedBooks { get; set; }
        [InverseProperty("ProjectEditor")]
        public List<Book> EditedBooks { get; set; }
    }

下图显示了在SQL Server中生成的表的关系。Books表与Chapters表关联,如前面的示例所示。现在,Users表与Books表有三个关联。

6. 关系

5. 使用流利API的关系

 指定关系的最强大的方法是使用流利API。在流利API中,使用HasOne和WithOne方法定义一对一关系,用HasOne和WithMany方法定义一对多关系,而多对一关系由HasMany和WithOne定义。

对于下面的代码示例,模型类型不包括数据库模式上的任何注释。Book类型是一个简单的POCO类型,它定义了图书信息的属性,包括关系属性:

    public class Book
    {
        public int BookId { get; set; }
        public string Title { get; set; }
        public List<Chapter> Chapters { get; } = new List<Chapter>();

        public User Author { get; set; }
        public User Reviewer { get; set; }
        public User Editor { get; set; }
    }

User类型的定义也是类似的。除了具有Name属性外,User类型还定义了与Book类型的三种不同的关系:

    public class User
    {
        public int UserId { get; set; }
        public string Name { get; set; }
        public List<Book> WrittenBooks { get; set; }
        public List<Book> ReviewedBooks { get; set; }
        public List<Book> EditedBooks { get; set; }
    }

Chapter类与Book类有关系。然而,Chapter类与Book类不同,因为Chapter类还定义了一个属性来关联一个外键:BookId:

    public class Chapter
    {
        public int ChapterId { get; set; }
        public int Number { get; set; }
        public string Title { get; set; }
        public int BookId { get; set; }
        public Book Book { get; set; }
    }

模型类型之间的映射现在在BooksContext的OnModelCreating方法中定义。Book类型与多个Chapter对象相关联;这是使用HasMany和WithOne定义的。Chapter类与一个Book对象相关联;这是使用HasOne和WithMany定义的。因为在Chapter类中还有一个外键属性,所以使用HasForeignKey方法来指定这个键:

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Book>(entity =>
            {
                entity.HasMany(b => b.Chapters)
                .WithOne(c => c.Book);
                entity.HasOne(b => b.Author)
                .WithMany(w => w.WrittenBooks);
                entity.HasOne(b => b.Reviewer)
                .WithMany(r => r.ReviewedBooks);
                entity.HasOne(b => b.Editor)
                .WithMany(p => p.EditedBooks);
            });
            modelBuilder.Entity<Chapter>(entity =>
            {
                entity.HasOne(c => c.Book)
                .WithMany(b => b.Chapters)
                .HasForeignKey(c => c.BookId);
            });
            modelBuilder.Entity<User>(entity =>
            {
                entity.HasMany(u => u.WrittenBooks)
                .WithOne(b => b.Author);
                entity.HasMany(u => u.ReviewedBooks)
                .WithOne(b => b.Reviewer);
                entity.HasMany(u => u.EditedBooks)
                .WithOne(b => b.Editor);
            });
        }

6. 根据约定的每个层次结构的表

 

本文地址:https://blog.csdn.net/qq_41708190/article/details/107322586