【问题标题】:ASP.NET MVC 5 EF Cannot add or update a child row: a foreign key constraint failsASP.NET MVC 5 EF 无法添加或更新子行:外键约束失败
【发布时间】:2016-01-24 13:22:22
【问题描述】:

我有一个线程,其中包含作为线程内容的一部分的 startpost。它还包括一个回复列表,这些回复也是帖子。

class Post {
    [Key]
    public int Id{get;set;}
    public DateTime CreationDate{get;set;}
    public virtual string Content{get;set;}
    public int ThreadId{get;set;}
    public virtual Thread Thread{get;set;}
}

class Thread {
    [Key]
    public int Id{get;set;}
    public string Title{get;set;}
    public int FirstPostId{get;set;}
    public virtual Post FirstPost{get;set;}
    public List<Post> Replys{get;set;}
}

创建线程后,我只需将它们添加到 DbContext 并保存即可。这没有问题。但是如果提交了回复,我会将它们添加到帖子列表中,并将线程的实体标记为这样修改

var db = new MyContext();
var thread = db.Threads.Where(thread => thread.Id = threadId).FirstOrDefault();
thread.Replys.Add(newPost);
db.Entry<Thread>(thread).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();

这是我在 Thread.FirstPost 上违反外键的问题:

无法添加或更新子行:外键约束失败 ("MyTable"."posts", CONSTRAINT "Thread_FirstPost" FOREIGN KEY ("Id") REFERENCES "threads" ("Id") ON DELETE NO ACTION ON更新无操作)

我找到了很多关于此的信息。简而言之,所有人都说,这与 EF 检查完整性的默认行为有关。因此,当必须删除线程时,它取决于也必须删除的 FirstPost,但这取决于似乎使 EF 感到困惑的线程。

对于这个问题,互联网有 2 个解决方案:使用 fluent-API 禁用使用 .WillCascadeOnDelete(false); 的实体的级联或通过删除约定完全禁用它。我尝试了两种方法:

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        //base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Entity<Thread>()
            .HasOptional(t => t.FirstPost)
            .WithRequired()
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Post>()
            .HasOptional(p => p.Thread)
            .WithMany()
            .HasForeignKey(p => p.ThreadId)
            .WillCascadeOnDelete(false);
}

但是没有任何效果,我得到了与以前相同的异常。我不知道为什么,似乎所有其他有这个问题的人都可以通过使用其中一种方法来解决它,但就我而言,两者都没有效果......

来自 Visual Studio Server-Explorer 的表定义

