【问题标题】:FK constraints may cause cycles or multiple cascade pathsFK 约束可能导致循环或多个级联路径
【发布时间】:2018-12-19 06:14:03
【问题描述】:

为什么我最初的 update-database 失败了,我需要在我的数据库表类中进行哪些更改才能使其正常工作?

当然,我可以将迁移脚本中的onDelete: ReferentialAction.Cascade 更改为onDelete: ReferentialAction.NoAction,但是我的应用程序中会遇到其他问题。我正在寻求无需编辑由add-migration 生成的迁移脚本的解决方案。换句话说,我愿意对我的数据库架构进行更改。

我想要的行为是,当我删除Product 时,关联的ProductPropertyOptionForProducts 也被删除,但不是相反,而不是与ProductPropertyOptionForProducts 关联的ProductPropertyOption

这是迁移输出错误消息:

在表“PropertyOptionsForProducts”上引入 FOREIGN KEY 约束“FK_PropertyOptionsForProducts_ProductPropertyOptions_ProductPropertyOptionId”可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。 无法创建约束或索引。查看以前的错误。

生成的导致错误的 SQL 命令:

CREATE TABLE[PropertyOptionsForProducts] (
[Id] int NOT NULL IDENTITY,
[CustomNumberValue] decimal (18, 2) NOT NULL,
[CustomRangeFrom] decimal (18, 2) NOT NULL,
[CustomRangeTo] decimal (18, 2) NOT NULL,
[CustomStringValue] nvarchar(max) NULL,
[ProductId] int NOT NULL,
[ProductPropertyId] int NOT NULL,
[ProductPropertyOptionId] int NOT NULL,
CONSTRAINT[PK_PropertyOptionsForProducts] PRIMARY KEY([Id]),
CONSTRAINT[FK_PropertyOptionsForProducts_Products_ProductId]
    FOREIGN KEY([ProductId])
    REFERENCES[Products] ([Id]) ON DELETE CASCADE,
CONSTRAINT[FK_PropertyOptionsForProducts_ProductPropertyOptions_ProductPropertyOptionId]
    FOREIGN KEY([ProductPropertyOptionId])
    REFERENCES[ProductPropertyOptions] ([Id]) ON DELETE CASCADE
);

课程:

public class ProductPropertyOption
{
    public int Id { get; set; }
    public int ProductPropertyId { get; set; }
    // some more properties
    public ProductProperty Property { get; set; }
    public ICollection<PropertyOptionForProduct> PropertyOptionForProducts { get; set; }
}


public class PropertyOptionForProduct
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int ProductPropertyId { get; set; }
    public int ProductPropertyOptionId { get; set; }
    // some more properties
    public Product Product { get; set; }
    public ProductPropertyOption ProductPropertyOption { get; set; }
}


public class Product
{
    public int Id { get; set; }
    public bool Published { get; set; }
    public int ProductGroupId { get; set; }
    public int ProductGroupSortOrder { get; set; }
    // some more properties
    public int ProductTypeId { get; set; }

    public ICollection<ProductImage> Images { get; set; }
    public ICollection<PropertyOptionForProduct> ProductPropertyOptionForProducts { get; set; }
    public ICollection<IdentifierForProduct> IdentifierForProducts { get; set; }
    public ProductType Type { get; set; }
    public ICollection<FrontPageProduct> InFrontPages { get; set; }
    public ICollection<ProductInCategory> InCategories { get; set; }
}


public class ProductType
{
    public int Id { get; set; }
    public string Title { get; set; }
    public List<ProductIdentifierInType> Identifiers { get; set; }
    public List<ProductProperty> Properties { get; set; }
    public ICollection<Product> Products { get; set; }
}


public class ProductProperty
{
    public int Id { get; set; }
    public int ProductTypeId { get; set; }
    // some more properties
    public List<ProductPropertyOption> Options { get; set; }
    public ProductType ProductType { get; set; }
}

图示的数据库(产品和类别部分):

