![](/img/trans.png)
[英]SQL Error: Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths. Entity Framework Core
[英]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 定義應該更改。
一般來說,我會推薦以下內容:
SkuValue
不需要產品的 FK,這是從它的父ProductSku
假定的OptionValue
也不需要Product
的 FK,因為這是從它的父Option
假設的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.