EF Core побеждает цель конструктора с нулевой проверкой - PullRequest
0 голосов
/ 29 июня 2018

Я спроектировал свои модели так, чтобы они создавались , а не с параметром 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; }
}

Таким образом, он выдаст Exception, если 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");
}

Я знаю, что это потому, что EF Core вызывает закрытый конструктор без параметров, и мне нужно загрузить (например, Eager, Explicit или Lazy) навигационное свойство Post.Blog.

Но что я хочу знать, так это то, что мой подход к проектированию моих моделей EF неверен, так как EF Core побеждает цель конструктора с проверкой нуля?

РЕДАКТИРОВАТЬ: С необязательными ссылочными типами C # 8, EF Core может двигаться в направлении, где он может устанавливать навигационные свойства, используя конструктор. См. Проблему EF Core: Поддержка ссылок C Nullable

1 Ответ

0 голосов
/ 11 декабря 2018

TL; DR * * 1001 Вы можете явно определить внешний ключ в вашей объектной модели, который модель базы данных использует для представления отношения 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

Мы хотим вставить новые записи в нашу таблицу, используя код вашего приложения, чтобы помочь пользователю создать запись перед ее передачей в базу данных. Мы начинаем с проблемной области № 1, вашей объектной модели.

В зависимости от того, как вы смоделировали ваши объекты, вы можете захотеть применить определенные инварианты. В вашем примере у вас есть правило, что каждый объект Post принадлежит объекту Blog. Тот факт, что каждая запись Post в таблице имеет связанную запись Blog, является просто побочным эффектом этого инварианта.

Сценарий № 2

Вы хотите выбрать запись из своей таблицы, используя код приложения, чтобы представить ее в памяти, чтобы вы могли отобразить ее пользователю. Мы начинаем с проблемной области № 3, вашей модели базы данных.

Ваша база данных обеспечивает ссылочную целостность с помощью внешних ключей, т. Е. BlogId. В EF Core вам не нужно определять этот внешний ключ для вашего объекта Post. EF Core создаст для вас свойство shadow на основе блога навигации вашего Post. Свойство shadow - это просто свойство, которое существует в модели базы данных, но не в вашей объектной модели.

Когда вы спрашиваете модель базы данных о ее записях, вам необходимо четко указать, какие отношения включить. По умолчанию это не объединит все внешние ключи с соответствующими таблицами, вам нужно сделать это явно. Ваша модель сущности также не делает это автоматически для вас. Вам нужно вызвать .Include в вашем операторе select, чтобы заполнить свойства навигации вашей объектной модели.

Что касается модели базы данных и вашей модели сущностей, ссылочная целостность существует и правильно отображается в вашей объектной модели. В результате инвариант вашей объектной модели не применяется, потому что вы начали с представления данных в модели базы данных и вернулись к представлению вашей объектной модели.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...