簡體   English   中英

實體框架:在表 '' 上引入 FOREIGN KEY 約束 '' 可能會導致循環或多個級聯路徑

[英]Entity Framework: Introducing FOREIGN KEY constraint '' on table '' may cause cycles or multiple cascade paths

在嘗試使用 Entity Framework 實現在此答案中找到的架構時,出現錯誤:

在表“OptionValues”上引入 FOREIGN KEY 約束“FK_OptionValues_Products_ProductId”可能會導致循環或多個級聯路徑

+---------------+     +---------------+
| PRODUCTS      |-----< PRODUCT_SKUS  |
+---------------+     +---------------+
| #product_id   |     | #product_id   |
|  product_name |     | #sku_id       |
+---------------+     |  sku          |
        |             |  price        |
        |             +---------------+
        |                     |
+-------^-------+      +------^------+
| OPTIONS       |------< SKU_VALUES  |
+---------------+      +-------------+
| #product_id   |      | #product_id |
| #option_id    |      | #sku_id     |
|  option_name  |      | #option_id  |
+---------------+      |  value_id   |
        |              +------v------+
+-------^-------+             |
| OPTION_VALUES |-------------+
+---------------+
| #product_id   |
| #option_id    |
| #value_id     |
|  value_name   |
+---------------+

model 類目前是這樣的

public class Option
{
    public int Id { get; set; }

    [ForeignKey("ProductId")]
    public int ProductId { get; set; }

    public string OptionName { get; set; }
    public Product Product { get; set; }
}

public class OptionValue
{
    public int Id { get; set; }

    [ForeignKey("ProductId")]
    public int ProductId { get; set; }

    [ForeignKey("OptionId")]
    public int OptionId { get; set; }

    public string OptionValueName { get; set; }
    public Product Product { get; set; }
    public Option Option { get; set; }
}

public class ProductSku
{
    public int Id { get; set; }

    [ForeignKey("ProductId")]
    public int ProductId { get; set; }

    public string Sku { get; set; }
    public decimal Price { get; set; }
    public Product Product { get; set; }
}

public class SkuValue
{
    public int Id { get; set; }

    [ForeignKey("ProductId")]
    public int ProductId { get; set; }

    [ForeignKey("ProductSkuId")]
    public int ProductSkuId { get; set; }

    [ForeignKey("OptionId")]
    public int OptionId { get; set; }

    [ForeignKey("OptionValueId")]
    public int OptionValueId { get; set; }

    public Product Product { get; set; }
    public ProductSku ProductSku { get; set; }
    public Option Option { get; set; }
    public OptionValue OptionValue { get; set; }
}

我在這里做錯了什么? 我怎么能解決這個問題?

這是因為從 OptionValues 中刪除一行會從其他表中刪除多行。 在 MySQL 中,您應該不會收到錯誤,因為我看到這種情況在 SQL 服務器中經常發生。 嘗試添加:

.WillCascadeOnDelete(false);

在您的模型構建器方法上。

這不會級聯刪除具有外鍵的所有其他行。

這里有一些競爭的東西,您的 model 看起來像是在嘗試使用多個 FK 來確保完整性,這與默認 EF 實現的一般預期不符,這意味着您將不得不對個人進行大量手動更新插入和更新字段時,更重要的是 EF 無法自行正確識別關系,您可能需要在Fluent Configuration中管理更多內容以使其正確。

以下陳述顯示了我如何解釋您的 model:

  • 一個Product有很多“Sku” - ProductSku
  • 一個Product有很多“選項”—— Option
  • 一個Option有許多命名的“值” - OptionValue
  • 每個OptionValue可以鏈接到許多“SKU” - SkuValue

如果是這種情況,那么您的圖表不太正確,您的 class 定義應該更改。

一般來說,我會推薦以下內容:

  1. SkuValue不需要產品的 FK,這是從它的父ProductSku假定的
  2. OptionValue也不需要Product的 FK,因為這是從它的父Option假設的
  3. SkuValue不需要FK 到Option ,因為這是從它的父OptionValue假設的

通過在這些表中的每一個中將 FK 留給Product ,EF 感到困惑,因為它可以看到,通過在子表中允許這些額外的導航鏈接,您的代碼可以為OptionValue分配不同的產品,而不是Product在其鏈接的Option中定義。

因此,雖然我們經常認為將字段留在那里有助於維護數據的完整性,但它們通過創建通常不可行的鏈接使您的代碼更容易違反

可以保留這些字段,但是當 model 有這樣的不一致時,我們需要添加更多配置以使其越界。

即使進行了這些更改,您仍將在所有這些表之間建立雙向鏈接,如此響應中所述: https://stackoverflow.com/a/65514280/1690217您將需要刪除其中的一個(或全部)默認級聯刪除行為。


通常,我發現刪除級聯刪除行為並通過流暢的配置顯式管理它會更安全,尤其是當您的子記錄具有深度重復鏈接時。

EF 中的默認約定工作得相當好,只要您堅持簡單的模型,只在表中使用單個鍵,不復制對父記錄中定義的子項的鍵引用,並且不與記錄該表已經是其子表。

