上一篇文学习了实体类型,知道了实体类型就是那个DbSet<T>里泛型位置的类,也是面向对象的方法对现实世界事物的抽象类。在EF Core中使用模型访问数据库时,你还可以认为实体类与数据库中的数据表对应(当然这种映射关系需要用一些手段)。
那这篇文来讲更加细化的实体类,对实体类中的属性进行学习。
在你的模型中,每个实体类都会有一系列属性,EF Core能从数据库中读写它们。若你使用的是关系数据库,实体属性将映射到数据表的列。
按照约定,所有带有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. 数据标注
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");
}
在使用关系数据库时,数据库提供程序会根据属性的.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)");
});
}
配置最大长度将提示数据库提供程序,为给定属性选择适当的列数据类型。最大长度仅适用于数组数据类型,例如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);
}
有一些关系数据类型支持精度和刻度特性。它们可以控制存储哪些值,以及该列需要多少存储空间。哪些数据类型支持精度和刻度取决于数据库,但在大多数数据库中,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);
}
在一些关系数据库中,存在表示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);
}
必需的(required)
可选的(optional)
如果一个属性包含null是合法的/有效的,则该属性被认为是可选的。如果null不是分配给该属性的有效值,那么它被认为是必要的属性。当映射到关系数据库schema时,必要的属性创建为不可为null的列,而可选的属性创建为可以为null的列。
按照约定,一个属性若它的.NET类型可以包含null则被配置为可选的,反之则是必需/必要属性。例如,所有带有.NET值类型的属性(int,decimal,bool等等)是必需属性,而所有带有可为null的.NET值类型的属性(int?,decimal?,bool?等等)配置为可选属性。
C#8引入了一个名为可空引用类型(NRT,nullable reference types),它允许对引用类型进行标注,表示它们为空时是否为有效值。该特性在新项目模板中默认启用,但在现有项目中保持禁用,除非你显示选择。可为空引用类型通过以下方式影响着EF Core的行为:
下面的示例展示了一个带有必需和可选属性的实体类型,禁用和启用可空引用特性:
// 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,从而改变数据库列的可空性。
按照约定,一个可选的属性可以按以下方式被配置为必需的:
// 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();
}
注意:
该特性是EF Core 5.0时引入的。
在文本列上可以定义排序规则,确定它们的比较和排序方式。例如,以下代码将一个SQL Server列配置为不区分大小写:
modelBuilder.Entity<Customer>().Property(c => c.Name)
.UseCollation("SQL_Latin1_General_CP1_CI_AS");
如果数据库中所有的列都需要使用某种排序规则,则应在数据库层面定义排序规则。
你可以在数据库列上设置任意的文本评论/注释,这允许你在数据库中记录你的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");
}
该特性是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稍微熟悉即可,还有了解实体类属性在关系数据库中对应数据表的列(字段)即可。