EFCore中实体与数据库表的替代映射方式
立即解锁
发布时间: 2025-08-23 01:41:56 阅读量: 1 订阅数: 6 

### EF Core 中实体与数据库表的替代映射方式
在数据库开发中,将实体类映射到数据库表是一项常见的任务。通常我们会采用一对一的映射方式,但在某些情况下,使用替代的映射方式能带来更好的性能和更灵活的设计。本文将介绍 EF Core 中几种替代的映射方式,包括 Fluent API 关系中的较少使用的选项、拥有类型、表层次结构(TPH)等。
#### 1. Fluent API 关系中的较少使用的选项
在 Fluent API 中,有两个较少使用但很有用的命令可用于设置关系:
- **HasConstraintName**:该方法允许你设置外键约束的名称。当你想要捕获外键错误异常并使用约束名称形成更友好的错误消息时,这个方法非常有用。具体实现可参考:[http://mng.bz/4ZwV](http://mng.bz/4ZwV)。
- **MetaData**:MetaData 属性提供对关系数据的访问,其中一些是可读写的。虽然 MetaData 属性暴露的大部分内容可以通过特定命令(如 IsRequired)访问,但如果你需要一些特殊的内容,可以查看 MetaData 属性支持的各种方法和属性。
#### 2. 替代的实体与数据库表映射方式
以下是五种替代的映射方式,每种方式在特定情况下都有其优势:
- **拥有类型(Owned types)**:允许将一个类合并到实体类的表中,对于使用普通类来分组数据很有用。
- **表层次结构(Table per hierarchy,TPH)**:允许将一组继承类保存到一个表中,例如从 Animal 类继承的 Dog、Cat 和 Rabbit 类。
- **表类型(Table per type,TPT)**:将每个类映射到不同的表。这种方法与 TPH 类似,但每个类映射到单独的表。
- **表拆分(Table splitting)**:允许将多个实体类映射到同一个表,当表中的某些列比所有列更常被读取时很有用。
- **属性包(Property bags)**:允许通过 Dictionary 创建实体类,让你可以在启动时创建映射。属性包还使用另外两个特性:将相同类型映射到多个表和在实体类中使用索引器。
#### 3. 拥有类型(Owned types)
EF Core 中的拥有类型允许你定义一个包含常见数据分组的类,如地址或审计数据,你可以在数据库的多个地方使用这些数据。拥有类型的类没有自己的主键,它依赖于“拥有”它的实体类来确定其身份。在领域驱动设计(DDD)中,拥有类型被称为值对象。
EF Core 的拥有类型与 EF6.x 的复杂类型类似,但最大的区别是你必须明确配置拥有类型,而 EF6.x 会将任何没有主键的类视为复杂类型,这可能会导致错误。此外,EF Core 的拥有类型还有一个额外的特性:拥有类型的数据可以配置为保存在一个单独的隐藏表中。
拥有类型有两种使用方式:
- **拥有类型数据与实体类存储在同一表中**
以 OrderInfo 实体类为例,它需要两个地址:BillingAddress 和 DeliveryAddress。这些地址由 Address 类提供,你可以通过在 Address 类上添加 [Owned] 属性将其标记为拥有类型。示例代码如下:
```csharp
public class OrderInfo
{
public int OrderInfoId { get; set; }
public string OrderNumber { get; set; }
public Address BillingAddress { get; set; }
public Address DeliveryAddress { get; set; }
}
[Owned]
public class Address
{
public string NumberAndStreet { get; set; }
public string City { get; set; }
public string ZipPostCode { get; set; }
[Required]
[MaxLength(2)]
public string CountryCodeIso2 { get; set; }
}
```
由于你在 Address 类上添加了 [Owned] 属性,并且在同一表中使用拥有类型,因此不需要使用 Fluent API 来配置拥有类型。但如果你不想使用 [Owned] 属性,可以使用以下 Fluent API 代码:
```csharp
public class SplitOwnDbContext: DbContext
{
public DbSet<OrderInfo> Orders { get; set; }
//… other code removed for clarity
protected override void OnModelCreating
(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OrderInfo>()
.OwnsOne(p => p.BillingAddress);
modelBuilder.Entity<OrderInfo>()
.OwnsOne(p => p.DeliveryAddress);
}
}
```
最终生成的表包含 OrderInfo 实体类的两个标量属性,后面跟着两组 Address 类的属性,一组以 BillingAddress_ 为前缀,另一组以 DeliveryAddress_ 为前缀。由于拥有类型属性可以为 null,所有属性在数据库中都存储为可空列。
EF Core 5 增加了一个特性,允许你指定拥有类型是必需的,即必须始终存在。你可以通过在 OrderInfo 的 DeliveryAddress 导航属性上添加 Fluent API 的 IsRequired 方法来实现:
```csharp
protected override void OnModelCreating
(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OrderInfo>()
.OwnsOne(p => p.BillingAddress);
modelBuilder.Entity<OrderInfo>()
.OwnsOne(p => p.DeliveryAddress);
modelBuilder.Entity<OrderInfo>()
.Navigation(p => p.DeliveryAddress)
.IsRequired();
}
```
使用拥有类型可以帮助你组织数据库,将常见的数据组转换为拥有类型,使代码更易于处理常见的数据组。以下是关于实体类中拥有类型的一些要点:
- 拥有类型的导航属性(如 BillingAddress)在读取实体时会自动创建并填充数据,无需使用 Include 方法或其他关系加载方式。
- Julie Lerman 指出,拥有类型可以替代一对一或零对一的关系,特别是当拥有类型较小时。拥有类型具有更好的性能,并且会自动加载。
- 拥有类型可以嵌套。例如,你可以创建一个 CustomerContact 拥有类型,它包含一个 Address 拥有类型。如果在另一个实体类(如 SuperOrder)中使用 CustomerContact 拥有类型,所有 CustomerContact 属性和 Address 属性都会添加到 SuperOrder 的表中。
以下是拥有类型存储在同一表中的流程:
```mermaid
graph LR
A[定义 OrderInfo 实体类] --> B[定义 Address 拥有类型]
B --> C{是否使用 [Owned] 属性}
C -- 是 --> D[自动合并到 OrderInfo 表]
C -- 否 --> E[使用 Fluent API 配置]
E --> D
```
- **拥有类型数据存储在与实体类不同的表中**
另一种方式是将拥有类型的数据存储在与实体类不同的表中。以 User 实体类为例,它有一个类型为 Address 的 HomeAddress 属性。你可以在配置代码中使用 ToTable 方法来实现:
```csharp
public class SplitOwnDbContext: DbContext
{
public DbSet<OrderInfo> Orders { get; set; }
//… other code removed for clarity
protected override void OnModelCreating
(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.OwnsOne(p => p.HomeAddress)
.ToTable("Addresses");
}
}
```
EF Core 会建立一个一对一的关系,其中主键也是外键,并且 OnDelete 状态设置为 Cascade,以便在删除主实体(User)时删除拥有类型的条目。数据库将有两个表:Users 和 Addresses。
```sql
CREATE TABLE [Users] (
[UserId] int NOT NULL IDENTITY,
```
0
0
复制全文
相关推荐









