數據庫
假設現在我們在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 }); } }
示例代碼
現在我們來設想下面一個場景:
假設數據庫中的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()) { 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>()也沒有什么問題,只是沒有必要而已。