Entity Framework 报告循环引用,但没有任何意义

Posted

技术标签:

【中文标题】Entity Framework 报告循环引用,但没有任何意义【英文标题】:Entity Framework reports circular reference, but it doesn't make any sense 【发布时间】:2012-01-23 19:42:07 【问题描述】:

这是大约。我正在使用的代码。

public class Note 
  public virtual Customer Customer  get; set; 
  public virtual User User  get; set; 
  public ICollection<NoteComment> Comments  get; set; 


public class NoteComment 
  public virtual User User  get; set; 


public class User 
  public ICollection<Note> Notes  get; set; 


public class Customer 

// --------------------------------------

public class OurDataContext 
  private void ConfigureNotes(DbModelBuilder modelBuilder) 
    modelBuilder.Entity<Note>()
      .HasRequired<User>(n => n.User)
      .WithMany(u => u.Notes)
      .Map(a => a.MapKey("UserId"));

    modelBuilder.Entity<Note>()
      .HasRequired(n => n.Customer)
      .WithMany(c => c.Notes)
      .Map(a => a.MapKey("idCustomer"));

    modelBuilder.Entity<Note>()
      .HasMany(n => n.Comments)
      .WithRequired()
      .HasForeignKey(c => c.NoteID);    
/*
    modelBuilder.Entity<NoteComment>()
      .HasRequired<User>(c => c.User)
      .WithMany()
      .Map(a => a.MapKey("UserId"));
*/
  

注意ConfigureNotes()方法中,最后的配置被注释掉了。如果我将此注释掉,EF 会很好地创建我的表,但如果我取消注释此块,我会收到以下错误:

Unhandled Exception: System.InvalidOperationException: The database creation succeeded, but the creation of the database objects did not. See inner exception for more details. ---> System.Data.SqlServerCe.SqlCeException: The referential relationship will result in a cyclical reference that is not allowed. [ Constraint name = Note_Comments ]
   at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr)
   at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommandText(IntPtr& pCursor, Boolean& isBaseTableCursor)
   at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options)
   at System.Data.SqlServerCe.SqlCeCommand.ExecuteNonQuery()
   at System.Data.SqlServerCe.SqlCeProviderServices.DbCreateDatabase(DbConnection connection, Nullable`1 timeOut, StoreItemCollection storeItemCollection)
   --- End of inner exception stack trace ---
   ...

我不明白为什么NoteComment => User 的导航属性在Note => NoteComment 之间生成循环引用。


EDIT

由于某种原因,将 NoteComment 类中的 FK 指定为可为空的属性解决了该问题。

public class NoteComment 
  public Guid? UserId  get; set; 
  [ForeignKey("UserId")]
  public virtual User User  get; set; 

然后我删除了数据上下文类中被注释掉的映射代码。

这并不理想,但我可以手动管理这个约束。

【问题讨论】:

【参考方案1】:

与其他数据库相比,SQL Server 对于可能的循环引用或多个删除路径非常保守。

您的来自 NoteComment 的多个删除路径:

Delete User -> Note -> NoteComment
Delete User -> NoteComment

一种解决方案是删除 Cascade On Delete for User -> NoteComment 并手动进行清理。

您还可以编写数据库触发器来进行清理。这是一个示例触发器:

CREATE TRIGGER [dbo].[Users_Delete_Cleanup] 
   ON  [dbo].[Users]
   INSTEAD OF DELETE
AS 
BEGIN
    IF @@ROWCOUNT = 0
        RETURN

    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Delete NoteComment <--> User associations
    DELETE nc FROM [dbo].[NoteComment] nc
    JOIN DELETED dUser ON dUser.[Id] = nc.[User_Id]

    -- Finally, delete user
    DELETE u
    FROM DELETED dUser
    JOIN [dbo].[Users] u ON u.[Id] = dUser.[Id]
END

编辑 - 附加信息:

如果您还没有,我强烈建议您使用EF Power Tools extension。这使您能够右键单击任何实现 DbContext 的类并获取实体框架上下文菜单 - 在解决方案资源管理器中右键单击您的 DbContext 类 -> 实体框架 -> 查看 DDL SQL

这将为您提供用于生成整个数据模型的 Sql 语句 - 对于查看 EF 认为它正在构建的确切内容非常有用。您可以尝试在 SqlServer 中手动运行它,并更接近它所遇到的错误。当 EF 构建 DDL Sql 时,如果没有编译错误,它通常会给你一些东西(或者一个完全神秘的空引用错误 - 然后检查你的输出窗口),但这些东西可能不会在 SqlServer 中运行。

此外,您可以手动删除级联删除与流畅配置的一对多关系,除非您想要该属性,否则无需指定键:

modelBuilder.Entity<NoteComment>()
    .HasRequired<User>(c => c.User)
    .WithMany()
    .WillCascadeOnDelete(false);

【讨论】:

好的,但仍然对此感到困惑的是,如果我注释掉映射代码,EF 仍然会从 NoteComments => User 生成自己的 FK,但它使用属性名称 User_ID,这就是我正在尝试使用手动映射进行更改。如果 SQL Server 在级联删除方面存在问题,为什么它会允许 EF 生成这种关系? 这是一个 EF CodeFirst 约定 - 在模型生成过程中根本不涉及 SqlServer。 EF 会自行生成架构,而不管 SqlServer 是否接受它。当您手动将键指定为可为空的类型时,将删除删除时的级联。【参考方案2】:

您在 User->Note Comments->Note->User->etc..之间有一个循环引用。

如果您有 Keys 设置,您将永远不会停止以上述方式引用。

有许多不同的方法可以结束循环引用。例如,[ScriptIgnore] 属性在导致循环引用的属性上方,或者您可以执行树搜索方法,其中每个分支检查以确保添加的对象不是父节点(递归地)一直到树的根节点。

【讨论】:

以上是关于Entity Framework 报告循环引用,但没有任何意义的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core 5 - 在表上引入 FOREIGN KEY 约束可能会导致循环或多个级联路径

在 Entity Framework 6 查询中取消引用可能为空的引用

Entity Framework Core 2 - 外键约束

在 Entity Framework 7 中创建自引用多对多关系

Entity Framework Core:外键和引用表 Id 列不匹配

如何将 C# 8.0 可空引用类型与 Entity Framework Core 模型一起使用?