Entity framework一对多关系配置

Entity framework one-to-many relationship configuration

一个客户有多个地址,一个仓库有一个地址。我的 C# 代码如下所示:

public class Address
{
    public int Id { get; set; }
    public string Text { get; set; }
    public Client Client { get; set; }    
    public Warehouse Warehouse { get; set; }
}

public class Client
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? AddressId { get; set; }
    public IList<Address> Addresses { get; set; }
}
public class Warehouse
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? AddressId { get; set; }
    public Address Address { get; set; }
}

表格如下。我将添加一些示例数据来说明我的观点:

地址

+─────+────────────────────+───────────+
| Id  | Text               | ClientId  |
+─────+────────────────────+───────────+
| 1   | Address of WH 1    |           |
| 2   | Address of WH 2    |           |
| 3   | Address 1 of Cl 2  | 1         |
| 4   | Address 2 of Cl 1  | 2         |
| 5   | Address 1 of Cl 1  | 2         |
+─────+────────────────────+───────────+

客户

+─────+───────────+────────────+
| Id  | Name      | AddressId  |
+─────+───────────+────────────+
| 1   | Client 2  | (null)     |
| 2   | Client 1  | (null)     |
| 3   | Client 3  | (null)     |
+─────+───────────+────────────+

仓库

+─────+──────────────+────────────+
| Id  | Name         | AddressId  |
+─────+──────────────+────────────+
| 1   | Warehouse 1  | 1          |
| 2   | Warehouse 2  | 2          |
+─────+──────────────+────────────+

我的目标是在地址中没有 ClientId 列。相反,对于每个 AddressId,在 Clients 中添加一个额外的条目。地址也会随着客户端的删除而级联删除。

+─────+───────────+────────────+
| Id  | Name      | AddressId  |
+─────+───────────+────────────+
| 1   | Client 2  | 4          |
| 1   | Client 2  | 5          |
| 2   | Client 1  | 1          |
| 3   | Client 3  | (null)     |
+─────+───────────+────────────+

您的原始架构有些损坏,您提议的更改更糟。每个 table 都应该有一个独特的 PK。如果您开始将 table 中的 ID 加倍,则意味着您正在求助于客户端 ID 和地址 ID 之间的复合 PK,但对于没有地址的客户端而言,这将不起作用。 (#null 地址 ID)

您要查找的是地址和客户端之间的Many-to-Many关系:

从table的角度来看:

地址

+─────+────────────────────+
| Id  | Text               |
+─────+────────────────────+
| 1   | Address of WH 1    |
| 2   | Address of WH 2    |
| 3   | Address 1 of Cl 2  |
| 4   | Address 2 of Cl 1  |
| 5   | Address 1 of Cl 1  |
+─────+────────────────────+

客户

+─────+───────────+
| Id  | Name      |
+─────+───────────+
| 1   | Client 2  |
| 2   | Client 1  |
| 3   | Client 3  |
+─────+───────────+

客户地址

+──────────+───────────+
| ClientId | AddressId |
+──────────+───────────+
| 1        | 4         |
| 1        | 5         |
| 1        | 1         |
+──────────+───────────+

从实体的角度来看,Client 可以有一个 Addresses 的集合,而 Addresses 可以有选择地有一个 Clients 的集合。

public class Address
{
    public int Id { get; set; }
    public string Text { get; set; }
}

public class Client
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Address> Addresses { get; set; } = new List<Address>();
}

地址不会引用仓库,而是仓库会通过 AddressId 引用地址。

public class Warehouse
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Address Address { get; set; }
}

连接客户端和地址意味着建立many-to-many关系。这可以在 DbContext 的 OnModelCreating 事件处理程序中完成:

// 英孚 6

modelBuilder.Entity<Client>()
    .HasMany(x => x.Addresses)
    .WithMany()
    .Map(x => { x.MapLeftKey("ClientId"); x.MapRightKey("AddressId"); x.ToTable("ClientAddresses"); });

modelBuilder.Entity<Warehouse>()
    .HasOptional(x => x.Address)
    .WithMany()
    .Map(x => x.MapKey("AddressId"));

在 EF Core 中,映射看起来有点不同,并且根据您可能使用的版本,可能需要也可能不需要将 ClientAddresses 定义为实体 class。 (需要 EF Core 3.1 及更早版本,不需要 EF Core 5/6)

而仓库 table 将有一个地址 ID 指向它的地址,并且可能有一个客户端 ID 指向客户端。 (取决于这两者之间的关系)在使用导航属性时,我强烈建议 而不是 将 FK 声明为实体中的属性。这会创建两个可能不匹配的真实来源,并在更新时导致不一致的行为,具体取决于导航 属性 是否已加载。相反,我建议对 FK 字段使用阴影属性并始终使用导航属性。 (对于批量更新等速度更重要的地方,使用 FK 并省去导航 属性)阴影属性在 EF Core 中得到更好的支持,因此它们的映射与我上面的不同。 (EF6)