如果您想刪除默認的級聯刪除行為,我建議您在OnModelCreating方法中使用這一行:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

如果您尚未刪除此約定,那么您將需要使用Fluent Notation來配置ForeignKey ,即使您已經嘗試使用Attribute Notation

在 Fluent 表示法中,您可以指定級聯行為並在單個鍵上啟用或禁用它,外鍵的流暢配置將覆蓋相同鍵字段的定義屬性。
請參閱EF Core 文檔中的級聯刪除

modelBuilder.Entity<Product>().HasMany(p => p.ProductsSkus).WithOne(sku => sku.Product).HasForeignKey(sku => sku.ProductId).OnDelete(DeleteBehavior.Restrict);

關於正確使用ForeignKeyAttribute

您對[ForeignKey]屬性的使用不符合要求,因此您必須已經用流利的表示法覆蓋了它。 注釋Navigation屬性時,使用ForeignKeyAttribute來描述FK屬性的名稱。 也可以反過來使用,可以將屬性放在FK屬性上,但不能用於描述Navigation屬性。

注意細微的差別:

public class Option
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public string OptionName { get; set; }
    [ForeignKey("ProductId")]
    public Product Product { get; set; }
}

或者這樣:

public class Option
{
    public int Id { get; set; }
    [ForeignKey("Product")]
    public int ProductId { get; set; }
    public string OptionName { get; set; }
    public Product Product { get; set; }
}

但是下一步,擺脫魔術字符串並使用對匹配屬性的編譯時安全引用:

public class Option
{
    public int Id { get; set; }
    [ForeignKey(nameof(Option.Product))]
    public int ProductId { get; set; }
    public string OptionName { get; set; }
    public Product Product { get; set; }
}

通過這種方式,您可以更多地了解傳遞給 FK 屬性的值是對另一個字段的實際引用的重要性。 請記住,使用屬性表示法時,當前不支持指定 FK 的級聯行為,因此請確保啟用或禁用自動應用級聯刪除的約定,這樣您就不必使用Fluent Notation定義每個 FK。

最終,以下 class 定義(對於您顯示的代碼段)應該可以工作,只需刪除任何可能覆蓋它的流利表示法:

請注意,我還定義了來自父記錄的導航鏈接以訪問從屬記錄,這允許您的Linq查詢通過導航鏈接在任一方向遍歷,而無需使用連接語法。

public class Product
{
    public int Id { get; set; }
    ...
    public virtual ICollection<Option> Options { get; set; } = new HashSet<Option>();
    public virtual ICollection<ProductSku> Skus { get; set; } = new HashSet<ProductSku>();
}

public class Option
{
    public int Id { get; set; }
    [ForeignKey(nameof(Option.Product))]
    public int ProductId { get; set; }

    public string OptionName { get; set; }
    public Product Product { get; set; }

    public virtual ICollection<OptionValue> Values { get; set; } = new HashSet<OptionValue>();
}

public class OptionValue
{
    public int Id { get; set; }
    [ForeignKey(nameof(OptionValue.Option))]
    public int OptionId { get; set; }

    public string OptionValueName { get; set; }
    public Option Option { get; set; }

    public virtual ICollection<SkuValue> Skus { get; set; } = new HashSet<SkuValue>();
}

public class ProductSku
{
    public int Id { get; set; }
    [ForeignKey(nameof(ProductSku.Product))]
    public int ProductId { get; set; }

    public string Sku { get; set; }
    public decimal Price { get; set; }
    public Product Product { get; set; }

    public virtual ICollection<SkuValue> Options { get; set; } = new HashSet<SkuValue>();
}

// many : many link between ProductSku and OptionValue
public class SkuValue
{
    public int Id { get; set; }
    [ForeignKey(nameof(SkuValue.ProductSku))]
    public int ProductSkuId { get; set; }
    [ForeignKey(nameof(SkuValue.OptionValue))]
    public int OptionValueId { get; set; }

    public ProductSku ProductSku { get; set; }
    public OptionValue OptionValue { get; set; }
}

嘗試從 OptionValue 和 SkuValue class 中刪除

 [ForeignKey("ProductId")]
  public int ProductId { get; set; }

因為您已經在 Option 和 ProductSku class 中有 ProductId

這里的問題是如何聲明外鍵。 在 SQL 上,您可以將“ON UPDATE”和“ON DELETE”arguments 設置為“CASCADE”或“NO ACTION”的外鍵。

“NO ACTION”不會導致該錯誤,但“CASCADE”會,因為它將與該行交互,並且它的所有引用會(或者更好的是,可能)導致 ciclic 引用,例如,在 OptionsValue 上刪除可以級聯到選項中的刪除,因此,級聯到產品中的刪除,因為它會導致 ProductsSku 上的刪除也會刪除 SkusValues(也指產品,所以再次級聯......)。

如果 all (或至少“父”表)設置為“NO ACTION”,那么只有在其他表上沒有引用它時才會刪除一行,並且可以安全使用。 因此,在您的聲明中,您需要將該級聯選項設置為 false(相當於生成的 sql 語句上的“NO ACTION”)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM