C# EF Core通过空检查违背了构造函数的目的

C# EF Core通过空检查违背了构造函数的目的,c#,entity-framework,entity-framework-core,C#,Entity Framework,Entity Framework Core,我设计的模型应该而不是null参数。例如,如果我想强制要求每个帖子都应该有一个相应的博客,那么我的模型如下所示: public class Post { private Post() { } public Post(Blog blog) { Blog = blog ?? throw new ArgumentNullException(nameof(blog)); } public int PostId { get; private set;

我设计的模型应该而不是
null
参数。例如,如果我想强制要求每个
帖子
都应该有一个相应的
博客
,那么我的模型如下所示:

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; }
}
这样,如果
blog
null
,它将抛出
异常

但我用的是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");
}
公共类测试
{
[事实]
公开无效测试()
{
使用(var ctx=new Context())
{
ctx.Database.EnsureDeleted();
ctx.Database.recreated();
添加(新帖子(newblog());
ctx.SaveChanges();
}
使用(var ctx=new Context())
{
var post=ctx.post.First();
Assert.NotNull(post.Blog);//失败
}
}
}
公共类上下文:DbContext
{
公共DbSet Post{get;set;}
公共数据库集博客{get;set;}
模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
{
实体(b=>b.HasOne(p=>p.Blog));
}
配置时受保护的覆盖无效(DbContextOptionsBuilderOptionsBuilder)=>optionsBuilder.UseSqlite(“数据源=db.sqlite”);
}
我知道这是因为EF Core调用没有参数的私有构造函数,我需要加载
Post.Blog
navigation属性(即,Eager、Explicit或Lazy)

但我想知道的是,我的EF模型设计方法是否不正确,因为EF核心通过空检查违背了构造函数的目的

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

TL;博士 您可以在对象模型中显式定义外键,数据库模型使用该外键表示日志/博客关系

公共类职位
{
私人邮政(){}
公开帖子(博客)
{
Blog=Blog??抛出新的ArgumentNullException(nameof(Blog));
}
public int PostId{get;private set;}
public int BlogId{get;private set;}#外键属性
公共博客Blog{get;private set;}
}
公共类博客
{
public int BlogId{get;private set;}
}
然后,您可以重写测试以检查该外键的空值

公共类测试
{
[事实]
公开无效测试()
{
使用(var ctx=new Context())
{
ctx.Database.EnsureDeleted();
ctx.Database.recreated();
添加(新帖子(newblog());
ctx.SaveChanges();
}
使用(var ctx=new Context())
{
var post=ctx.post.First();
Assert.NotEqual(0,post.BlogId);//通过
}
}
}
公共类上下文:DbContext
{
公共DbSet Post{get;set;}
公共数据库集博客{get;set;}
模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
{
实体(b=>b.HasOne(p=>p.Blog));
}
配置时受保护的覆盖无效(DbContextOptionsBuilderOptionsBuilder)=>optionsBuilder.UseSqlite(“数据源=db.sqlite”);
}
另一种方法是在选择帖子时告诉实体模型包含导航属性

公共类测试
{
[事实]
公开无效测试()
{
使用(var ctx=new Context())
{
ctx.Database.EnsureDeleted();
ctx.Database.recreated();
添加(新帖子(newblog());
ctx.SaveChanges();
}
使用(var ctx=new Context())
{
var post=ctx.post.Include(p=>p.Blog.First();
Assert.NotNull(post.Blog);//传递
}
}
}
公共类上下文:DbContext
{
公共DbSet Post{get;set;}
公共数据库集博客{get;set;}
模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
{
实体(b=>b.HasOne(p=>p.Blog));
}
配置时受保护的覆盖无效(DbContextOptionsBuilderOptionsBuilder)=>optionsBuilder.UseSqlite(“数据源=db.sqlite”);
}


详细说明 如果您的设计保证,那么您的方法是正确的。不正确的是你对责任分离的理解。我们有多个关注领域:

  • 对象模型与不变量
  • 实体模型与对象关系映射
  • 数据库模型、约束和引用完整性
  • EF Core负责#2。EF Core需要了解对象模型和数据库模型的结构,才能成功地在两者之间进行映射。您可以在对象模型或实体模型中指定数据库约束,以帮助您在访问数据库之前捕获违反这些约束的情况,但这不是必需的

    让我们看两个场景

    情景1

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

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

    场景#2

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

    您的数据库使用外键(即BlogId)强制引用完整性。在EF Core中,您不需要在Post对象上定义此外键。EF Core将根据帖子的导航属性为您创建一个阴影属性