EFCore中如何正确地设置两张表之间的关联关系
数据库
假设现在我们在SQL Server数据库中有下⾯两张表:
Person表,代表的是⼀个⼈:
CREATE TABLE[dbo].[Person](
[ID][int]IDENTITY(1,1) NOT NULL,
[PersonCode][nvarchar](20) NULL,
[Name][nvarchar](50) NULL,
[Age][int]NULL,
CONSTRAINT[PK_Person]PRIMARY KEY CLUSTERED
(
[ID]ASC
)WITH (PAD_INDEX =OFF, STATISTICS_NORECOMPUTE =OFF, IGNORE_DUP_KEY =OFF, ALLOW_ROW_LOCKS =ON, ALLOW_PAGE_LOCKS =ON) ON[PRIMARY],
CONSTRAINT[IX_Person]UNIQUE NONCLUSTERED
(
[PersonCode]ASC
)WITH (PAD_INDEX =OFF, STATISTICS_NORECOMPUTE =OFF, IGNORE_DUP_KEY =OFF, ALLOW_ROW_LOCKS =ON, ALLOW_PAGE_LOCKS =ON) ON[PRIMARY]
) ON[PRIMARY]
GO
其主键是ID,⽽且主键是⾃增列。Person表还有个PersonCode列是唯⼀键,然后Name和Age列⽤来描述⼀个⼈的名字和年龄。
Book表,代表的是⼀本书:
CREATE TABLE[dbo].[Book](
[ID][int]IDENTITY(1,1) NOT NULL,
[BookCode][nvarchar](20) NULL,
[PersonCode][nvarchar](20) NULL,
[BookName][nvarchar](50) NULL,
[ISBN][nvarchar](20) NULL,
CONSTRAINT[PK_Book]PRIMARY KEY CLUSTERED
(
[ID]ASC
)WITH (PAD_INDEX =OFF, STATISTICS_NORECOMPUTE =OFF, IGNORE_DUP_KEY =OFF, ALLOW_ROW_LOCKS =ON, ALLOW_PAGE_LOCKS =ON) ON[PRIMARY],
CONSTRAINT[IX_Book]UNIQUE NONCLUSTERED
(
[BookCode]ASC
)WITH (PAD_INDEX =OFF, STATISTICS_NORECOMPUTE =OFF, IGNORE_DUP_KEY =OFF, ALLOW_ROW_LOCKS =ON, ALLOW_PAGE_LOCKS =ON) ON[PRIMARY]
) ON[PRIMARY]
GO
其主键是ID,⽽且主键也是⾃增列。Book表的BookCode列是唯⼀键,Book表的PersonCode列引⽤Person表的PersonCode列值,所以Book表的PersonCode列实际上是外键,但是我们并没有在数据库中设置两张表之间的外键关系,我们将稍后在EF Core中的实体之间设置外键关系,来演⽰就算在数据库中没有设置外键,EF Core也可以设置实体之间的外键关系。 所以Person表和Book表实际上是⼀对多关系,通过两张表的PersonCode列,⼀个Person对应多个Book,表⽰⼀个⼈可以拥有多本书。Book表还有BookName列和ISBN列,分别⽤来记录⼀本书的书名和ISBN号码。
实体
新建⼀个.NET Core控制台项⽬,现在我们在EF Core中建⽴Person表和Book表的实体:
Person实体,对应数据库的Person表,其属性Book是⼀个ICollection<Book>类型的Book实体集合,表⽰⼀个Person实体包含多个Book实体:
public partial class Person
{
public int Id { get; set; }
public string PersonCode { get; set; }
public string Name { get; set; }
public int? Age { get; set; }
//通过Person实体的Book属性,可以到多个Book实体,说明Person表是⼀对多关系中的主表
public virtual ICollection<Book> Book { get; set; }
}
Book实体,对应数据库的Book表,其属性Person是⼀个Person实体,表⽰⼀个Book实体只能到⼀个Person实体:
public partial class Book
{
public int Id { get; set; }
public string BookCode { get; set; }
public string PersonCode { get; set; }
public string BookName { get; set; }
public string Isbn { get; set; }
//通过Book实体的Person属性,可以到⼀个Person实体,说明Book表是⼀对多关系中的从表
public virtual Person Person { get; set; }
}
然后是继承DbContext的TestDBContext类,其中最重要的地⽅是OnModelCreating⽅法中设置Person实体和Book实体⼀对多关系的Fluent API,每⼀⾏都写明了注释:
public partial class TestDBContext : DbContext
{
public TestDBContext()
{
}
public TestDBContext(DbContextOptions<TestDBContext> options)
: base(options)
{
}
public virtual DbSet<Book> Book { get; set; }
public virtual DbSet<Person> Person { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=Dtt!123456;Database=TestDB");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>(entity =>
{
entity.HasKey(e => e.BookCode);//设置Book实体的BookCode属性为EF Core实体的Key属性
entity.HasIndex(e => e.BookCode)
.HasName("IX_Book")
.IsUnique();
entity.Property(e => e.Id).ValueGeneratedOnAdd();//设置Book实体的Id属性为插⼊数据到数据库Book表时⾃动⽣成,因为Book表的ID列为⾃增列            entity.Property(e => e.Id).HasColumnName("ID");
entity.Property(e => e.BookCode).HasMaxLength(20);
entity.Property(e => e.BookName).HasMaxLength(50);
entity.Property(e => e.Isbn)
.HasColumnName("ISBN")
.HasMaxLength(20);
entity.Property(e => e.PersonCode).HasMaxLength(20);
});
modelBuilder.Entity<Person>(entity =>
{
entity.HasKey(e => e.PersonCode);//设置Person实体的PersonCode属性为EF Core实体的Key属性
entity.HasIndex(e => e.PersonCode)
.HasName("IX_Person")
.IsUnique();
entity.Property(e => e.Id).ValueGeneratedOnAdd();//设置Person实体的Id属性为插⼊数据到数据库Person表时⾃动⽣成,因为Person表的ID列为⾃增列
entity.Property(e => e.Id).HasColumnName("ID");
entity.Property(e => e.Name).HasMaxLength(50);
entity.Property(e => e.PersonCode).HasMaxLength(20);
//设置Person实体和Book实体之间的⼀对多关系,尽管我们并没有在数据库中建⽴Person表和Book表之间的⼀对多外键关系,但是我们可以⽤EF Core的Fluent API在实体层⾯设置外键关系
entity.HasMany(p => p.Book)//设置Person实体通过属性Book可以到多个Book实体,表⽰Person表是⼀对多关系中的主表
.WithOne(b => b.Person)//设置Book实体通过属性Person可以到⼀个Person实体,表⽰Book表是⼀对多关系中的从表
.HasPrincipalKey(p => p.PersonCode)//设置Person表的PersonCode列为⼀对多关系中的主表键
.HasForeignKey(b => b.PersonCode)//设置Book表的PersonCode列为⼀对多关系中的从表外键
.OnDelete(DeleteBehavior.ClientSetNull);//设置⼀对多关系的级联删除效果为DeleteBehavior.ClientSetNull
});
}
writeline方法属于类
}
⽰例代码
现在我们来设想下⾯⼀个场景:
假设数据库中的Person表有⼀⾏数据如下:
数据库中的Book表有三⾏数据如下:
可以看到Book表三⾏数据的PersonCode列都为NULL,那么我们怎么在EF Core中更改Book表三⾏数据的PersonCode列为Person表的PersonCode列值呢?也就是说将Book表三⾏数据的PersonCode列都改为Person表的值P001,从⽽表⽰James这个⼈拥有三本书。
本例的⽰例代码都写在了.NET Core控制台项⽬的Program类中,这⾥先将代码全部贴出来:
class Program
{
///<summary>
///初始化Person表和Book表的数据,没有设置Book表的外键列PersonCode的值
///</summary>
static void InitData()
{
//初始化数据库数据
using (var dbContext = new TestDBContext())
{
var james = new Person() { PersonCode = "P001", Name = "James", Age = 30 };
dbContext.Person.Add(james);
var chineseBook = new Book() { BookCode = "B001", Isbn = "001", BookName = "Chinese" };//没有设置Book表中外键列PersonCode的值
var japaneseBook = new Book() { BookCode = "B002", Isbn = "001", BookName = "Japanese" };//没有设置Book表中外键列PersonCode的值
var englishBook = new Book() { BookCode = "B003", Isbn = "001", BookName = "English" };//没有设置Book表中外键列PersonCode的值
//插⼊三条数据到Book表
dbContext.Book.Add(chineseBook);
dbContext.Book.Add(japaneseBook);
dbContext.Book.Add(englishBook);
dbContext.SaveChanges();
}
}
///<summary>
///删除Person表和Book表的所有数据
///</summary>
static void DeleteAllData()
{
using (var dbContext = new TestDBContext())
using (var dbContext = new TestDBContext())
{
dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Book]");
dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Person]");
}
}
///<summary>
///不正确地设置Person表和Book表的关联关系,这种⽅法会让EF Core错误地⽣成INSERT语句,⽽不是UPDATE语句
///</summary>
static void SetRelationshipIncorrectly()
{
using (var dbContext = new TestDBContext())
{
var james = dbContext.Person.First(e => e.Name == "James");//⾸先通过DbContext从数据库中查询出要建⽴关联关系的Person表实体
var chineseBook = new Book() { BookCode = "B001" };//只构造Book实体的Key属性即可,根据BookCode值"B001"来构造Chinese Book
var japaneseBook = new Book() { BookCode = "B002" };//只构造Book实体的Key属性即可,根据BookCode值"B002"来构造Japanese Book
var englishBook = new Book() { BookCode = "B003" };//只构造Book实体的Key属性即可,根据BookCode值"B003"来构造English Book
Console.WriteLine($"Before adding, chineseBook entity state is :{dbContext.Entry(chineseBook).State.ToString()}");//可以看到由于此时Book实体chineseBook没有被DbContext跟踪,所以状态是Detached
Console.WriteLine($"Before adding, japaneseBook entity state is :{dbContext.Entry(japaneseBook).State.ToString()}");//可以看到由于此时Book实体japaneseBook没有被DbContext跟踪,所以状态是Detached
Console.WriteLine($"Before adding, englishBook entity state is :{dbContext.Entry(englishBook).State.ToString()}");//可以看到由于此时Book实体englishBook没有被DbContext跟踪,所以状态是Detached
Console.WriteLine();
james.Book = new List<Book>();//由于我们在上⾯调⽤dbContext.Person.First(e => e.Name == "James")时,没有⽤EF Core中Eager Loading的Include⽅法来加载Book实体集合,所以这⾥要⽤List类来构造⼀个Book实体集合,否则james.Book为null
james.Book.Add(chineseBook);//添加chineseBook到Person类的Book实体集合
Console.WriteLine("chineseBook was added into Person.Book collection");
james.Book.Add(japaneseBook);//添加japaneseBook到Person类的Book实体集合
Console.WriteLine("japaneseBook was added into Person.Book collection");
james.Book.Add(englishBook);//添加englishBook到Person类的Book实体集合
Console.WriteLine("englishBook was added into Person.Book collection");
Console.WriteLine();
Console.WriteLine($"After querying DbContext.Entry(chineseBook), chineseBook entity state is :
{dbContext.Entry(chineseBook).State.ToString()}");//调⽤DbContext.Entry()⽅法后,DbContext发现⼀个原本状态是Detached的Book实体chineseBook被加⼊到Person.Book集合中了,所以此时chineseBook的实体状态变为了Added
Console.WriteLine($"After querying DbContext.Entry(japaneseBook), japaneseBook entity state is :
{dbContext.Entry(japaneseBook).State.ToString()}");//调⽤DbContext.Entry()⽅法后,DbContext发现⼀个原本状态是Detached的Book实体japaneseBook 被加⼊到Person.Book集合中了,所以此时japaneseBook的实体状态变为了Added
Console.WriteLine($"After querying DbContext.Entry(englishBook), englishBook entity state is :
{dbContext.Entry(englishBook).State.ToString()}");//调⽤DbContext.Entry()⽅法后,DbContext发现⼀个原本状态是Detached的Book实体englishBook被加⼊到Person.Book集合中了,所以此时englishBook的实体状态变为了Added
dbContext.SaveChanges();//由于此时chineseBook、japaneseBook和englishBook的EntityState都是Added,所以此时DbContext.SaveChanges⽅法调⽤后,EF Core⽣成的是INSERT语句,将chineseBook、japaneseBook和englishBook插⼊数据库表Book,导致插⼊了重复值到唯⼀键列BookCode,所以数据库报错
}
}
///<summary>
///正确地设置Person表和Book表的关联关系,这种⽅法会让EF Core正确地⽣成UPDATE语句,在数据库中设置Book表的PersonCode列数据
///</summary>
static void SetRelationshipCorrectly()
{
using (var dbContext = new TestDBContext())
{

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。