EF Core中如何正确地设置两张表之间的关联关系
数据库
假设现在我们在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();
- entity.Property(e => e.BookName).HasMaxLength();
- entity.Property(e => e.Isbn)
- .HasColumnName("ISBN")
- .HasMaxLength();
- entity.Property(e => e.PersonCode).HasMaxLength();
- });
- 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();
- entity.Property(e => e.PersonCode).HasMaxLength();
- //设置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
- });
- }
- }
示例代码
现在我们来设想下面一个场景:
假设数据库中的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 = };
- dbContext.Person.Add(james);
- var chineseBook = new Book() { BookCode = "B001", Isbn = "", BookName = "Chinese" };//没有设置Book表中外键列PersonCode的值
- var japaneseBook = new Book() { BookCode = "B002", Isbn = "", BookName = "Japanese" };//没有设置Book表中外键列PersonCode的值
- var englishBook = new Book() { BookCode = "B003", Isbn = "", 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())
- {
- 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())
- {
- 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
- dbContext.Attach(chineseBook);//将chineseBook关联到DbContext,开始跟踪
- dbContext.Attach(japaneseBook);//将japaneseBook关联到DbContext,开始跟踪
- dbContext.Attach(englishBook);//将englishBook关联到DbContext,开始跟踪
- Console.WriteLine($"After querying DbContext.Entry(chineseBook), chineseBook entity state is :{dbContext.Entry(chineseBook).State.ToString()}");//由于上面chineseBook被Attach到DbContext开始跟踪了,所以此时chineseBook的实体状态是Unchanged
- Console.WriteLine($"After querying DbContext.Entry(japaneseBook), japaneseBook entity state is :{dbContext.Entry(japaneseBook).State.ToString()}");//由于上面japaneseBook被Attach到DbContext开始跟踪了,所以此时japaneseBook的实体状态是Unchanged
- Console.WriteLine($"After querying DbContext.Entry(englishBook), englishBook entity state is :{dbContext.Entry(englishBook).State.ToString()}");//由于上面englishBook被Attach到DbContext开始跟踪了,所以此时englishBook的实体状态是Unchanged
- 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($"Berfore querying DbContext.Entry(chineseBook), chineseBook.PersonCode is :{chineseBook.PersonCode ?? "null"}");//此时由于我们还没有调用DbContext.Entry()方法,所以DbContext还无法察觉到chineseBook已经被添加到Person类的Book实体集合了,所以chineseBook.PersonCode为null
- Console.WriteLine($"After querying DbContext.Entry(chineseBook), chineseBook entity state is :{dbContext.Entry(chineseBook).State.ToString()}");//调用DbContext.Entry()方法后,DbContext发现一个原本状态是Unchanged的Book实体chineseBook被加入到Person.Book集合中了,所以此时chineseBook的实体状态变为了Modified
- Console.WriteLine($"After querying DbContext.Entry(chineseBook), chineseBook.PersonCode is :{chineseBook.PersonCode}");//由于上面我们调用DbContext.Entry(chineseBook)使得DbContext得知了chineseBook被加入到Person.Book集合中了,所以DbContext还将Book实体的外键属性PersonCode也进行了赋值,为P001
- Console.WriteLine();
- Console.WriteLine($"Berfore querying DbContext.Entry(japaneseBook),japaneseBook.PersonCode is :{japaneseBook.PersonCode ?? "null"}");//很有意思的是我们上面在chineseBook上调用DbContext.Entry()方法后,japaneseBook的PersonCode属性也不为null了,变为了P001,说明调用一次DbContext.Entry()方法后,会引发DbContext重新检查所有被跟踪实体的状态
- Console.WriteLine($"After querying DbContext.Entry(japaneseBook), japaneseBook entity state is :{dbContext.Entry(japaneseBook).State.ToString()}");//在上面为chineseBook调用DbContext.Entry()方法时,DbContext同时发现了原本状态是Unchanged的Book实体japaneseBook,也被加入到了Person.Book集合中,所以japaneseBook的实体状态也变为了Modified
- Console.WriteLine($"After querying DbContext.Entry(japaneseBook), japaneseBook.PersonCode is :{japaneseBook.PersonCode}");//在上面为chineseBook调用DbContext.Entry()方法时,DbContext得知了japaneseBook也被加入到了Person.Book集合中,所以DbContext将japaneseBook的PersonCode属性也赋值为P001了
- Console.WriteLine();
- Console.WriteLine($"Berfore querying DbContext.Entry(englishBook),englishBook.PersonCode is :{englishBook.PersonCode ?? "null"}");//在上面为chineseBook调用DbContext.Entry()方法时,DbContext得知了englishBook也被加入到了Person.Book集合中,所以DbContext将englishBook的PersonCode属性也赋值为P001了
- Console.WriteLine($"After querying DbContext.Entry(englishBook), englishBook entity state is :{dbContext.Entry(englishBook).State.ToString()}");//在上面为chineseBook调用DbContext.Entry()方法时,DbContext同时发现了原本状态是Unchanged的Book实体englishBook,也被加入到了Person.Book集合中,所以englishBook的实体状态也变为了Modified
- Console.WriteLine($"After querying DbContext.Entry(englishBook), englishBook.PersonCode is :{englishBook.PersonCode}");//在上面为chineseBook调用DbContext.Entry()方法时,DbContext得知了englishBook也被加入到了Person.Book集合中,所以DbContext将englishBook的PersonCode属性也赋值为P001了
- dbContext.SaveChanges();//由于此时chineseBook、japaneseBook和englishBook的EntityState都是Modified,所以此时DbContext.SaveChanges方法调用后,EF Core生成的是UPDATE语句,通过更新数据库Book表的PersonCode列,将Chinese、Japanese和English三行Book数据同Person表的数据成功关联了起来
- }
- }
- static void Main(string[] args)
- {
- DeleteAllData();//调用DeleteAllData方法删除Person表和Book表的所有数据,防止有脏数据
- InitData();//初始化Person表和Book表的数据
- SetRelationshipCorrectly();//正确地的设置Person表和Book表的关联关系
- SetRelationshipIncorrectly();//不正确地的设置Person表和Book表的关联关系,该方法会抛出异常错误
- Console.WriteLine("Press any key to quit...");
- Console.ReadKey();
- }
- }
示例代码中的DeleteAllData方法,是清表语句,用来删除Person表和Book表的所有数据,防止有脏数据。
InitData方法用来初始化Person表和Book表的数据,Person表插入了一行数据,Book表插入了三行数据且PersonCode列都为NULL,调用InitData方法后数据库Person表和Book表的数据就和上面示例代码前的两个截图相同了。
测试SetRelationshipIncorrectly方法
先将示例代码的Main方法改为如下:
- static void Main(string[] args)
- {
- DeleteAllData();//调用DeleteAllData方法删除Person表和Book表的所有数据,防止有脏数据
- InitData();//初始化Person表和Book表的数据
- //SetRelationshipCorrectly();//正确地的设置Person表和Book表的关联关系
- SetRelationshipIncorrectly();//不正确地的设置Person表和Book表的关联关系,该方法会抛出异常错误
- Console.WriteLine("Press any key to quit...");
- Console.ReadKey();
- }
SetRelationshipIncorrectly方法用来演示怎么错误地设置Person表和Book表的关联关系,可以看到由于我们在其中新建的三个Book实体
- 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
最终在调用DbContext.SaveChanges方法时其实体状态都是Added,所以调用DbContext.SaveChanges方法时,EF Core在数据库中生成的是INSERT语句,尝试将这三个实体数据插入数据库Book表,由于调用InitData方法后,数据库Book表中已经有相同PersonCode列值的数据了,Book表的PersonCode列又是唯一键,所以DbContext.SaveChanges方法抛出异常。
我们可以从EF Core的后台日志中,查看到调用DbContext.SaveChanges方法时生成的是INSERT语句:
- =============================== EF Core log started ===============================
- SaveChanges starting for 'TestDBContext'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- DetectChanges starting for 'TestDBContext'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- DetectChanges completed for 'TestDBContext'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Opening connection to database 'TestDB' on server 'localhost'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Opened connection to database 'TestDB' on server 'localhost'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Beginning transaction with isolation level 'ReadCommitted'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Executing update commands individually as the number of batchable commands (3) is smaller than the minimum batch size (4).
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Executing DbCommand [Parameters=[@p0='?' (Size = 20), @p1='?' (Size = 50), @p2='?' (Size = 20), @p3='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
- SET NOCOUNT ON;
- INSERT INTO [Book] ([BookCode], [BookName], [ISBN], [PersonCode])
- VALUES (@p0, @p1, @p2, @p3);
- SELECT [ID]
- FROM [Book]
- WHERE @@ROWCOUNT = 1 AND [BookCode] = @p0;
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Failed executing DbCommand (8ms) [Parameters=[@p0='?' (Size = 20), @p1='?' (Size = 50), @p2='?' (Size = 20), @p3='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
- SET NOCOUNT ON;
- INSERT INTO [Book] ([BookCode], [BookName], [ISBN], [PersonCode])
- VALUES (@p0, @p1, @p2, @p3);
- SELECT [ID]
- FROM [Book]
- WHERE @@ROWCOUNT = 1 AND [BookCode] = @p0;
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Disposing transaction.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Closing connection to database 'TestDB' on server 'localhost'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Closed connection to database 'TestDB' on server 'localhost'.
- =============================== EF Core log finished ===============================
在SetRelationshipIncorrectly方法中我们还输出了chineseBook、japaneseBook和englishBook三个Book实体的EntityState,可以看到将chineseBook、japaneseBook和englishBook三个Book实体添加到Person类的Book实体集合后,EntityState发生了相应的变化。
测试SetRelationshipCorrectly方法
先将示例代码的Main方法改为如下:
- static void Main(string[] args)
- {
- DeleteAllData();//调用DeleteAllData方法删除Person表和Book表的所有数据,防止有脏数据
- InitData();//初始化Person表和Book表的数据
- SetRelationshipCorrectly();//正确地的设置Person表和Book表的关联关系
- //SetRelationshipIncorrectly();//不正确地的设置Person表和Book表的关联关系,该方法会抛出异常错误
- Console.WriteLine("Press any key to quit...");
- Console.ReadKey();
- }
SetRelationshipCorrectly方法用来演示如何正确地设置Person表和Book表的关联关系,首先我们将
- 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
这三个新建的Book实体Attach到了DbContext,如下所示:
- dbContext.Attach(chineseBook);//将chineseBook关联到DbContext,开始跟踪
- dbContext.Attach(japaneseBook);//将japaneseBook关联到DbContext,开始跟踪
- dbContext.Attach(englishBook);//将englishBook关联到DbContext,开始跟踪
所以这次最终在调用DbContext.SaveChanges方法时其实体状态都是Modified,那么调用DbContext.SaveChanges方法时,EF Core在数据库中生成的是UPDATE语句,正确地将chineseBook、japaneseBook和englishBook这三个Book实体的PersonCode列值更新到了数据库Book表中,我们可以从EF Core的后台日志中,查看到调用DbContext.SaveChanges方法时生成的是UPDATE语句:
- =============================== EF Core log started ===============================
- SaveChanges starting for 'TestDBContext'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- DetectChanges starting for 'TestDBContext'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- DetectChanges completed for 'TestDBContext'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Opening connection to database 'TestDB' on server 'localhost'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Opened connection to database 'TestDB' on server 'localhost'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Beginning transaction with isolation level 'ReadCommitted'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Executing update commands individually as the number of batchable commands (3) is smaller than the minimum batch size (4).
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Executing DbCommand [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
- SET NOCOUNT ON;
- UPDATE [Book] SET [PersonCode] = @p0
- WHERE [BookCode] = @p1;
- SELECT @@ROWCOUNT;
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Executed DbCommand (4ms) [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
- SET NOCOUNT ON;
- UPDATE [Book] SET [PersonCode] = @p0
- WHERE [BookCode] = @p1;
- SELECT @@ROWCOUNT;
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- A data reader was disposed.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Executing DbCommand [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
- SET NOCOUNT ON;
- UPDATE [Book] SET [PersonCode] = @p0
- WHERE [BookCode] = @p1;
- SELECT @@ROWCOUNT;
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Executed DbCommand (4ms) [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
- SET NOCOUNT ON;
- UPDATE [Book] SET [PersonCode] = @p0
- WHERE [BookCode] = @p1;
- SELECT @@ROWCOUNT;
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- A data reader was disposed.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Executing DbCommand [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
- SET NOCOUNT ON;
- UPDATE [Book] SET [PersonCode] = @p0
- WHERE [BookCode] = @p1;
- SELECT @@ROWCOUNT;
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Executed DbCommand (4ms) [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
- SET NOCOUNT ON;
- UPDATE [Book] SET [PersonCode] = @p0
- WHERE [BookCode] = @p1;
- SELECT @@ROWCOUNT;
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- A data reader was disposed.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Committing transaction.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Closing connection to database 'TestDB' on server 'localhost'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Closed connection to database 'TestDB' on server 'localhost'.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- Disposing transaction.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- An 'Book' entity tracked by 'TestDBContext' changed from 'Modified' to 'Unchanged'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- An 'Book' entity tracked by 'TestDBContext' changed from 'Modified' to 'Unchanged'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- An 'Book' entity tracked by 'TestDBContext' changed from 'Modified' to 'Unchanged'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
- =============================== EF Core log finished ===============================
- =============================== EF Core log started ===============================
- SaveChanges completed for 'TestDBContext' with 3 entities written to the database.
- =============================== EF Core log finished ===============================
最终数据库中Person表和Book表的数据如下所示:
Person表
Book表
在SetRelationshipCorrectly方法中我们也输出了chineseBook、japaneseBook和englishBook三个Book实体的EntityState,可以看到这三个Book实体的EntityState发生的变化
DbContext何时会检查被跟踪实体的状态?
从目前得知的情况,调用DbContext.Entry方法和DbContext.SaveChanges方法时,都会触发DbContext对所有被跟踪实体的重新检查,从而更新被跟踪实体的EntityState和相关实体属性。在EF Core中应该还有其它方法也会触发DbContext重新检查被跟踪实体的状态,后面发现后再做补充。
更正
本文中示例代码中的SetRelationshipIncorrectly和SetRelationshipCorrectly两个方法,有一段描述有点问题,如下:
- james.Book = new List<Book>();//由于我们在上面调用dbContext.Person.First(e => e.Name == "James")时,没有用EF Core中Eager Loading的Include方法来加载Book实体集合,所以这里要用List类来构造一个Book实体集合,否则james.Book为null
其实我发现在EF Core中就算没开启Lazy Loading和没使用Eager Loading的Include方法时,使用DbContext做查询后(例如上面代码中的james就来自查询dbContext.Person.First(e => e.Name == "James")),也会给集合导航属性赋值为System.Collections.Generic.HashSet<T>,只不过其长度(Count)为0。
所以上面这段代码给james.Book赋值其实是没有必要的,因为james.Book本来就不为null,后面直接调用james.Book的Add方法即可。只不过貌似给james.Book赋值为new List<Book>()也没有什么问题,只是没有必要而已。
EF Core中如何正确地设置两张表之间的关联关系的更多相关文章
- 一起学Hadoop——实现两张表之间的连接操作
---恢复内容开始--- 之前我们都是学习使用MapReduce处理一张表的数据(一个文件可视为一张表,hive和关系型数据库Mysql.Oracle等都是将数据存储在文件中).但是我们经常会遇到处理 ...
- mysql 如何找出两张表之间的关系
分析步骤: #1.先站在左表的角度去找 是否左表的多条记录可以对应右表的一条记录,如果是,则证明左表的一个字段foreign key 右表一个字段(通常是id) #2.再站在右表的角度去找 是否右表的 ...
- EF Core中Fluent Api如何删除指定数据表中的行
这两天一直在研究在code first下如何删除数据表中的指定行,于是开始搜狗,后来百度,压根就找不到资料,后来一想可能我的搜索关键字有问题,而且ef core命令与ef的命令差不多,于是从这两个方面 ...
- JS之document例题讲解1(两张表之间数据转移、日期时间选择、子菜单下拉、用div做下拉菜单、事件总结)
作业一:两个列表之间数据从一个列表移动到另一个列表 <div style="width:600px; height:500px; margin-top:20px"> & ...
- EF Core中怎么实现自动更新实体的属性值到数据库
我们在开发系统的时候,经常会遇到这种需求数据库表中的行被更新时需要自动更新某些列. 数据库 比如下面的Person表有一列UpdateTime,这列数据要求在行被更新后自动更新为系统的当前时间. Pe ...
- EF Core中如何通过实体集合属性删除从表的数据
假设在数据库中有两个表:Person表和Book表,Person和Book是一对多关系 Person表数据: Book表数据: 可以看到数据库Book表中所有的数据都属于Person表中"F ...
- 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获
项目开发中的一些注意事项以及技巧总结 1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...
- EF Core中避免贫血模型的三种行之有效的方法(翻译)
Paul Hiles: 3 ways to avoid an anemic domain model in EF Core 1.引言 在使用ORM中(比如Entity Framework)贫血领域模型 ...
- EF Core 中DbContext不会跟踪聚合方法和Join方法返回的结果,及FromSql方法使用讲解
EF Core中: 如果调用Queryable.Count等聚合方法,不会导致DbContext跟踪(track)任何实体. 此外调用Queryable.Join方法返回的匿名类型也不会被DbCont ...
随机推荐
- 分数规划(Bzoj1486: [HNOI2009]最小圈)
题面 传送门 分数规划 分数规划有什么用? 可以把带分数的最优性求解式化成不带除发的运算 假设求max{\(\frac{a}{b},b>0\)} 二分一个权值\(k\) 令\(\frac{a}{ ...
- Sublime Text3之安裝Emmet及使用技巧
首先准备工作: 如果你的Sublime Text3没有Package Control组件先看一下这里,如果以安装请忽略: 1.按Ctrl+`调出sublime text的console 2.粘贴以下代 ...
- 【MySQL数据库】一些bug的解决
往往碰到mysql配置好后,第二天就登不上,也运行不了服务. 在cmd中输入 net start mysql 报mysql无法启动系统错误1067等 解决方案: 以前搞到最后没办法,重装了,后来找到 ...
- HTML5 Canvas中绘制椭圆的几种方法
1.canvas自带的绘制椭圆的方法 ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)是后来 ...
- Node.js 的安装
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 的运行环境,简单的说就是运行在服务端的 JavaScript.所以学起来还是比较容易接受的. Node.js 使用事件驱动 ...
- MUI框架-13-使用百度地图 API(图文教程)
MUI框架-13-使用百度地图 API(图文教程) 后面有实例,转载请注明出处 一.申请百度地图权限 1.打开 百度地图开放平台:http://lbsyun.baidu.com/apiconsole/ ...
- c# 调用 c dll 例子
// case 1 传递 int* ///////////////////////////////////////////// extern “C” __declspec(dllexport) int ...
- Java BigDecimal初探
更新时间:2016-03-17 一.引言 <Effactive Java>中有这样的描述:float和double类型的主要设计目标是为了科学计算和工程计算.他们执行二进制浮点运算,这是为 ...
- Android开发精彩博文收藏——UI界面类
本文收集整理Android开发中关于UI界面的相关精华博文,共大家参考!本文不定期更新! 1. Android使用Fragment来实现TabHost的功能(解决切换Fragment状态不保存)以及各 ...
- vs 编译error1083
1)右键查看该项目的属性 2)点击配置属性——〉 C/C++ ——〉 常规 ——〉 附加包含目录——〉将报错文件所在目录添加进去 3) 将项目的本地路径替换为工程相对路径 一般来说,打不开文件 ...