通过多个列在两个实体之间创建多对多关系
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,为我指明了正确的方向。
【讨论】:
以上是关于通过多个列在两个实体之间创建多对多关系的主要内容,如果未能解决你的问题,请参考以下文章