c# - EF Core 通过空检查破坏了构造函数的目的

标签 c# entity-framework entity-framework-core

我设计的模型使用null 参数创建。例如,如果我想强制每个 Post 都应该有一个相应的 Blog,那么我的模型将如下所示:

public class Post
{
    private Post() { }
    public Post(Blog blog)
    {
        Blog = blog ?? throw new ArgumentNullException(nameof(blog));
    }
    public int PostId { get; private set; }
    public Blog Blog { get; private set; }
}
public class Blog
{
    public int BlogId { get; private set; }
}

这样,如果 blognull,它将抛出一个 Exception

但我使用的是 EF Core,但它未通过此测试。

public class Tests
{
    [Fact]
    public void Test()
    {
        using (var ctx = new Context())
        {
            ctx.Database.EnsureDeleted();
            ctx.Database.EnsureCreated();
            ctx.Add(new Post(new Blog()));
            ctx.SaveChanges();
        }
        using (var ctx = new Context())
        {
            var post = ctx.Post.First();
            Assert.NotNull(post.Blog); //fail
        }
    }
}
public class Context : DbContext
{
    public DbSet<Post> Post { get; set; }
    public DbSet<Blog> Blog { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(b => b.HasOne(p => p.Blog));
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlite("datasource=db.sqlite");
}

我知道这是因为 EF Core 调用了不带参数的私有(private)构造函数,我需要加载(即 Eager、Explicit 或 Lazy)Post.Blog 导航属性。

但我想知道我的 EF 模型设计方法是否不正确,因为 EF Core 通过 null 检查破坏了构造函数的目的?

编辑:使用 C#8 的可空引用类型,EF Core 可能会朝着可以使用构造函数设置导航属性的方向发展。请参阅 EF Core 问题:Support C# nullable references

最佳答案

长话短说

您可以在您的对象模型中显式定义外键,数据库模型使用该外键来表示 Post/Blog 关系。

public class Post
{
    private Post() { }
    public Post(Blog blog)
    {
        Blog = blog ?? throw new ArgumentNullException(nameof(blog));
    }
    public int PostId { get; private set; }
    public int BlogId {get; private set; } # Foreign Key Property
    public Blog Blog { get; private set; }
}
public class Blog
{
    public int BlogId { get; private set; }
}

然后您可以重写您的测试以检查此外键是否存在空值。

public class Tests
{
    [Fact]
    public void Test()
    {
        using (var ctx = new Context())
        {
            ctx.Database.EnsureDeleted();
            ctx.Database.EnsureCreated();
            ctx.Add(new Post(new Blog()));
            ctx.SaveChanges();
        }
        using (var ctx = new Context())
        {
            var post = ctx.Post.First();
            Assert.NotEqual(0, post.BlogId); //passes
        }
    }
}
public class Context : DbContext
{
    public DbSet<Post> Post { get; set; }
    public DbSet<Blog> Blog { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(b => b.HasOne(p => p.Blog));
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlite("datasource=db.sqlite");
}

另一种方法是在您选择帖子时告诉您的实体模型包含导航属性。

public class Tests
{
    [Fact]
    public void Test()
    {
        using (var ctx = new Context())
        {
            ctx.Database.EnsureDeleted();
            ctx.Database.EnsureCreated();
            ctx.Add(new Post(new Blog()));
            ctx.SaveChanges();
        }
        using (var ctx = new Context())
        {
            var post = ctx.Post.Include(p=>p.Blog).First();
            Assert.NotNull(post.Blog); //passes
        }
    }
}
public class Context : DbContext
{
    public DbSet<Post> Post { get; set; }
    public DbSet<Blog> Blog { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(b => b.HasOne(p => p.Blog));
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlite("datasource=db.sqlite");
}



详细说明

如果您的设计允许,您的方法就是正确的。不正确的是您对职责分离的理解。我们有多个关注领域:

  1. 对象模型和不变量
  2. 实体模型和对象关系映射
  3. 数据库模型、约束和引用完整性

EF Core 负责#2。 EF Core 需要知道您的对象模型和数据库模型的结构才能在两者之间成功映射。您可以在对象模型或实体模型中指定数据库约束,以帮助您在访问数据库之前捕获违反这些约束的行为,但这不是必需的。

让我们来看两个场景。

场景一

我们想将新记录插入到我们的表中,使用您的应用程序代码帮助用户在将记录提交到数据库之前制作记录。我们从关注的领域#1,您的对象模型开始。

根据您为对象建模的方式,您可能希望强制执行某些不变量。在您的示例中,您的规则是每个 Post 对象都属于一个 Blog 对象。事实上,表中的每个 Post 记录都有一个关联的 Blog 记录,这纯粹是这个不变量的副作用。

场景#2

您想从表中选择一条记录,使用您的应用程序代码在内存中表示该记录,以便将其显示给用户。我们从关注的领域#3,你的数据库模型开始。

您的数据库通过外键(即 BlogId)强制执行参照完整性。在 EF Core 中,您不需要在 Post 对象上定义此外键。 EF Core 会根据你的 Post 的导航属性 Blog 为你创建一个 shadow 属性。影子属性只是存在于数据库模型中但不存在于您的对象模型中的属性。

当向数据库模型询问它的记录时,它要求您非常具体地说明要包括哪些关系。它不会默认将所有外键连接到它们各自的表,您需要明确地执行此操作。您的实体模型也不会自动为您执行此操作。您需要在 select 语句中调用 .Include 来填充对象模型的导航属性。

就数据库模型和您的实体模型而言,参照完整性存在,并正确映射到您的对象模型。结果,你的对象模型的不变性没有被强制执行,因为你从数据库模型的数据表示开始,然后回到你的对象模型的表示。

关于c# - EF Core 通过空检查破坏了构造函数的目的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51092081/

相关文章:

c# - Entity Framework 数据库首先从基类继承模型

c# - EF Core 2.1 HasData() 在后续迁移中为未更改的实体创建删除和重新插入

c# - 通过 Internet 更新我的 Windows 应用程序的新方法

c# - asp.net 中服务器传输中的 ThreadAbortException

c# - 用于嵌套层次结构的响应式(Reactive)扩展展开/扫描方法

.net-core - 点网核心 3.1,EF1001 : internal ef core api usage

c# - EF Core 删除对自身的引用而不删除

c# - Xamarin Connectivity 插件总是抛出 NotImplementedException

c# - 使用 Entity Framework 自动递增主键

c# - Linq GroupBy 子句不包括计数为零的项目