第三单元 模型创建

发布时间 2023-12-11 16:57:09作者: 誉尚学教育

模型创建分为正向工程(CodeFirst)与反向工程(DbFirst).

正向工程的模型配置也可以创建任意的数据库关系对象,如:字段,字段说明,表,索引,外键等等。

可在派生上下文中替代 OnModelCreating 方法,并使用 ModelBuilder API 来配置模型。 此配置方法最为有效,并可在不修改实体类的情况下指定配置。 Fluent API 配置具有最高优先级,并将替代约定和数据注解(特性)。

由于FluentAPI编写起来比较麻烦,实际开发中很少手写。

using Microsoft.EntityFrameworkCore;
​
namespace EFModeling.EntityProperties.FluentAPI.Required;
​
internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
​
    #region Required
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Url)
            .IsRequired();
    }
    #endregion
}
​
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

 

1. 分组配置

为了减小 OnModelCreating 方法的大小,可以将实体类型的所有配置提取到实现 IEntityTypeConfiguration 的单独类中。

public class BlogEntityTypeConfiguration : IEntityTypeConfiguration<Blog>
{
    public void Configure(EntityTypeBuilder<Blog> builder)
    {
        builder
            .Property(b => b.Url)
            .IsRequired();
    }
}

 

然后,只需从 OnModelCreating 调用 Configure 方法。

new BlogEntityTypeConfiguration().Configure(modelBuilder.Entity<Blog>());

 

使用程序集批量添加

将指定类型的程序集中所有实现IEntityTypeConfiguration 接口的类型都添加至配置中。

modelBuilder.ApplyConfigurationsFromAssembly(typeof(BlogEntityTypeConfiguration).Assembly);

 

备注

应用配置的顺序是不确定的,因此仅当顺序不重要时才应使用此方法。

 

2. 表、属性配置

1. 表配置

指定表名

fluent API

public class BlogEntityConfig:IEntityTypeConfiguration<Blog>
{
    public void Configure(EntityTypeBuilder<Blog> builder)
    {
        builder.ToTable("blog");
    }
}

 

数据注解

[Table("blog")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

 

指定Schema

注意,SqlServer 是可以指定schema ,但是mysql中并不存在schema ,所以不能指定

fluentAPI:

public class BlogEntityConfig:IEntityTypeConfiguration<Blog>
{
    public void Configure(EntityTypeBuilder<Blog> builder)
    {
        builder.ToTable("blog","renwoxing");// 只能在SQLServer下指定schema
    }
}

 

数据注解

[Table("blogs", Schema = "blogging")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

 

也可以统一指定所有:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasDefaultSchema("renwoxing");// 默认的schema 为dbo
    modelBuilder.ApplyConfigurationsFromAssembly(typeof(Blog).Assembly);
}

 

表注释

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().ToTable(
        tableBuilder => tableBuilder.HasComment("博客"));
}

 

数据注解

[Comment("博客")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

 

排除表

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Ignore<Blog>();
}

 

数据注解

[NotMapped]
public class BlogMetadata
{
    public DateTime LoadedFromDatabase { get; set; }
}

 

迁移中排除

EF Core 5.0 中引入了从迁移中排除表的功能。它可将相同的实体类型映射到多个 DbContext 类型中(也就是多个DbContext中都可以使对同一个实体类型进行操作)。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<IdentityUser>()
        .ToTable("AspNetUsers", t => t.ExcludeFromMigrations());
}

 

此配置迁移不会创建 AspNetUsers 该表,但 IdentityUser 仍包含在模型中,并且可正常使用。

如果需要再次使用迁移来管理表,则应创建不包括 AspNetUsers 的新迁移。 下一次迁移将包含对表所做的任何更改。

导航属性表

按照约定,上下文的 DbSet 属性中公开的类型作为实体包含在模型中。 还包括在 OnModelCreating 方法中指定的实体类型,以及通过递归探索其他发现的实体类型的导航属性找到的任何类型。

下面的代码示例中包含了所有类型:

  • 包含 Blog,因为它在上下文的 DbSet 属性中公开。

  • 包含 Post,因为它是通过 Blog.Posts 导航属性发现的。

  • 包含 AuditEntry因为它是 OnModelCreating 中指定的。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<AuditEntry>();
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; } // 集合导航属性
}

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

    public Blog Blog { get; set; }// 引用导航属性,并自动创建外键BlogId ,<导航属性名><主键属性名>
}

