通过多个列在两个实体之间创建多对多关系

Posted

技术标签:

【中文标题】通过多个列在两个实体之间创建多对多关系【英文标题】:Create many-to-many relationship between two entities via multiple columns 【发布时间】:2021-12-21 04:28:50 【问题描述】:

我有以下四张表:

客户

Id
FirstName
...

消费点

Id
Address
...

发票

Id
InvoiceNumber
CustomerId
ConsumptionPointId
...

合约账户

Id
ContractAccountNumber
CustomerId
ConsumptionPointId
IsCurrentDelivery
...

我想获取发票的 ContractAccountNumber。

是否可以在这两者之间创建某种关系以直接访问 Invoice 的 ContractAccount(s)?

目前我正在做类似的事情:

invoice.Customer.ContractAccounts
    .Where(ca => ca.ConsumptionPoint == invoice.ConsumptionPoint &&
    ca.IsCurrentDelivery == true).FirstOrDefault();

update 在 SQL 中我只需使用多个条件进行连接:

SELECT i.Id AS InvoiceId, ca.Id AS ContractAccountId, 
ca.ContractAccountNumber
FROM Invoices i
LEFT JOIN ContractAccounts ca
ON i.ConsumptionPointId = ca.ConsumptionPointId
AND i.CustomerId = ca.CustomerId
WHERE ca.IsCurrentDelivery = 1

更新 2:

基本上我只是想摆脱 Where-Clause 中的 ca.ConsumptionPoint == invoice.ConsumptionPoint 并想在关系中定义它。

实际上这是一个多对多的关系:一个 Invoice 可以链接到多个 ContractAccounts(通过不同的 Customer/ConsumptionPoint 组合),一个 ContractAccount 可以链接到多个 Invoices。有没有办法告诉 .net 基于两个自定义列的组合建立多对多关系?

【问题讨论】:

您可以在数据库中创建一个 VIEW 以仅公开具有所需条件的数据,并在您的实际实体和您的 VIEW 之间创建 1-1 关系 @Cleptus 带有 VIEW 的想法听起来很有趣。我认为这应该有效。我会试试这个。是的,应该是 ca.IsCurrentDelivery 感谢您的更正。 【参考方案1】:

我认为您不需要两个密钥。使用 EF 核心,您可以创建这样的关系:

发票:

public class Invoice

  public int Id get; set;
  public int InvoiceNumber get; set;
  public int CustomerId get; set;
  public Customer Customer get; set;
  public int ConsumptionPointId get; set;

  public int ContractAccountId get; set;
  public ContractAccount ContractAccount get; set;

public class ContractAccount

  public int Id get; set;
  public int ContractAccountNumber get; set;
  public bool IsCurrentDelivery get; set;

然后配置:

modelBuilder.Entity<Invoice>()
  .HasOne(b => b.ContractAccount)
  .WithOne()
  .HasForeignKey<Invoice>(b => b.ContractAccountId);

//probably already exists in your code
modelBuilder.Entity<Invoice>()
  .HasOne(b => b.Customer )
  .WithMany()
  .HasForeignKey(b => b.CustomerId);

然后您可以直接从发票中访问它:invoice.ContractAccount

【讨论】:

我相信你可能已经跳过了关系不是1-1本身而是1-1(filtered data)。作为“CustomerId/ConsumptionPointId (with IsCurrentDelivery==true) 的组合是唯一的”问题中的相关部分 可能是,描述写得很复杂。另外我猜这个唯一索引有问题 - 它限制每个 CustomerId/ConsumptionPointId 有两条记录,一条 IsCurrentDelivery = true 和 other = false 这个解决方案会在Invoice 实体中产生一个ContractAccountId 外键。但这不是想要的。可以将一个 ContractAccount 设置为 IsCurrentDelivery=false,并且可以为相同的 Customer/ConsumptionPoint 组合创建另一个 ContractAccount。在这种情况下,不应通过更改外键来手动更新发票。它应该“即时”更新(通过为 Customer/ConsumptionPoint 等价物请求具有当前交付的 ContractAccount)。在有问题的纯 SQL 中查看我更新的解决方案。【参考方案2】:

经过更深入的调查,我找到了解决方案。

基本上它只是一个多对多的关系。所以我们可以为 Invoice 和 ContractAccount 模型添加适当的属性:

public class Invoice

    [Key]
    public long Id  get; set; 
    public Customer Customer  get; set; 
    public ConsumptionPoint ConsumptionPoint  get; set; 
    public virtual ICollection<ContractAccount> ContractAccounts  get; set; 


public class ContractAccount

    [Key]
    public long Id  get; set; 
    public Customer Customer  get; set; 
    public ConsumptionPoint ConsumptionPoint  get; set; 
    public ICollection<Invoice> Invoices  get; set; 

现在,我们只需要手动配置关系:

protected override void OnModelCreating(ModelBuilder modelBuilder)

    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Invoice>()
        .HasMany(i => i.ContractAccounts)
        .WithOne()
        .HasForeignKey(ca => new  ca.CustomerId, ca.ConsumptionPointId )
        .HasPrincipalKey(i => new  i.CustomerId, i.ConsumptionPointId );
    modelBuilder.Entity<ContractAccount>()
        .HasMany(ca => ca.Invoices)
        .WithOne()
        .HasForeignKey(i => new  i.CustomerId, i.ConsumptionPointId )
        .HasPrincipalKey(ca => new  ca.CustomerId, ca.ConsumptionPointId );

就是这样。现在我可以这样做:

invoice.ContractAccounts
    .Where(ca => ca.IsCurrentDelivery == true).FirstOrDefault();

这比以前好多了。

感谢您的 cmets,为我指明了正确的方向。

【讨论】:

以上是关于通过多个列在两个实体之间创建多对多关系的主要内容,如果未能解决你的问题,请参考以下文章

mybatis11--多对多关联查询

在关系数据库中对相同实体之间的多个多对多关系进行建模

实体框架多对多关系错误

Hibernate 多对多

为两个实体设置多对多关系装饰器与创建第三个实体?

Hibernate的多对多关系