借用微软EFCore8官方的示例,我画了张类图:
blog(博客)与Post(文章)是1对多的关系,显式表达出两者间是双向导航:双方都可见。
Post(文章)与Tag(标签)是多对多的关系,单向导航:Post可见Tag,但是Tag不可见Post。
映射到代码(注意,为了能够进行数据库迁移,手动改了一些地方,比如增加了Id,但是实体类是可用的):
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
namespace MaxTools.EFCoreTest
{
public class Blog
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Owner { get; set; }
public List<Post> Posts { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
namespace MaxTools.EFCoreTest
{
public class Post
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public List<Tag> Tags { get; set; }
[DeleteBehavior(DeleteBehavior.Cascade)]
public Blog Blog { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.ComponentModel.DataAnnotations;
namespace MaxTools.EFCoreTest
{
public class Tag
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
}
*说明3:由于“Tag不可见Post”,所以不必设置导航属性。
注意以上代码:
1)双向导航中,双方都有导航属性List<...> ...
2)单向导航中,只有一方有属性List<...> ...
另外,需要考虑“组合关系”的时候,手动在代码里加映射属性,如下:
PostgresContext
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
namespace MaxTools.EFCoreTest;
public partial class PostgresContext : DbContext
{
public PostgresContext()
{
}
public PostgresContext(DbContextOptions<PostgresContext> options)
: base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseNpgsql("PORT=5432;DATABASE=postgres;HOST=localhost;PASSWORD=postgres;USER ID=postgres;");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
// DbSet 属性表示数据库中的表
public DbSet<Blog> BlogSet { get; set; }
public DbSet<Post> PostSet { get; set; }
public DbSet<Tag> TagSet { get; set; }
}
客户端程序
using System;
using Microsoft.EntityFrameworkCore;
namespace MaxTools.EFCoreTest
{
public class Program
{
static void Main()
{
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
// Tag tag1 = new Tag();
// tag1.Name = "testTag1";
// Tag tag2 = new Tag();
// tag2.Name = "testTag2";
// List<Tag> tags1 = new List<Tag>();
// tags1.Add(tag1);
// tags1.Add(tag2);
// Tag tag3 = new Tag();
// tag3.Name = "testTag3";
// Tag tag4 = new Tag();
// tag4.Name = "testTag4";
// List<Tag> tags2 = new List<Tag>();
// tags2.Add(tag3);
// tags2.Add(tag4);
// Post post1 = new Post();
// post1.Title = "post1";
// post1.tags = tags1;
// Post post2 = new Post();
// post2.Title = "post2";
// post2.tags = tags2;
// List<Post> posts = new List<Post>();
// posts.Add(post1);
// posts.Add(post2);
// Blog blog = new Blog();
// blog.Name = "testBlog";
// blog.Owner = "Zhang";
// blog.Posts = posts;
// PostgresContext context = new PostgresContext();
// context.BlogSet.Add(blog);
// int count = context.SaveChanges();
// Console.WriteLine(count);
// //删除
PostgresContext context = new PostgresContext();
Blog blog = context.BlogSet
.Include(x => x.Posts)
.ThenInclude(x => x.Tags)
.FirstOrDefault(x => x.Id == 1);
context.Remove(blog);
int count = context.SaveChanges();
Console.WriteLine(count);
}
}
}
在数据库中的表现为:
把逻辑的思考与数据库设计分离开。
以上代码经测可以级联删除,数据库是Postgres 16.1。