public class AuditEntry
{
    public int AuditEntryId { get; set; }
    public string Username { get; set; }
    public string Action { get; set; }
}

 

 

 

2. 属性配置

按照约定,所有具有 Getter 和 Setter 的公共属性都将包含在模型中。

指定列名

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.BlogId)
        .HasColumnName("blog_id");
}

 

数据注解

public class Blog
{
    [Column("blog_id")]
    public int BlogId { get; set; }

    public string Url { get; set; }
}

 

数据类型

fluentAPI

例如,SQL Server 将 DateTime 属性映射到 datetime2(7) 列,将 string 属性映射到 nvarchar(max) 列(或对于用作键的属性,映射到 nvarchar(450))。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        eb =>
        {
            eb.Property(b => b.Url).HasColumnType("varchar(200)");
            eb.Property(b => b.Rating).HasColumnType("decimal(5, 2)");
        });
}

 

数据注解

public class Blog
{
    public int BlogId { get; set; }

    [Column(TypeName = "varchar(200)")]
    public string Url { get; set; }

    [Column(TypeName = "decimal(5, 2)")]
    public decimal Rating { get; set; }
}

 

最大长度

配置最大长度可向数据库提供程序提供有关为给定属性选择适当列数据类型的提示。 最大长度仅适用于数组数据类型,如 stringbyte[]

备注

在向提供程序传递数据之前,实体框架不会执行任何最大长度的验证。 而是由提供程序或数据存储根据情况进行验证。 例如,当面向 SQL Server 时,超过最大长度将导致异常,因为基础列的数据类型不允许存储多余的数据。

在下面的示例中,将最大长度配置为 500 将导致在 SQL Server 上创建 nvarchar(500) 类型的列:

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Url) // mysql为varchar(500),sqlserver nvarchar(500)
        .HasMaxLength(500);
}

 

数据注解

public class Blog
{
    public int BlogId { get; set; }

    [MaxLength(500)]
    public string Url { get; set; }
}

 

必需与可选

按照约定,其 .NET 类型可包含 null 的属性将配置为可选属性,而 .NET 类型不能包含 null 的属性将配置为必需属性。 例如,所有具有 .NET 值类型 (int``decimalbool等) 的属性都配置为必需,并且具有可为 null 的 .NET 值类型的所有属性 (int?``decimal?bool?等) 配置为可选。

C# 8 引入了一项名为可为 null 引用类型 (NRT) 的新功能,该功能允许对引用类型进行批注,指示引用类型能否包含 null。 默认情况下,此功能在新项目模板中处于启用状态,但在现有项目中保持禁用状态,除非显式选择启用此功能。 可为 Null 的引用类型通过以下方式影响 EF Core 的行为:

  • 如果禁用可为 null 的引用类型,则使用 .NET 引用类型的所有属性都按约定 ((例如 string) )配置为可选。

  • 如果启用了可为 null 的引用类型,则基于属性的 .NET 类型的 C# 为 Null 性来配置属性:string? 将配置为可选属性,但 string 将配置为必需属性。

以下示例显示了具有必需属性和可选属性的实体类型,禁用并启用可为 null 的引用功能:

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Url)
        .IsRequired(); // 表示必须有值才能入库,如果未加,则表示可选
}

 

数据注解

public class CustomerWithoutNullableReferenceTypes
{
    public int Id { get; set; }

    [Required] 
    public string FirstName { get; set; }

    [Required]
    public string LastName { get; set; }

    public string MiddleName { get; set; } // 可选非必填
}

 

列注释

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Url)
        .HasComment("博客地址");
}

 

数据注解

public class Blog
{
    public int BlogId { get; set; }

    [Comment("博客地址")]
    public string Url { get; set; }
}

 

 

3. 表约束、索引

1. 主键约束

默认主键

根据约定,名为 Id<type name>Id 的属性将被配置为实体的主键。

internal class Car
{
    public string Id { get; set; } // 约定为主键
    public string LicensePlate { get; set; } 
    public string Make { get; set; }
    public string Model { get; set; }
}

internal class Truck
{
    public string TruckId { get; set; } // 约定为主键

