.net 如何在不使用外键约束的情况下创建一对一Map?

yvgpqqbh  于 2023-03-04  发布在  .NET
关注(0)|答案(2)|浏览(93)
    • 问题**-我需要在两个具有属性"SourceTransactionId"的实体之间创建某种类型的Map,其中任何一个实体都可以先添加到数据库中,然后再添加另一个,但仍然可以进行如下查询。
    • 我想要的**:向发件人或收件人显示转账(取决于谁请求查看其转账)及其关联的StripePayment数据:
var transfers = _dbContext.StripeTransfers.Select(p => new {
   TransferAmount = p.StripePayment.Amount,
   TransferDate = p.DateCreated,
   Sender = p.StripePayment.Sender.UserName,
   Receiver = p.StripePayment.Receiver.UserName
}).Where(p => p.StripePayment.Sender.Id == userId || p.StripePayment.Receiver.Id == userId)
.ToListAsync();
    • 要求**-我不知道将首先创建哪个实体,因为StripeTransfer是从一个webhook创建的,而该webhook可能在我创建StripePayment实体之前收到,因此任何一行都应该能够在另一行之前添加。

下面是我的代码:

public class StripePayment
{
    // primary key
    public int Id { get; set; }

    public string SourceTransactionId { get; set; }
    public StripeTransfer StripeTransfer { get; set; }

    public int Amount { get; set; }
    public int SenderId { get; set; }
    public User Sender { get; set; }
    public int ReceiverId { get; set; }
    public User Receiver { get; set; }
}

public class StripeTransfer
{
    // primary key
    public int Id { get; set; }

    public string SourceTransactionId { get; set; }
    public StripePayment StripePayment { get; set; }

    public DateTime DateCreated { get; set; }
}
    • 我尝试的操作**-我尝试添加外键约束,但这不允许我在创建StripePayment之前添加StripeTransfer。
modelBuilder.Entity<StripePayment>()
            .HasOne<StripeTransfer>(t => t.StripeTransfer)
            .WithOne(t => t.StripePayment)
            .HasPrincipalKey<StripePayment>(p => p.SourceTransactionId)
            .HasForeignKey<StripeTransfer>(t => t.SourceTransactionId)
            .IsRequired(false);

尝试在StripePayment之前添加StripeTransfer时收到错误:
INSERT语句与FOREIGN KEY约束"FK_StripeTransfers_StripePayments_SourceTransactionId"冲突。冲突发生在数据库"yogabandy-database-dev"、表"dbo.StripePayments"、列"LatestChargeId"中。\n语句已终止。

8wigbo56

8wigbo561#

这是一个特殊的0..1 - 0..1关系,我还没有遇到过,让我总结一下它的性质,这样我们就可以检查一下我们是否在同一页上。

  • 数据库中有两个独立的实体(表)。
  • 它们由 * key *(即唯一)字段连接,这些字段是 * alternate *(非主)键字段。
  • 连接由两个实体中具有相同值的这些字段在语义上定义。
  • 只要具有相同键值的两个实体以任何到达顺序进入数据库,就在逻辑上建立连接。

在没有任何关系建模的情况下,可以通过两个连接属性上的定制join语句来查询实体,这可能是一个可行的解决方案。
但让我们来看看EF在这里能做些什么。
在数据库中,由于两个实体可以在没有对应实体的情况下存在,因此任何实体都不能具有指向另一实体的外键。只能通过引用这两个表的联接表来强制关系。
尽管如此,好消息是EF的配置API的表达能力足以对此进行建模。
类(省略用户字段):

public class StripePayment
{
    public int Id { get; set; }
    public int Amount { get; set; }

    public string SourceTransactionId { get; set; }
    public PaymentTransfer? PaymentTransfer { get; set; }
}

public class StripeTransfer
{
    public int Id { get; set; }
    public DateTime DateCreated { get; set; }
    
    public string SourceTransactionId { get; set; }
    public PaymentTransfer? PaymentTransfer { get; set; }
}

交叉点类别:

public class PaymentTransfer
{
    public string SourceTransactionId { get; set; }
    public StripePayment StripePayment { get; set; }
    public StripeTransfer StripeTransfer { get; set; }
}

是的,它只是一个string属性。
Map配置:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var pt = modelBuilder.Entity<PaymentTransfer>();
    pt.HasKey(e => e.SourceTransactionId);
    pt.HasOne(e => e.StripePayment).WithOne(e => e.PaymentTransfer)
        .HasPrincipalKey<StripePayment>(e => e.SourceTransactionId)
        .HasForeignKey<PaymentTransfer>(e => e.SourceTransactionId);
    pt.HasOne(e => e.StripeTransfer).WithOne(e => e.PaymentTransfer)
        .HasPrincipalKey<StripeTransfer>(e => e.SourceTransactionId)
        .HasForeignKey<PaymentTransfer>(e => e.SourceTransactionId);
}

一些亮点:

  • EF核心允许与替代键(两个实体中的SourceTransactionId)的关系。它通过HasForeignKey语句识别这些键。
  • 与交叉点类别的关系为1:1,使整个关系为0..1 - 0..1。
  • PaymentTransfer.SourceTransactionId是两个独立(主体)实体的主键和外键(对我来说,一个字段中有这么多函数是这个问题有趣的部分)。
  • 如果EF从此模型创建数据库架构,则实体表中的两个SourceTransactionId字段都将获得唯一索引。当首先使用数据库时,请确保创建这些索引。

现在,退一步,让我们评估一下选项。

  • 一个简单的join语句就足够了,但是手工编码的连接很繁琐而且容易出错,不过,当它隐藏在一些可重用的方法中时,它可能是最好的选择。
  • 一个junction类,虽然需要复杂的配置,但并不难理解,而且非常轻量级。
  • 通过数据库约束强制关系。
  • 它允许通过导航属性(查询中的p.PaymentTransfer.StripePayment.Amount等)进行查询。
  • 它是一种可见的、不言自明的关系表达
  • 但这在逻辑上是多余的:如果实体通过相同的键值"逻辑"连接,则没有连接记录,它们没有"物理"连接。
fnvucqvd

fnvucqvd2#

请尝试此操作。您只需更改 HasPrincipalKeyHasForeignKey 中的泛型类型。

modelBuilder.Entity<StripePayment>()
        .HasOne<StripeTransfer>(t => t.StripeTransfer)
        .WithOne(t => t.StripePayment)
        .HasPrincipalKey<StripeTransfer>(p => p.SourceTransactionId)
        .HasForeignKey<StripePayment>(t => t.SourceTransactionId)
        .IsRequired(false);

相关问题