Entity Framework (EF) Core 是轻量化、可扩展、开源和跨平台版的常用 Entity Framework 数据访问技术。用于程序中的class类和数据库中的表互相之间建立映射关系。在学习过程中,EFCore中的属性配置显的尤为重要它是学习好asp.net core的基础是配置数据库表结构的重要基石。本篇内容为学习与整理微软文档(MicrosoftDoc)笔者认为重要部分具体内容参见: Microsoft Build
EF Core 可用作对象关系映射程序 (O/RM),这可以实现以下两点:
EF Core 支持多个数据库引擎,可通过名为数据库提供程序的插件库访问许多不同的数据库。
EF Core 提供程序通常跨次要版本工作,但不跨主要版本工作。 例如,针对 EF Core 2.1 发布的提供程序应用于 EF Core 2.2,但不适用于 EF Core 3.0。
使用 Entity Framework Core (EF Core) 时的典型工作单元包括:
根据上下文跟踪实体实例。 实体将在以下情况下被跟踪
可以使用 Startup.cs 的 ConfigureServices 方法中的 AddDbContext 将 EF Core 添加到此配置。 例如:
C#
。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(
options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection"));
}
此示例将名为 ApplicationDbContext 的 DbContext 子类注册为 ASP.NET Core 应用程序服务提供程序(也称为依赖关系注入容器)中的作用域服务。 上下文配置为使用 SQL Server 数据库提供程序,并将从 ASP.NET Core 配置读取连接字符串。 在 ConfigureServices 中的何处调用 AddDbContext 通常不重要。
ApplicationDbContext 类必须公开具有 DbContextOptions << ApplicationDbContext> >参数的公共构造函数。 这是将 AddDbContext 的上下文配置传递到 DbContext 的方式。 例如:
C#
。
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
然后,ApplicationDbContext 可以通过构造函数注入在 ASP.NET Core 控制器或其他服务中使用。 例如:
c#
。
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
最终结果是为每个请求创建一个 ApplicationDbContext 实例,并传递给控制器,以在请求结束后释放前执行工作单元。
可以按照常规的 .NET 方式构造 DbContext 实例,例如,使用 C# 中的 new。 可以通过重写 OnConfiguring 方法或通过将选项传递给构造函数来执行配置。 例如:
C#
。public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test");
}
}
通过此模式,还可以轻松地通过 DbContext 构造函数传递配置(如连接字符串)。 例如:
C#
。public class ApplicationDbContext : DbContext
{
private readonly string _connectionString;
public ApplicationDbContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
或者,可以使用 DbContextOptionsBuilder 创建 DbContextOptions 对象,然后将该对象传递到 DbContext 构造函数。 这使得为依赖关系注入配置的 DbContext 也能显式构造。 例如,使用上述为 ASP.NET Core 的 Web 应用定义的 ApplicationDbContext 时:
ApplicationDbContext C#
。
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Entity Framework Core 使用一组约定来根据实体类的形状生成模型。 可指定其他配置以补充和/或替代约定的内容。
模型配置主要分为两种方法:
可在派生上下文中替代 OnModelCreating 方法,并使用 ModelBuilder API 来配置模型。 此配置方法最为有效,并可在不修改实体类的情况下指定配置。 Fluent API 配置具有最高优先级,并将替代约定和数据注释。
C#
。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; }
如果你有多个表需要配置。为了减小 OnModelCreating 方法的大小,可以将实体类型的所有配置提取到实现 IEntityTypeConfiguration 的单独类中。
C#
。public class BlogEntityTypeConfiguration : IEntityTypeConfiguration<Blog>
{
public void Configure(EntityTypeBuilder<Blog> builder)
{
builder
.Property(b => b.Url)
.IsRequired();
}
}
然后,只需从 OnModelCreating 调用 Configure 方法。
C#
。protected override void OnModelCreating(ModelBuilder modelBuilder)
{
new BlogEntityTypeConfiguration().Configure(modelBuilder.Entity<Blog>());
}
也可以在给定程序集中应用实现 IEntityTypeConfiguration 的类型中指定的所有配置。
C#
。protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
modelBuilder.ApplyConfigurationsFromAssembly(typeof(BlogEntityTypeConfiguration).Assembly);
}
应用配置的顺序是不确定的,因此仅当顺序不重要时才应使用此方法。
也可将特性(称为数据注释)应用于类和属性。 数据注释会替代约定,但会被 Fluent API 配置替代。
C#
。using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace EFModeling.EntityProperties.DataAnnotations.Annotations;
internal class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
}
[Table("Blogs")]
public class Blog
{
public int BlogId { get; set; }
[Required]
public string Url { get; set; }
}
在上下文中包含一种类型的 DbSet 意味着它包含在 EF Core 的模型中;我们通常将此类类型称为实体。 EF Core 可以从/向数据库中读取和写入实体实例,如果使用的是关系数据库,EF Core 可以通过迁移为实体创建表。
按照约定,上下文的 DbSet 属性中公开的类型作为实体包含在模型中。 还包括在 OnModelCreating 方法中指定的实体类型,以及通过递归探索其他发现的实体类型的导航属性找到的任何类型。
下面的代码示例中包含了所有类型:
C#
。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; }
}
public class AuditEntry
{
public int AuditEntryId { get; set; }
public string Username { get; set; }
public string Action { get; set; }
}
模型中的每个实体类型都有一组属性,EF Core 将从数据库中读取和写入这些属性。 如果使用的是关系数据库,实体属性将映射到表列。
按照约定,使用关系数据库时,实体属性将映射到与属性同名的表列。如果希望配置具有不同名称的列,可以按以下代码片段进行操作:
Fluent API C#
。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Id)
.HasColumnName("id");//数据库表字段为英文小写
}
使用关系数据库时,数据库提供程序会根据属性的 .NET 类型选择数据类型。 它还会考虑其他元数据,例如配置的最大长度、属性是否是主键的一部分等等。
例如,SQL Server 将 DateTime 属性映射到 datetime2(7) 列,将 string 属性映射到 nvarchar(max) 列(或对于用作键的属性,映射到 nvarchar(450))。
还可以配置列以指定列的确切数据类型。 例如,以下代码将 Url 配置为非 unicode 字符串,其最大长度为 200,并将 Rating 配置为十进制,其精度为 5,小数位数为 2:
Fluent API C#
。
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[]。
在下面的示例中,将最大长度配置为 500 将导致在 SQL Server 上创建 nvarchar(500) 类型的列:
下面展示一些 内联代码片
。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasMaxLength(500);
}
某些关系数据类型支持精度和小数位数 Facet,它们用于控制可以存储哪些值,以及列需要多少存储。 哪些数据类型支持精度和小数位数取决于数据库,但在大多数数据库中,decimal 和 DateTime 类型支持这些 Facet。 对于 decimal 属性,精度用于定义表示列将包含的任何值所需的最大位数,小数位数用于定义所需的最大小数位数。 对于 DateTime 属性,精度用于定义表示秒的小数部分所需的最大位数,不使用小数位数。
在以下示例中,将 Score 属性配置为精度为 14 和小数位数为 2 将导致在 SQL Server 上创建 decimal(14,2) 类型的列,将 LastUpdated 属性配置为精度为 3 将导致创建 datetime2(3) 类型的列:
C#
。
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);
}
EF Core 5.0 中引入了用于配置精度和小数位数的 Fluent API。如果不先定义精度,则永远不会定义小数位数,因此用于定义小数位数的 Fluent API 为 HasPrecision(precision, scale)。
在某些关系数据库中,存在不同的类型来表示 Unicode 和非 Unicode 文本数据。 例如,在 SQL Server 中,nvarchar(x)用于表示 UTF-16 中的 Unicode 数据,而varchar(x)用于表示非 Unicode 数据 (,但请参阅有关 UTF-8 支持) SQL Server说明。 对于不支持此概念的数据库,配置此概念将不起作用。
默认情况下,文本属性配置为 Unicode。 可以将列配置为非 Unicode,如下所示:
C#
。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.Property(b => b.Isbn)
.IsUnicode(false);
}
禁用了可为 null 的引用类型C#(默认设置)
。
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; } // 可选的按照惯例
}
启用了可为 null 的引用类型 C#可为 null 引用类型 (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
//注意以下构造函数绑定的使用,它避免了对未初始化的非空属性的编译警告。
public Customer(string firstName, string lastName, string? middleName = null)
{
FirstName = firstName;
LastName = lastName;
MiddleName = middleName;
}
}
按约定为可选属性的属性可以配置为必需属性,如下所示:
C#
。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
在关系数据库中,可以为列配置默认值;如果插入的行没有该列的值,则将使用默认值。
可以在属性上配置默认值: C"
。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.Property(b => b.Rating)
.HasDefaultValue(3);
}
还可以指定用于计算默认值的 SQL 片段: C#
。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Created)
.HasDefaultValueSql("getdate()");
}
在大多数关系数据库中,可以将列配置为在数据库中计算其值,并且通常使用引用其他列的表达式:
C#
。
modelBuilder.Entity<Person>()
.Property(p => p.DisplayName)
.HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
以上命令将创建一个虚拟计算列,每次从数据库中提取时都会计算其值。 你也可以将计算列指定为存储(有时称为持久化)计算列,这意味着系统会在每次更新行时计算该列,并将其与常规列一起存储在磁盘上:
C#
。modelBuilder.Entity<Person>()
.Property(p => p.NameLength)
.HasComputedColumnSql("LEN([LastName]) + LEN([FirstName])", stored: true);
C#
。protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasKey(c => c.LicensePlate);
}
还可将多个属性配置为实体的键 - 这称为组合键。 组合键只能使用 Fluent API 进行配置;约定永远不会设置组合键,并且不能使用数据注释来配置组合键。
下面展示一些 内联代码片
。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasKey(c => new { c.State, c.LicensePlate });
}
根据约定,在关系数据库上,主键使用名称 PK_ 进行创建。 可按如下方式配置主键约束的名称:
主键名称 "HasName"C#
。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasKey(b => b.BlogId)
.HasName("PrimaryKey_BlogId");
}
虽然 EF Core 支持使用任何基元类型的属性作为主键(包括 string、Guid、byte[]
等),但并非所有数据库都支持所有类型作为键。 在某些情况下,键值可以自动转换为支持的类型,否则应手动指定转换。
向上下文添加新实体时,键属性必须始终具有非默认值,但某些类型将由数据库生成。 在这种情况下,当添加实体以用于跟踪时,EF将尝试生成一个临时值。 调用 SaveChanges 后,临时值将替换为数据库生成的值。
如果键属性的值由数据库生成,并且在添加实体时指定了非默认值,则 EF 将假定该实体已存在于数据库中,并尝试更新它,而不是插入新的实体。
若要避免这种情况,请禁用值生成。
EF Core 会自动为主键设置值生成 - 但我们可能希望对非键属性执行相同的操作。 你可以将任何属性配置为针对插入的实体生成其值,如下所示:
C#
。protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Inserted)
.ValueGeneratedOnAdd();
}
同样,可以将属性配置为在添加或更新时生成其值:
C#
。protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.LastUpdated)
.ValueGeneratedOnAddOrUpdate();
}
一个常见的请求是让数据库列包含第一次插入列的日期/时间(添加时生成的值)或上次更新列的日期/时间(添加或更新时生成的值)。 由于可通过各种策略执行此操作,因此 EF Core 提供程序通常不会为日期/时间列自动设置值生成 - 你必须自行配置。
若要将日期/时间列配置为包含行的创建时间戳,通常需要使用适当的 SQL 函数来配置默认值。 例如,在 SQL Server 上,你可以使用以下命令:
创建时间戳 C#
。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Created)
.HasDefaultValueSql("getdate()");
}
请确保选择适当的函数,因为可能存在多个函数(例如 GETDATE() 与 GETUTCDATE())。
尽管存储计算列看起来非常适合管理上次更新时间戳,但数据库通常不允许在计算列中指定诸如 GETDATE() 之类的函数。 作为替代方法,你可以设置一个数据库触发器来达到同样的效果:
更新时间戳 C#
。
CREATE TRIGGER [dbo].[Blogs_UPDATE] ON [dbo].[Blogs]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;
DECLARE @Id INT
SELECT @Id = INSERTED.BlogId
FROM INSERTED
UPDATE dbo.Blogs
SET LastUpdated = GETDATE()
WHERE BlogId = @Id
END
尽管为属性配置了值生成,但在许多情况下,你仍然可以为其显式指定一个值。 此操作能否真正起作用取决于已配置的特定值生成机制;虽然你可以指定显式值而不是使用列的默认值,但不能对计算列执行相同的操作。
若要使用显式值替代值生成,只需将属性设置为该属性类型的 CLR 默认值(string 为 null,int 为 0,Guid 为 Guid.Empty,等等)以外的任意值。
默认情况下,尝试将显式值插入 SQL Server IDENTITY 会失败,因为SQL Server 不允许将显式值插入 IDENTITY 列中。 为此,必须在调用 SaveChanges() 之前手动启用 IDENTITY_INSERT,如下所示:
启用 IDENTITY_INSERT C#
。
using (var context = new ExplicitIdentityValuesContext())
{
context.Blogs.Add(new Blog { BlogId = 100, Url = "http://blog1.somesite.com" });
context.Blogs.Add(new Blog { BlogId = 101, Url = "http://blog2.somesite.com" });
context.Database.OpenConnection();
try
{
//启用 IDENTITY_INSERT 开
context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT dbo.Blogs ON");
context.SaveChanges();
//启用 IDENTITY_INSERT 关
context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT dbo.Blogs OFF");
}
finally
{
context.Database.CloseConnection();
}
}