    public string Make { get; set; }
    public string Model { get; set; }
}

 

指定主键

可将单个属性配置为实体的主键,如下所示:

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Car>()
        .HasKey(c => c.LicensePlate);
}

 

数据注解

internal class Car
{
    [Key]
    public string LicensePlate { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

 

 

联合主键

还可将多个属性配置为实体的键 - 这称为组合键。

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Car>()
        .HasKey(c => new { c.State, c.LicensePlate });
}

 

数据注解

属性 [PrimaryKey] 是在 EF Core 7.0 中引入的。 在旧版本中使用 Fluent API。

[PrimaryKey(nameof(State), nameof(LicensePlate))]
internal class Car
{
    public string State { get; set; }
    public string LicensePlate { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

 

 

主键名称

根据约定,在关系数据库上,主键使用名称 PK_<type name> 进行创建。也 可按如下方式配置主键约束的名称:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(b => b.BlogId)
        .HasName("PrimaryKey_BlogId");// 默认名称是:pk_blog
}

 

2. 值自动生成

默认值约束

在关系数据库中,可以为列配置默认值;如果插入的行没有该列的值,则将使用默认值。

可以在属性上配置默认值:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Rating)
        .HasDefaultValue(3);
}

 

还可以指定用于计算默认值的 SQL 片段:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Created)
        .HasDefaultValueSql("getdate()");
}

 

主键自增

按照约定,如果应用程序未提供值,则将类型为 short、int、long 或 Guid 的非复合主键设置为针对插入的实体自动生成值。

数据类型的主键值会设置为自增长。也可以使用如下语句取消自增长

modelBuilder.Entity<Blog>()
     .Property(b => b.BlogId).ValueGeneratedNever(); // 取消自动增长

 

也可以显示配置值:

fluentAPI

// 添加时生成值
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Inserted)
        // .HasDefaultValue(DateTime.Now) // SqlServer下必须添加这句话
        .ValueGeneratedOnAdd(); // 一般用于主键自增
}

 

数据注解

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public DateTime Inserted { get; set; }
}

 

 

非主键值生成

同样,可以将属性配置为在添加或更新时生成其值:

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.LastUpdated)
        .ValueGeneratedOnAddOrUpdate(); // mysql 下可以自动生成,SQLServer下不行
}

 

数据注解

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime LastUpdated { get; set; }
}

 

 

警告

与默认值或计算列不同,我们没有指定值的生成方式;这取决于所使用的数据库提供程序。 数据库提供程序可能会自动为某些属性类型设置值生成,但其他属性类型可能需要你手动设置值的生成方式。

例如,在 SQL Server 上,如果 GUID 属性配置为在添加时生成值,提供程序会自动在客户端执行值生成,并使用算法生成最佳顺序 GUID 值。 但是,在 DateTime 属性上指定 ValueGeneratedOnAdd 将不起作用,

一个常见的请求是让数据库列包含第一次插入列的日期/时间(添加时生成的值)或上次更新列的日期/时间(添加或更新时生成的值)。 由于可通过各种策略执行此操作,因此 EF Core 提供程序通常不会为日期/时间列自动设置值生成 - 你必须自行配置。

同样,配置为在添加或更新时生成值并标记为并发标记的 byte[] 属性将设置为 rowversion 数据类型,以便在数据库中自动生成值。 但是,指定 ValueGeneratedOnAdd 不起作用。

 

3. 外键关系

默认情况下,如果在类型上发现导航属性,将创建关系。 如果当前数据库提供程序无法将属性指向的类型映射为标量类型,则属性被视为导航属性

有许多用于描述关系的术语

  • 依赖实体:这是包含外键属性的实体。 有时是指关系的“子级”。

  • 主体实体:这是包含主键/备选键属性的实体。 有时是指关系的“父级”。

  • 主体键:唯一标识主体实体的属性。 它可能是主键,也可能是备选键。

  • 外键:依赖实体中用于存储相关实体的主体键值的属性。

  • 导航属性:在引用相关实体的主体实体和/或依赖实体上定义的属性。

    • 集合导航属性:包含对许多相关实体的引用的导航属性。

    • 引用导航属性:保留对单个相关实体的引用的导航属性。

    • 反向导航属性:讨论特定导航属性时,此术语是指关系另一端上的导航属性。

  • 自引用关系:依赖实体类型与主体实体类型相同的关系。

