• EFCore学习笔记(3)——实体属性


    一、前言

    上一篇文学习了实体类型,知道了实体类型就是那个DbSet<T>里泛型位置的类,也是面向对象的方法对现实世界事物的抽象类。在EF Core中使用模型访问数据库时,你还可以认为实体类与数据库中的数据表对应(当然这种映射关系需要用一些手段)。

    那这篇文来讲更加细化的实体类,对实体类中的属性进行学习。


    二、实体属性

    在你的模型中,每个实体类都会有一系列属性,EF Core能从数据库中读写它们。若你使用的是关系数据库,实体属性将映射到数据表的列。

    1. 如何包含或排除属性

    按照约定,所有带有getter和setter的公开属性都将被包含在模型中。
    可以通过以下方式排除指定的属性:

    // 1. 数据标注
    public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }
    
        [NotMapped]
        public DateTime LoadedFromDatabase { get; set; }
    }
    // 2. fluent API
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Ignore(b => b.LoadedFromDatabase);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2. 列名

    按照约定,当使用关系数据库时,实体的属性会映射到和属性有着相同名称的表的列上。
    如果你更喜欢用不同的名称来配置列,可以使用以下代码段来做到这一点:

    // 1. 数据标注
    public class Blog
    {
        [Column("blog_id")]
        public int BlogId { get; set; }
    
        public string Url { get; set; }
    }
    // 2. fluent API
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.BlogId)
            .HasColumnName("blog_id");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3. 列数据类型

    在使用关系数据库时,数据库提供程序会根据属性的.NET类型来选择数据类型。它还考虑了其他元数据,例如配置的最大长度、该属性是否是主键的一部分等。

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

    你也可以将你的列配置为一个确切的数据类型。例如,下列代码将Url配置为一个最大长度为200的非unicode字串,并且将Rating配置为一个5位精度、小数点位数为2的decimal值。

    // 1. 数据标注
    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; }
    }
    // 2. fluent API
    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)");
            });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.1. 最大长度

    配置最大长度将提示数据库提供程序,为给定属性选择适当的列数据类型。最大长度仅适用于数组数据类型,例如string和byte[]。

    注意:
    EF在传数据到数据库提供程序之前,不进行任何最大长度的验证。
    这取决于数据库提供程序或数据存储来验证是否合适。
    例如,当目标为SQL Server时,超过最大长度将会导致异常,因为底层列的数据类型不允许存储多余的数据。
    
    • 1
    • 2
    • 3
    • 4

    以下例子中,配置最大长度为500将会在SQL Server中创建一个类型为nvarchar(500)的列。

    // 1. 数据标注
    public class Blog
    {
        public int BlogId { get; set; }
    
        [MaxLength(500)]
        public string Url { get; set; }
    }
    // 2. fluent API
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Url)
            .HasMaxLength(500);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    △3.2. 精度和刻度

    有一些关系数据类型支持精度和刻度特性。它们可以控制存储哪些值,以及该列需要多少存储空间。哪些数据类型支持精度和刻度取决于数据库,但在大多数数据库中,decimal和DateTime类型支持这两者。对于decimal属性,precision(精度)定义了表示列包含的任意值的最大位数,scale(刻度)定义了最大小数点位数。对于DateTime属性,精度定义了表示秒的部分所需的最大数字数,刻度不会被用到。

    注意:
    EF在传数据给数据库提供者之前不做任何精度或刻度的验证。
    这取决于数据库提供程序或数据存储程序来进行适当的验证。
    例如,当针对SQL Server时,datetime类型的列不允许设置精度,而datetime2类型的列可以设置精度为0-7之间。
    
    • 1
    • 2
    • 3
    • 4

    在以下示例中,将Score属性配置为精度14,刻度2将会在SQL Server中创建一个decimal(14,2)类型的列,将LastUpdated属性配置为精度3会生成一个类型为datetime2(3)的列:

    // 1. 数据标注 EF Core 6.0引入
    public class Blog
    {
        public int BlogId { get; set; }
        [Precision(14, 2)]
        public decimal Score { get; set; }
        [Precision(3)]
        public DateTime LastUpdated { get; set; }
    }
    // 2. fluent API EF Core 5.0引入
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Score)
            .HasPrecision(14, 2);
    
        modelBuilder.Entity<Blog>()
            .Property(b => b.LastUpdated)
            .HasPrecision(3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    △3.3. Unicode

    在一些关系数据库中,存在表示Unicode和非Unicode文本数据的不同类型。例如,在SQL Server中,nvarchar(x)用于表示UTF-16中的Unicode数据,而varchar(x)用于表示非Unicode数据。对于不支持此概念的数据库,配置此参数没有任何作用。

    默认情况下,文本属性被配置为Unicode。你可以按以下方式将列配置为非Unicode:

    // 1. 数据标注 EF Core 6.0引入
    public class Book
    {
        public int Id { get; set; }
        public string Title { get; set; }
    
        [Unicode(false)]
        [MaxLength(22)]
        public string Isbn { get; set; }
    }
    // 2. fluent API
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Book>()
            .Property(b => b.Isbn)
            .IsUnicode(false);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4. 必需的和可选的属性

    必需的(required)
    可选的(optional)
    如果一个属性包含null是合法的/有效的,则该属性被认为是可选的。如果null不是分配给该属性的有效值,那么它被认为是必要的属性。当映射到关系数据库schema时,必要的属性创建为不可为null的列,而可选的属性创建为可以为null的列。

    4.1. 约定

    按照约定,一个属性若它的.NET类型可以包含null则被配置为可选的,反之则是必需/必要属性。例如,所有带有.NET值类型的属性(int,decimal,bool等等)是必需属性,而所有带有可为null的.NET值类型的属性(int?,decimal?,bool?等等)配置为可选属性。

    C#8引入了一个名为可空引用类型(NRT,nullable reference types),它允许对引用类型进行标注,表示它们为空时是否为有效值。该特性在新项目模板中默认启用,但在现有项目中保持禁用,除非你显示选择。可为空引用类型通过以下方式影响着EF Core的行为:

    • 若禁用了可为空引用类型,所有带有.NET引用类型的属性都按照约定配置为可选属性(如,string)
    • 若启用了可为空引用类型,属性将基于.NET类型的C#可空性(nullability)来配置:string?会配置为可选,而string会配置为必需。

    下面的示例展示了一个带有必需和可选属性的实体类型,禁用和启用可空引用特性:

    // 1. 禁用NRT
    public class CustomerWithoutNullableReferenceTypes
    {
        public int Id { get; set; }
    
        [Required] // Data annotations needed to configure as required
        public string FirstName { get; set; }
    
        [Required]
        public string LastName { get; set; } // Data annotations needed to configure as required
    
        public string MiddleName { get; set; } // Optional by convention
    }
    // 2. 启用NRT
    public class Customer
    {
        public int Id { get; set; }
        public string FirstName { get; set; } // Required by convention
        public string LastName { get; set; } // Required by convention
        public string? MiddleName { get; set; } // Optional by convention
    
        // Note the following use of constructor binding, which avoids compiled warnings
        // for uninitialized non-nullable properties.
        public Customer(string firstName, string lastName, string? middleName = null)
        {
            FirstName = firstName;
            LastName = lastName;
            MiddleName = middleName;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    一般推荐使用可空引用类型,因为它将C#代码中表达的可空性传递给EF Core的模型和数据库,并避免了使用Fluent API或者数据标注来再次表达相同的意思。

    注意:
    在现有项目中启用了可空引用类型时,请谨慎操作:
    之前被配置为可选的引用类型属性现在将被配置为必需,除非它们被显示标注为可空。
    在管理关系数据库schema时,这可能会导致生成migrations,从而改变数据库列的可空性。
    
    • 1
    • 2
    • 3
    • 4

    4.2. 显式配置

    按照约定,一个可选的属性可以按以下方式被配置为必需的:

    // 1. 数据标注
    public class Blog
    {
        public int BlogId { get; set; }
    
        [Required]
        public string Url { get; set; }
    }
    // 2. fluent API
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Url)
            .IsRequired();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    △5. 列排序规则

    注意:
    该特性是EF Core 5.0时引入的。
    
    • 1
    • 2

    在文本列上可以定义排序规则,确定它们的比较和排序方式。例如,以下代码将一个SQL Server列配置为不区分大小写:

    modelBuilder.Entity<Customer>().Property(c => c.Name)
        .UseCollation("SQL_Latin1_General_CP1_CI_AS");
    
    • 1
    • 2

    如果数据库中所有的列都需要使用某种排序规则,则应在数据库层面定义排序规则。

    △6. 列评论

    你可以在数据库列上设置任意的文本评论/注释,这允许你在数据库中记录你的schema:

    // 1. 数据标注 EF Core 5.0引入
    public class Blog
    {
        public int BlogId { get; set; }
    
        [Comment("The URL of the blog")]
        public string Url { get; set; }
    }
    // 2. fluent API
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Url)
            .HasComment("The URL of the blog");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    △7. 列排序

    该特性是EF Core 6.0引入的。
    默认情况下,当使用Migrations创建表时,EF Core首先排序主键的列,然后是实体类型和所拥有类型的属性,最后是基类的属性。不过,你可以指定不同的列排序:

    // 1. 数据标注
    public class EntityBase
    {
        [Column(Order = 0)]
        public int Id { get; set; }
    }
    
    public class PersonBase : EntityBase
    {
        [Column(Order = 1)]
        public string FirstName { get; set; }
    
        [Column(Order = 2)]
        public string LastName { get; set; }
    }
    
    public class Employee : PersonBase
    {
        public string Department { get; set; }
        public decimal AnnualSalary { get; set; }
    }
    // 2. fluent API
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Employee>(x =>
        {
            x.Property(b => b.Id)
                .HasColumnOrder(0);
    
            x.Property(b => b.FirstName)
                .HasColumnOrder(1);
    
            x.Property(b => b.LastName)
                .HasColumnOrder(2);
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    需要注意的是,在一般情况下,大部分数据库在表创建时仅支持排序列。这意味着不能使用列顺序属性对现有表中的列重新排序。


    三、结尾语

    初学对属性操作的两种方法 数据标注和fluent API稍微熟悉即可,还有了解实体类属性在关系数据库中对应数据表的列(字段)即可。

  • 相关阅读:
    MCE | 第二代 HIV-INSTI 的作用方式
    在微信小程序中安装和使用vant框架
    FPGA project : beep
    Vue2源码学习笔记 - 14.响应式原理—核心本质
    SpringBoot+Vue实现前后端分离的中学成绩管理系统
    pyspark常用功能记录
    Vue_组件详解
    .NET NativeAOT 指南
    SpringBoot整合Redis实现常用功能
    Redis 的内存策略
  • 原文地址:https://blog.csdn.net/BadAyase/article/details/125480717