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");
}
Подробное объяснение
Ваш подход верен, если ваш дизайн это оправдывает. Что неверно, так это ваше понимание разделения обязанностей. У нас есть несколько проблемных областей:
- Объектная модель и инварианты
- Отображение модели объекта и отношения объекта
- Модель базы данных, ограничения и ссылочная целостность
EF Core отвечает за # 2. EF Core должен знать структуру как вашей объектной модели, так и модели базы данных, чтобы успешно отобразить между ними. Вы можете указать ограничения базы данных в объектной модели или в модели сущностей, чтобы помочь вам обнаружить нарушения этих ограничений перед выполнением поездки в базу данных, но они не обязательны.
Давайте рассмотрим два сценария.
Сценарий 1
Мы хотим вставить новые записи в нашу таблицу, используя код вашего приложения, чтобы помочь пользователю создать запись перед ее передачей в базу данных. Мы начинаем с проблемной области № 1, вашей объектной модели.
В зависимости от того, как вы смоделировали ваши объекты, вы можете захотеть применить определенные инварианты. В вашем примере у вас есть правило, что каждый объект Post принадлежит объекту Blog. Тот факт, что каждая запись Post в таблице имеет связанную запись Blog, является просто побочным эффектом этого инварианта.
Сценарий № 2
Вы хотите выбрать запись из своей таблицы, используя код приложения, чтобы представить ее в памяти, чтобы вы могли отобразить ее пользователю. Мы начинаем с проблемной области № 3, вашей модели базы данных.
Ваша база данных обеспечивает ссылочную целостность с помощью внешних ключей, т. Е. BlogId. В EF Core вам не нужно определять этот внешний ключ для вашего объекта Post. EF Core создаст для вас свойство shadow на основе блога навигации вашего Post. Свойство shadow - это просто свойство, которое существует в модели базы данных, но не в вашей объектной модели.
Когда вы спрашиваете модель базы данных о ее записях, вам необходимо четко указать, какие отношения включить. По умолчанию это не объединит все внешние ключи с соответствующими таблицами, вам нужно сделать это явно. Ваша модель сущности также не делает это автоматически для вас. Вам нужно вызвать .Include в вашем операторе select, чтобы заполнить свойства навигации вашей объектной модели.
Что касается модели базы данных и вашей модели сущностей, ссылочная целостность существует и правильно отображается в вашей объектной модели. В результате инвариант вашей объектной модели не применяется, потому что вы начали с представления данных в модели базы данных и вернулись к представлению вашей объектной модели.