【问题标题】:EF Core FOREIGN KEY constraint > multiple cascade pathsEF Core FOREIGN KEY 约束 > 多个级联路径
【发布时间】:2021-01-05 00:07:07
【问题描述】:

我试图在我的模型中实现以下关系(简化)。换句话说,每个Employee 可以有多个银行Accounts(私人账户、公司账户等)。每个员工都可以携带任何与公司相关的Receipt(例如煤气费、警察罚款等)。现在,根据这张收据的总价值,公司可以决定他们是否会立即向用户收取这个价值,或者,如果这是大量的钱,他们需要每月扣除(因此每张收据可以有多个Transactions在这种情况下被理解为扣除)。如果立即对Receipt 收费(请参阅模型上的ReceiptType 属性),则此实体仅存在一个Transaction。最后(特别是在长期扣除的情况下),Transaction 需要访问Account,因为我们需要指定,这个值将被记入哪个员工的帐户。

这是我指定实体的方式:

public class EmployeeDB
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public virtual IEnumerable<EmployeeAccountDB> Accounts { get; set; }
    public virtual IEnumerable<ReceiptDB> Receipts { get; set; }
}

public class EmployeeAccountDB
{
    public long Id { get; set; }
    public long EmployeeId { get; set; }
    public virtual EmployeeDB Employee { get; set; }
    public virtual IEnumerable<TransactionDB> Transactions { get; set; }
}

public class ReceiptDB
{
    public long Id { get; set; }
    public long EmployeeDBId { get; set; }
    public ReceiptTypeEnum ReceiptType { get; set; }
    public double Value { get; set; }
    public double VAT { get; set; }
    public double TotalValue { get; set; }

    public virtual EmployeeDB Employee { get; set; }
    public virtual IEnumerable<TransactionDB> Transactions { get; set; }
}

public class TransactionDB
{
    public long Id { get; set; }
    public long EmployeeAccountId { get; set; }
    public long ReceiptDBId { get; set; }

    public virtual ReceiptDB Receipt { get; set; }
    public virtual EmployeeAccountDB Account { get; set; }
}

现在我通过 Fluent API 指定了以下实体配置:

public class EmployeeDBConfiguration : IEntityTypeConfiguration<EmployeeDB>
{
    public void Configure(EntityTypeBuilder<EmployeeDB> builder)
    {
        builder.ToTable("Employees", ApplicationDbContext.DefaultSchema);
        builder.HasKey(t => t.Id);
        builder.Property(t => t.HireDate).IsRequired();
        builder.HasMany(x => x.Accounts).WithOne(x => x.Employee).OnDelete(DeleteBehavior.Cascade);
        builder.HasMany(x => x.Receipts).WithOne(x => x.Employee).OnDelete(DeleteBehavior.Cascade);
    }
}

public class EmployeeAccountDBConfiguration : IEntityTypeConfiguration<EmployeeAccountDB>
{
    public void Configure(EntityTypeBuilder<EmployeeAccountDB> builder)
    {
        builder.ToTable("EmployeeAccounts", ApplicationDbContext.DefaultSchema);
        builder.HasKey(t => t.Id);
        builder.Property(t => t.Id).ValueGeneratedOnAdd();
        builder.HasOne(x => x.Employee).WithMany(o => o.Accounts).HasForeignKey(nameof(EmployeeAccountDB.EmployeeId));
        builder.HasMany(x => x.Transactions).WithOne(x => x.Account).OnDelete(DeleteBehavior.Cascade);
    }
}

   public class ReceiptDBConfiguration : IEntityTypeConfiguration<ReceiptDB>
    {
    public void Configure(EntityTypeBuilder<ReceiptDB> builder)
    {
        builder.ToTable("Receipts", ApplicationDbContext.DefaultSchema);
        builder.HasKey(t => t.Id);
        builder.Property(t => t.Id).ValueGeneratedOnAdd();
        builder.Property(t => t.TotalValue).IsRequired();
        builder.HasOne(x => x.Employee).WithMany(x=>x.Receipts).HasForeignKey(x => x.EmployeeDBId);
        builder.HasMany(x => x.Transactions).WithOne(x => x.Receipt).OnDelete(DeleteBehavior.Cascade);
    }
    }

class TransactionDBConfiguration : IEntityTypeConfiguration<TransactionDB>
{
    public void Configure(EntityTypeBuilder<TransactionDB> builder)
    {
        builder.ToTable("Transactions", ApplicationDbContext.DefaultSchema);
        builder.HasKey(t => t.Id);
        builder.Property(t => t.Id).ValueGeneratedOnAdd();
    }
}

迁移创建得很好。但是,当我尝试通过dotnet ef database update 创建数据库时,就会出现问题。我遇到以下问题

引入 FOREIGN KEY 约束 表“事务”上的“FK_Transactions_Receipts_ReceiptDBId”可能 导致循环或多级联路径

我不确定真正的问题是什么。我的目标是(我试图设置的)当Employee 被删除时,与该实体相关的所有后续实体AccountReceiptTransaction 也将被删除(如配置中指定的 >级联删除)。

我们将非常感谢您就此事提供任何帮助。如果我的模型结构不正确,我很乐意对其进行重构以使其保持简单和干净。

附:我正在连接 SQL 数据库。

【问题讨论】:

  • 看看stackoverflow.com/questions/27792131/…。 SQL Server 无法处理删除链。一个出路是使用逻辑删除在每个类中设置一个public bool IsActive {get;set;}
  • 这可能与以下内容重复:stackoverflow.com/questions/851625/…
  • 收据是否只记入一个帐户?如果收据与与员工关联的帐户(或与帐户关联的交易)相关联,为什么收据会链接到员工?
  • 收据未与账户相关联,因为交易负责从账户中扣款。收据只是有关特定账单或发票的信息的持有者。但是 Receipt 应该包含有关 Employee 的信息,以防我们需要显示与 Employee 关联的所有 Receipts
  • 所以收据和交易不相关?

标签: c# sql database-design ef-core-3.1


【解决方案1】:

我收到了很多关于 SO 的精彩文章,它们正好解决了我的问题,但最终我决定如何解决它的方式如下:

对于Account,我决定实现软删除功能(这导致该帐户永远不会被删除,除非Employee 被删除),因此我能够丢弃员工>帐户>交易路径如下(最后一行代码):

public class EmployeeAccountDBConfiguration : IEntityTypeConfiguration<EmployeeAccountDB>
{
    public void Configure(EntityTypeBuilder<EmployeeAccountDB> builder)
    {
        builder.ToTable("EmployeeAccounts", ApplicationDbContext.DefaultSchema);
        builder.HasKey(t => t.Id);
        builder.Property(t => t.Id).ValueGeneratedOnAdd();
        builder.HasOne(x => x.Employee).WithMany(o => o.Accounts).HasForeignKey(nameof(EmployeeAccountDB.EmployeeId));
        builder.HasMany(x => x.Transactions).WithOne(x => x.Account).OnDelete(DeleteBehavior.NoAction);
    }
}

这样,SQL 服务器只能看到模型中的 1 个正确的级联路径(员工 > 收据 > 交易),效果很好。

【讨论】:

    猜你喜欢
    • 2018-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-08
    • 2013-10-22
    相关资源
    最近更新 更多