【问题讨论】:

  • 多个级联路径是从 ProductType 到 PropertyOptionForProduct:(1) ProductType -> Product -> PropertyOptionForProduct 和 (2) ProductType -> ProductProperty -> ProductPropertyOption -> PropertyOptionForProduct跨度>
  • 您是否尝试过绘制各个类以及它们之间的引用?我觉得你有太多的关系无法理解:)
  • @IvanStoev 在我的ProductType-view 中,我需要包含ProductProperty -> ProductPropertyOption,在Product-view 中,我需要包含PropertyOptionForProduct -> @ 987654338@ -> ProductProperty。在没有多个级联路径的情况下如何做到这一点?
  • @Boris 我有一个简单的地图,在方框之间用鱼尾纹表示关系。我已将其添加到问题的底部。 :)

标签: c# database-design entity-framework-core entity-framework-migrations ef-core-2.0


【解决方案1】:

关系图清楚地显示了从ProductTypePropertyOptionForProduct的多重级联路径:

(1)ProductType -> Product -> PropertyOptionForProduct

(2)ProductType -> ProductProperty -> ProductPropertyOption -> PropertyOptionForProduct

唯一的解决方案是通过关闭至少一个关系的级联删除来中断级联路径,然后手动处理主体实体删除。

可能最简单的方法是破坏一些根路径,例如ProductType -> ProductProperty

modelBuilder.Entity<ProductType>()
    .HasMany(e => e.Properties)
    .WithOne(e => e.ProductType)
    .OnDelete(DeleteBehavior.Restrict);

那么当你需要删除一个ProductType,而不是“正常”时:

db.Remove(db.Set<ProductType>().Single(e => e.Id == id));
db.SaveChanges();

你要先删除相关的Properties:

var productType = db.Set<ProductType>().Include(e => e.Properties).Single(e => e.Id == id);
db.RemoveRange(productType.Properties);
db.Remove(productType);
db.SaveChanges();

【讨论】:

    【解决方案2】:

    afaik,“级联”操作包含在关系数据库的最初设计中。起初,它被视为一种控制孤儿记录可能性的便捷方式。起初是……。

    然而,随着这些数据库变得越来越大,Cascade 引起的问题越来越多,超出了它们的价值……正如您所见。

    一种解决方案是创建扩展所有直接关系的视图。视图上的“代替”触发器将在删除目标实体之前处理依赖实体的删除。

    例如,视图“ProductTypeForDelete”可能如下所示:

    select * from ProductTypeForDelete where ID = 1001;
    ID    TABLE              KEY
    ===== ==========         =====
    1001  Product            300
    1001  Product            301
    1001  ProductProperty    203
    

    考虑命令:

    delete from ProductTypeForDelete where ID = 1001;
    

    触发器将接收上面显示的结果集。它在 Product 表中显示了 2 个依赖项,在 ProductProperty 表中显示了一个。所以视图上的delete 触发器知道它需要先从这两个表中删除,然后再从 ProductType 表中删除。

    还有视图 ProductForDelete 和 ProductPropertyForDelete 会继续这个链。视图 PropertyOptionForProductForDelete 上的 delete 触发器会知道它位于链的末尾并执行删除。然后执行链将展开,在执行过程中从目标表中删除。

    您可能认为这需要很多视图和很多触发器,但这些都只是代码并且非常易于维护。另一个优点是这在从关系链中的任何位置删除时都有效。要删除产品而不是整个产品类型,只需发出命令:

    delete from ProductForDelete where ID = 300;
    

    一切都按预期进行。

    我们不只是在模拟“级联”功能吗?不,有一个非常重要的区别。如果您已使用级联删除定义了所有表,则从 ProductType 表中删除将锁定该表,然后锁定 Product 和 ProductProperty 表,然后继续进行。必须先锁定每个关系分支中的每个表,然后才能执行任何删除操作。使用视图,首先在链的末尾执行锁定,执行删除,释放锁定,然后锁定下一个表。这正是您想要的行为。

    【讨论】:

      【解决方案3】:

      您可以将它添加到 DataContext.cs,这对我有用。

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
         modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
         modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-25
        • 2015-05-22
        • 1970-01-01
        • 2020-07-25
        相关资源
        最近更新 更多