首页 > 解决方案 > Entity Framework Core 3.x 数据库首次设计,其中数据库设计时没有外键

问题描述

我正在使用大约 10 年前设计的不使用外键约束的数据库。我们目前正在寻求使用 EF Core 将我们的软件升级到更统一的解决方案。我真的尝试出售公司以解决外键问题,但这需要更激烈的切换,他们希望我们可以保留当前的解决方案并一次切换部分。

在使用 EF Core 进行一些测试后,我发现选择数据并使用属性指定外键允许 EF Core 根据这些关系正确选择数据。但是,它在插入、更新或删除时不处理外键约束。当我试图研究这个问题时,我发现 EF Core 可能依赖 SQL Server 来处理插入、更新和删除约束,因此忽略了这些操作的 EF Core 属性。

我已经尝试使用 ModelBuilder 来指定 .OnDelete(DeleteBehavior.Cascade) 并且最好的 EF 似乎做的是更新对 NULL 的引用但从不抛出外键约束异常。

我想我的问题是这样的:

无论如何,Entity Framework Core 是否可以直接处理外键约束,而无需在 SQL 数据库中设置外键?例如,如果我尝试删除 EF Core 中具有关系的行,EF Core 是否可以拒绝删除,或者如果有插入但它所引用的表中不存在 id 是否可以拒绝插入. 并且只使用Property Attribute [ForeignKey] 或模型生成器来指定外键?

标签: c#entity-framework-core

解决方案


实体框架并非旨在保护数据库完整性。EF 对验证的立场主要是:自己做。越来越多,因为他们删除IValidatableObject了对 EF 核心的支持。但我实际上认为这是一个合理的方法。

所以你必须自己做。但是检查完整性违规是开发人员无论如何都应该做的事情,即使存在外键约束,因为您不想用原始数据库异常给用户带来麻烦——也不想尝试将它们转换成更可口的东西。约束只是最后的捕获(这在高并发的情况下尤其有用)。所以事实上,你唯一错过的就是最后的收获。生活没有那么糟糕。

有一种方法可以在一定程度上减轻这种不足。但是,您必须能够创建视图。

这个想法是创建一个可更新的视图,从原始表中选择所有字段并使用所有必需的外键进行内部连接。实体映射到此视图。如果在此视图中完成插入或更新,您将收到异常,因为 EF 遇到意外数量的修改行。

这是一个例子:

数据库架构:

CREATE TABLE [dbo].[Member](
    [ID] [int] NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
    [ClubID] [int] NOT NULL,
 CONSTRAINT [PK_Member] PRIMARY KEY CLUSTERED ([ID] ASC)
GO

CREATE TABLE [dbo].[Club](
    [ID] [int] NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_Group] PRIMARY KEY CLUSTERED ([ID] ASC)
GO

CREATE VIEW [dbo].[MemberView]
WITH SCHEMABINDING
AS
SELECT m.ID, m.Name, m.CLubID, c.Name AS ClubName
FROM dbo.Member m
INNER JOIN dbo.Club c ON m.ClubID = c.ID

课程:

public class Member
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int ClubID { get; set; }
    public Club Club { get; set; }

    // Fake FK constraint by computed field that won't be found if reference ID doesn't match.
    public string ClubName { get; set; } 
}

public class Club
{
    public int ID { get; set; }
    public string Name { get; set; }
    public ICollection<Member> Members { get; set; }
}

显式映射:

modelBuilder.Entity<Member>()
    .Property(m => m.ClubName).ValueGeneratedOnAddOrUpdate();
modelBuilder.Entity<Member>().ToTable("MemberView");
modelBuilder.Entity<Club>().ToTable("Club");

这里的重要部分是映射ClubNameValueGeneratedOnAddOrUpdate.

现在故意插入一个不存在俱乐部 ID 的成员:

using (var db = new MyContext(connectionString))
{
    db.Clubs.Add(new Club { ID = 1, Name = "Medalist Golf Club" });
    db.SaveChanges();
}

using (var db = new MyContext(connectionString))
{
    db.Members.Add(new Member { ID = 1, Name = "Tiger Woods", ClubID = 2 });
    db.SaveChanges();
}

由于此ValueGeneratedOnAddOrUpdate映射,EF 将添加一个SELECTINSERT获取数据库生成的值。

  INSERT INTO [MemberView] ([ID], [ClubID], [Name])
  VALUES (@p0, @p1, @p2);
  SELECT [ClubName]
  FROM [MemberView]
  WHERE @@ROWCOUNT = 1 AND [ID] = @p0;

这失败了,因为俱乐部2不存在并且视图没有返回一行。EF 将抛出一个DbUpdateConcurrencyException“预期会影响 1 行但实际上影响 0 行的数据库操作”。它与 FK 违规不同,但至少这提供了一种在一个原子事务中进行插入和完整性检查的方法。


推荐阅读