ASP.NET MVC 5 EF无法添加或更新子行:外键约束失败

时间:2015-10-25 10:58:35

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

我有一个线程,其中包含一个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的线程。

互联网有两个解决此问题的方法:使用fluent-API为{1}}的实体禁用级联,或通过删除约定完全禁用它。我试过两种方式:

.WillCascadeOnDelete(false);

但是没有任何工作,我得到了比以前更多的例外。我不明白为什么,似乎所有其他有这个问题的人都可以通过使用其中一种方法解决它,但在我的情况下两者都没有效果......

来自Visual Studio Server-Explorer的表定义

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);
}

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

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

以下是我在此主题研究中发现的一些页面: 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

2 个答案:

答案 0 :(得分:0)

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

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,否则你将无法创建(除非你同时创建两者 - 意味着在同一个db中.savechanges())。

想一想。你想用一个尚不存在的帖子来定义一个帖子。

答案 1 :(得分:0)

根本问题是您的模型在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的外键。这意味着您不能每Post次插入多个Thread! (因为每个后续的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之间的关系转换为1对多,但实际上这意味着Thread现在有一个外键到它的第一个帖子,这些复杂PK / FK组合消失了。请注意,FirstPostId应该是一个可以为空的int来支持这一点。

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