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

19 篇文章 1 订阅

一、前言

上一篇文学习了实体类型,知道了实体类型就是那个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);
}

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");
}

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)");
        });
}

3.1. 最大长度

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

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

以下例子中,配置最大长度为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);
}

△3.2. 精度和刻度

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

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

在以下示例中,将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);
}

△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);
}

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;
    }
}

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

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

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();
}

△5. 列排序规则

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

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

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

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

△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");
}

△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);
    });
}

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


三、结尾语

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值