以下代码展示了 BlogPost 之间的一对多关系

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

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

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

 

  • Post 是依赖实体

  • Blog 是主体实体

  • Blog.BlogId 是主体键(在此示例中,它是主键)

  • Post.BlogId 是外键

  • Post.Blog 是引用导航属性

  • Blog.Posts 是集合导航属性

  • Post.BlogBlog.Posts 的反向导航属性(反之亦然)

     

默认外键关系

关系的最常见模式是在关系的两端定义导航属性,并在依赖实体类中定义外键属性。

  • 如果在两种类型之间找到了一对导航属性,则它们将被配置为相同关系的反向导航属性。

  • 如果依赖实体包含一个名称与以下模式之一匹配的属性,则该属性将被配置为外键:

    • <navigation property name><principal key property name>
      
      <navigation property name>Id
      
      <principal entity name><principal key property name>
      
      <principal entity name>Id

       

 

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

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

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
    
    // 下面这种情况,BlogEntityId 也会生成外键
    //public int BlogEntityId { get; set; }
    //public Blog? BlogEntity { get; set; }
}

 

本示例将使用突出显示的属性来配置关系。

备注

如果属性是主键或属性的类型与主体键不兼容,则不会将其配置为外键。

无外键属性

虽然建议在依赖实体类中定义外键属性,但这不是必需的。 如果未找到外键属性,则将使用名称 <navigation property name><principal key property name><principal entity name><principal key property name> 引入外键属性(如果依赖类型上不存在导航)。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

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

    public Blog Blog { get; set; } // 自动生成一个外键字段:BlogId
}

 

单个导航属性

仅包含一个导航属性(没有反向导航,也没有外键属性)就足以按约定定义关系。 还可包含一个导航属性和一个外键属性。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    // 会自动生成一个BlogId字段
}

 

配置外键

如果按约定未发现外键属性,通常可这样做:

fluentAPI

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogForeignKey); // mysql会生成两个外键
            // .HasConstraintName("ForeignKey_Post_Blog"); // 配置外键名
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

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

    public int BlogForeignKey { get; set; }
    public Blog Blog { get; set; }
}

 

数据注解

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

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

    public int BlogForeignKey { get; set; }

    [ForeignKey("BlogForeignKey")]
    public Blog Blog { get; set; }
}
 

 

 

没有导航属性

无需提供导航属性。 只需在关系的一侧提供外键。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne<Blog>()
            .WithMany()
            .HasForeignKey(p => p.BlogId);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

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

    public int BlogId { get; set; }
}

 

级联删除

可使用 Fluent API 显式配置给定关系的级联删除行为。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .OnDelete(DeleteBehavior.Cascade);
}

 

一对一

一对一关系在两侧都有引用导航属性。 它们遵循与一对多关系相同的约定,但在外键属性上引入了一个唯一索引,以确保只有一个依赖项与每个主体相关。

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}
 

 

多对多

多对多关系需要两端的集合导航属性。 与其他类型的关系一样,它们也可通过约定发现。

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

    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public long TagId { get; set; }
    public String TagName { get;set;}
    public ICollection<Post> Posts { get; set; }
}

 

在数据库中实现此关系的方式是使用联接表,其中包含 PostTag 的外键。 例如,以下就是 EF 将在关系数据库中为上述模型创建的内容。

CREATE TABLE [Posts] (
    [PostId] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NULL,
    [Content] nvarchar(max) NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([PostId])
);

CREATE TABLE [Tags] (
    [TagId] nvarchar(450) NOT NULL,
    CONSTRAINT [PK_Tags] PRIMARY KEY ([TagId])
);