CREATE TABLE `posts` (
  `Id` int(11) NOT NULL,
  `CreationDate` datetime NOT NULL,
  `Content` longtext NOT NULL,
  `ThreadId` int(11) NOT NULL,
  PRIMARY KEY (`Id`),
  KEY `ThreadId` (`ThreadId`),
  CONSTRAINT `Thread_Post` FOREIGN KEY (`Id`) REFERENCES `threads` (`Id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `Thread_Replys` FOREIGN KEY (`ThreadId`) REFERENCES `threads` (`Id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1

CREATE TABLE `threads` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Title` longtext NOT NULL,
  `PostId` int(11) NOT NULL,
  `ViewsCount` int(11) NOT NULL,
  `IsClosed` tinyint(1) NOT NULL,
  `IsVisible` tinyint(1) NOT NULL,
  `ReplysCount` int(11) NOT NULL,
  PRIMARY KEY (`Id`),
  UNIQUE KEY `Id` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

EF 生成的表定义(来自数据库服务器)

CREATE TABLE `Posts`(
    `Id` int NOT NULL, 
    `CreationDate` datetime NOT NULL, 
    `Content` longtext NOT NULL, 
    `ThreadId` int NOT NULL
)
ALTER TABLE `Posts` ADD PRIMARY KEY (Id)
ALTER TABLE `Posts` ADD CONSTRAINT Thread_Post
ALTER TABLE `Posts` ADD KEY (`ThreadId`)
ALTER TABLE `Posts` ADD CONSTRAINT Thread_Replys
ALTER TABLE `Posts` ADD CONSTRAINT Thread_Post
    FOREIGN KEY (Id)
    REFERENCES `Threads` (Id)
ALTER TABLE `Posts` ADD CONSTRAINT Thread_Replys
    FOREIGN KEY (ThreadId)
    REFERENCES `Threads` (Id)
ALTER TABLE `Posts` ADD CONSTRAINT Thread_Replys
    FOREIGN KEY (ThreadId)
    REFERENCES `Threads` (Id)
    ON DELETE NO ACTION ON UPDATE NO ACTION

CREATE TABLE `Threads`(
    `Id` int NOT NULL AUTO_INCREMENT UNIQUE, 
    `Title` longtext NOT NULL, 
    `PostId` int NOT NULL, 
    `ViewsCount` int NOT NULL, 
    `IsClosed` bool NOT NULL, 
    `IsVisible` bool NOT NULL, 
    `ReplysCount` int NOT NULL
)
ALTER TABLE `Threads` ADD PRIMARY KEY (Id)

以下是我在研究该主题时发现的一些页面: http://weblogs.asp.net/manavi/associations-in-ef-code-first-ctp5-part-3-one-to-one-foreign-key-associations

http://www.codeproject.com/Articles/368164/EF-Data-Annotations-and-Code-Fluent

http://geekswithblogs.net/danemorgridge/archive/2010/12/17/ef4-cpt5-code-first-remove-cascading-deletes.aspx

http://patrickdesjardins.com/blog/entity-framework-4-3-delete-cascade-with-code-first-poco

ASP.NET MVC 4 Multiple Foreign Keys Referencing Single Parent Entity

http://www.davepaquette.com/archive/2012/09/15/whered-my-data-go-andor-how-do-i-get-rid-of-it.aspx

http://czetsuya-tech.blogspot.de/2012/01/specify-on-delete-no-action-or-on.html#.Viy-0X54u9J

Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths - why?

Entity Framework: how to solve "FOREIGN KEY constraint may cause cycles or multiple cascade paths"?

Specify ON DELETE NO ACTION in ASP.NET MVC 4 C# Code First

【问题讨论】:

  • 它说 FOREIGN KEY 约束失败,它与删除时的级联无关。当您向数据库添加条目时,您只需确保所有 FK 都与它们所引用的导航属性上的键匹配(即使它们稍后被更改,它们也必须设置为相同的临时值)跨度>
  • 我编辑了一些我在研究过程中发现的页面来解决这个错误。我认为这里的重点是更新而不是删除,因为我不删除数据,我更新线程。 FK 很好,在创建线程 EF 后使用 Id 1 正确加载 FirstPost。这就是我无法理解此错误的原因 - 我认为我必须指定更新操作而不是 NO ACTION,但是我不知道该怎么做。
  • 我认为它只是提到了插入失败的完整约束。
  • 在线程类中,尝试在public virtual Post FirstPost{get;set;} 上方添加[ForeignKey("FirstPostId")],您需要为此使用using System.ComponentModel.DataAnnotations;
  • 这给了我在后模型中找不到 FK 的错误。我认为它应该是 [ForeignKey("FirstPost")] 并高于 FirstPostId 属性,但这不起作用。 @DevilSuichiro 抱歉,我完全听不懂你想说什么。

标签: c# database entity-framework asp.net-mvc-5


【解决方案1】:

您的外键设置不正确,如果您使用原始类名,可以不明确定义外键,我将在您的代码中解释:

class Post {
    [Key]
    public int Id{get;set;}
    public DateTime CreationDate{get;set;}
    public virtual string Content{get;set;}
    public int ThreadId{get;set;}  **-> here you used ThreadId which is implicitly a foreignkey for Thread, and that's good**
    public virtual Thread Thread{get;set;}
}

class Thread {
    [Key]
    public int Id{get;set;}
    public string Title{get;set;}
    public int FirstPostId{get;set;} **-> here you can do the same by changing this to PostId**
    public virtual Post FirstPost{get;set;} **-> and this to Post**
    public List<Post> Replys{get;set;}
}

或者你有更好的选择使用数据注释:

替换这个:

 public virtual Post FirstPost{get;set;}

用这个:

[ForeignKey("FirstPostId")]
public virtual Post FirstPost{get;set;}

这告诉 EF FirstPostId 是 `FirstPost 的外键。

让我知道这是否有效。

更新

我已经手动更改了您的 sql 代码,现在可以使用:

CREATE TABLE threads (
  Id int NOT NULL,
  Title nvarchar(max) NOT NULL,
  PostId int,
  ViewsCount int NOT NULL,
  IsClosed tinyint NOT NULL,
  IsVisible tinyint NOT NULL,
  ReplysCount int NOT NULL,
  PRIMARY KEY (Id),
) 

CREATE TABLE posts (
  Id int NOT NULL,
  CreationDate datetime NOT NULL,
  Content nvarchar(max) NOT NULL,
  ThreadId int NOT NULL,
  PRIMARY KEY (Id),
  CONSTRAINT Thread_Post FOREIGN KEY (Id) REFERENCES threads (Id) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT Thread_Replys FOREIGN KEY (ThreadId) REFERENCES threads (Id) ON DELETE NO ACTION ON UPDATE NO ACTION
)

ALTER TABLE threads ADD CONSTRAINT Post_Thread FOREIGN KEY (PostId) REFERENCES posts (Id) ON DELETE NO ACTION ON UPDATE NO ACTION

您不能将线程定义为需要 postID,并同时发布以需要 threadID,如果这样做,您将无法创建任何一个(除非您同时创建两者 - 意思是在同一个数据库中.savechanges())。

考虑一下。您想使用尚不存在的帖子定义线程。

【讨论】:

  • 我将Thread.FirstPostId 重命名为Thread.PostId => 无效,再次出现“无法添加或更新子行...”错误。使用 ForeignKey-Attribute 我得到一个属性无效的异常,因为在 Post-Model 中找不到 PostId。我认为属性应该是ForeignKey("FirstPost") 并放在PostId 之上-然后无效属性的异常消失了,但是由于FK FirstPost,我再次得到“无法添加或更新子行”异常: (
  • 如果您尝试重命名字段,请将FirstPost 重命名为Post。您还可以发布您的数据库表定义吗?
  • 我重命名了FirstPost-Attribute,但还是一样。您可以在此处查看 EF 生成的定义:u-img.net/view/6007Yn.png
  • 您的意思是生成表的普通 SQL?
  • 您在 Fluent API 更改之前提供了代码。在 Visual Studio -> 打开服务器资源管理器 -> 连接到您的数据库 -> 就在表上 -> 单击打开表定义 -> 在那里你可以看到代码。尝试加载它here
【解决方案2】:

根本问题是您的模型包含 PostThread 之间的 1:1 关联,其中 Thread 是主要或独立实体。这由部分表示...

modelBuilder.Entity<Thread>()
    .HasOptional(t => t.FirstPost)
    .WithRequired()

你看到它反映在 DDL 语句中......

ALTER TABLE `Posts` ADD CONSTRAINT Thread_Post
    FOREIGN KEY (Id)
    REFERENCES `Threads` (Id)

所以Post's 主键也是Thread 的外键。这意味着每个Thread 不能插入多个Post! (因为每个后续的 Post 都必须有一个新的 PK 值,但这并不引用现有的 Thread 所以你会违反约束)。

您可以通过将Post 设置为主要实体来解决这个问题(也许)。在这种情况下,Thread 将有一个 PK/FK 组合引用它的第一个 Post。然而,对我来说,1:1 关联表明实体与它们几乎是一个的点密切相关(Student-StudentDetails)。所以我不认为1:1的关联在这里是合适的。

我会建议这个映射:

modelBuilder.Entity<Thread>()
    .HasOptional(t => t.FirstPost)
    .WithMany()
    .HasForeignKey(t => t.FirstPostId)
    .WillCascadeOnDelete(false);

modelBuilder.Entity<Post>()
    .HasRequired(p => p.Thread)
    .WithMany(t => t.Replies)
    .HasForeignKey(p => p.ThreadId)
    .WillCascadeOnDelete(false);

这理论上将ThreadFirstPost之间的关系变成了一对多,但实际上这意味着Thread现在有了一个外键到它的第一个帖子,这些复杂的PK/FK组合就没有了.请注意,FirstPostId 应该是一个可为空的 int 以支持这一点。

另一方面,如果您认为 Thread 和它的第一个帖子 密切相关,您可以考虑将两者合并到一个线程中,该线程也具有其第一个帖子的属性 (@987654343 @、Content)。您最终会得到一个非常简单的线程模型(帖子?),其中仍然没有多余的回复。

【讨论】:

  • 您的代码给了我一个错误,即找不到任务之间的正确映射 - 但我现在明白问题的原因了。我的第一种方法是像你说的那样使用继承(线程继承自 Post)。邮件的问题是:我想稍后显示最新的内容(所以线程和帖子)。当 Thread 的第一篇文章是 Post 时,这会更加简单明了,因此我只需查询按 CreationDate 排序并按 ThreadId 分组的 Post-Table。
  • 但是由于这不起作用,我将尝试将LastPostTime 属性添加到线程,这样我就可以简单地查询按LastPostTime 排序的线程表。这将导致一些冗余信息,但我认为没有更好的方法来实现这一点。
  • 您可能的意思是 EF 无法确定插入/更新的正确顺序?是的,这可能是个问题,您应该先保存Thread。合并Thread/FirstPost 类的优点是保存线程和帖子是原子操作(当然,它是一个记录)。
猜你喜欢
  • 2015-02-16
  • 2020-10-04
相关资源
最近更新 更多