CREATE TABLE [PostTag] (
    [PostsId] int NOT NULL,
    [TagsId] nvarchar(450) NOT NULL,
    CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),
    CONSTRAINT [FK_PostTag_Posts_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([PostId]) ON DELETE CASCADE,
    CONSTRAINT [FK_PostTag_Tags_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([TagId]) ON DELETE CASCADE
);

 

在内部,EF 创建一个实体类型来表示将称为联接实体类型的联接表。 Dictionary<string, object> 当前用于处理外键属性的任意组合,有关详细信息,请参阅属性包实体类型。 模型中可以存在多个多对多关系,因此必须为联接实体类型指定唯一的名称,在本例中为 PostTag。 允许此操作的功能称为共享类型实体类型。

4. 检查约束

检查约束是一项标准关系功能,让你可以定义一个条件,该条件必须适用于表中的所有行;任何违反约束的插入或修改数据的尝试都将失败。 检查约束类似于非 null 约束(禁止列中的空值)或唯一约束(禁止重复),但允许定义任意 SQL 表达式。

可使用 Fluent API 指定表的检查约束(以 SQL 表达式的形式提供):

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Product>()
        .ToTable(b => b.HasCheckConstraint("CK_Prices", "[Price] > [DiscountedPrice]"));
}

 

可在同一个表上定义多个检查约束,每个约束都有自己的名称。

4. 索引

索引是许多数据存储中的常见概念。 尽管它们在数据存储中的实现可能会有所不同,但它们可用于使基于列(或一组列)的查找更加高效,可对列指定索引,如下所示:

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasIndex(b => b.Url);
}

 

数据注解

[Index(nameof(Url))]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

 

 

根据约定,会在用作外键的每个属性(或属性集)中创建索引。

复合索引

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasIndex(p => new { p.FirstName, p.LastName });
}

 

数据注解

[Index(nameof(FirstName), nameof(LastName))]
public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

 

 

多个列上的索引(也称为复合索引),可以加快对索引列进行筛选的查询速度,还可以加快仅对索引覆盖的第一列进行筛选的查询速度。

唯一索引

尝试为索引的列集插入多个具有相同值的实体将导致引发异常。

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasIndex(b => b.Url)
        .IsUnique();
}

 

数据注解

[Index(nameof(Url), IsUnique = true)]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

 

 

索引名称

根据约定,在关系数据库中创建的索引被命名为 IX_<type name>_<property name>。 对于复合索引,<property name> 将成为以下划线分隔的属性名称列表。

可设置在数据库中创建的索引的名称:

fluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasIndex(b => b.Url)
        .HasDatabaseName("Index_Url");
}

 

数据注解

[Index(nameof(Url), Name = "Index_Url")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

 

 

覆盖索引

通过某些关系数据库,可配置一组列,这些列包含在索引中,但不是其“键”的一部分。 当查询中的所有列都作为键列或非键列包含在索引中时,这可以显著提高查询性能,因为无需访问表本身。 有关 SQL Server 包含的列的详细信息,请参阅文档

在以下示例中,Url 列是索引键的一部分,因此对该列的任何查询筛选都可以使用索引。 但除此之外,仅访问 TitlePublishedOn 列的查询地全表扫描,不走索引查找:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasIndex(p => p.Url)
        .IncludeProperties(  // mysql 中没有所谓的包含列的概念
            p => new { p.Title, p.PublishedOn });
}

 

SQLServer 创建覆盖索引的例子:

CREATE NONCLUSTERED INDEX IX_Address_PostalCode  
ON Person.Address (PostalCode)  
INCLUDE (AddressLine1, AddressLine2, City, StateProvinceID);  

 

mysql提示

mysql中的覆盖索引:如果查询条件使用的是普通索引(或是联合索引的最左原则字段),查询结果是联合索引的字段或是主键,不用回表操作,直接返回结果,减少IO磁盘读写读取正行数据

 

5. 数据种子

与 EF6 不同,在 EF Core 中,种子(用于初始化的)设定数据可以在模型配置中与实体类型相关联。 随后,将数据库升级为新版本模型时,EF Core 迁移可以自动计算需要应用的插入、更新或删除操作。

备注

迁移仅在确定应执行哪些操作以使种子数据达到所需状态时考虑模型更改。 因此,对迁移之外执行的任何数据更改都可能会丢失或导致错误。

例如,这将在 OnModelCreating 中为 Blog 配置种子数据:

// 种子数据,主键就算是自增也需要显式的赋值
// 如果是在数据库层面修改数据,重新迁移不会覆盖现有的数据,如果从代码中修改种子数据,则重新迁移会被覆盖
modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Url = "http://sample.com" });

 

我们推荐使用数据库层面的insert 语句来添加种子数据。

 

视频配套链接:课程简介 (cctalk.com)