数据库


假设现在我们在SQL Server数据库中有下面两张表:

Person表,代表的是一个人:

  1. CREATE TABLE [dbo].[Person](
  2. [ID] [int] IDENTITY(1,1) NOT NULL,
  3. [PersonCode] [nvarchar](20) NULL,
  4. [Name] [nvarchar](50) NULL,
  5. [Age] [int] NULL,
  6. CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED
  7. (
  8. [ID] ASC
  9. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
  10. CONSTRAINT [IX_Person] UNIQUE NONCLUSTERED
  11. (
  12. [PersonCode] ASC
  13. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  14. ) ON [PRIMARY]
  15. GO

其主键是ID,而且主键是自增列。Person表还有个PersonCode列是唯一键,然后Name和Age列用来描述一个人的名字和年龄。

Book表,代表的是一本书:

  1. CREATE TABLE [dbo].[Book](
  2. [ID] [int] IDENTITY(1,1) NOT NULL,
  3. [BookCode] [nvarchar](20) NULL,
  4. [PersonCode] [nvarchar](20) NULL,
  5. [BookName] [nvarchar](50) NULL,
  6. [ISBN] [nvarchar](20) NULL,
  7. CONSTRAINT [PK_Book] PRIMARY KEY CLUSTERED
  8. (
  9. [ID] ASC
  10. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
  11. CONSTRAINT [IX_Book] UNIQUE NONCLUSTERED
  12. (
  13. [BookCode] ASC
  14. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  15. ) ON [PRIMARY]
  16. 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实体:

  1. public partial class Person
  2. {
  3. public int Id { get; set; }
  4. public string PersonCode { get; set; }
  5. public string Name { get; set; }
  6. public int? Age { get; set; }
  7.  
  8. //通过Person实体的Book属性,可以找到多个Book实体,说明Person表是一对多关系中的主表
  9. public virtual ICollection<Book> Book { get; set; }
  10. }

Book实体,对应数据库的Book表,其属性Person是一个Person实体,表示一个Book实体只能找到一个Person实体:

  1. public partial class Book
  2. {
  3. public int Id { get; set; }
  4. public string BookCode { get; set; }
  5. public string PersonCode { get; set; }
  6. public string BookName { get; set; }
  7. public string Isbn { get; set; }
  8.  
  9. //通过Book实体的Person属性,可以找到一个Person实体,说明Book表是一对多关系中的从表
  10. public virtual Person Person { get; set; }
  11. }

然后是继承DbContext的TestDBContext类,其中最重要的地方是OnModelCreating方法中设置Person实体和Book实体一对多关系的Fluent API,每一行都写明了注释:

  1. public partial class TestDBContext : DbContext
  2. {
  3. public TestDBContext()
  4. {
  5. }
  6.  
  7. public TestDBContext(DbContextOptions<TestDBContext> options)
  8. : base(options)
  9. {
  10. }
  11.  
  12. public virtual DbSet<Book> Book { get; set; }
  13. public virtual DbSet<Person> Person { get; set; }
  14.  
  15. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  16. {
  17. if (!optionsBuilder.IsConfigured)
  18. {
  19. optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=Dtt!123456;Database=TestDB");
  20. }
  21. }
  22.  
  23. protected override void OnModelCreating(ModelBuilder modelBuilder)
  24. {
  25. modelBuilder.Entity<Book>(entity =>
  26. {
  27. entity.HasKey(e => e.BookCode);//设置Book实体的BookCode属性为EF Core实体的Key属性
  28.  
  29. entity.HasIndex(e => e.BookCode)
  30. .HasName("IX_Book")
  31. .IsUnique();
  32.  
  33. entity.Property(e => e.Id).ValueGeneratedOnAdd();//设置Book实体的Id属性为插入数据到数据库Book表时自动生成,因为Book表的ID列为自增列
  34. entity.Property(e => e.Id).HasColumnName("ID");
  35.  
  36. entity.Property(e => e.BookCode).HasMaxLength();
  37.  
  38. entity.Property(e => e.BookName).HasMaxLength();
  39.  
  40. entity.Property(e => e.Isbn)
  41. .HasColumnName("ISBN")
  42. .HasMaxLength();
  43.  
  44. entity.Property(e => e.PersonCode).HasMaxLength();
  45. });
  46.  
  47. modelBuilder.Entity<Person>(entity =>
  48. {
  49. entity.HasKey(e => e.PersonCode);//设置Person实体的PersonCode属性为EF Core实体的Key属性
  50.  
  51. entity.HasIndex(e => e.PersonCode)
  52. .HasName("IX_Person")
  53. .IsUnique();
  54.  
  55. entity.Property(e => e.Id).ValueGeneratedOnAdd();//设置Person实体的Id属性为插入数据到数据库Person表时自动生成,因为Person表的ID列为自增列
  56. entity.Property(e => e.Id).HasColumnName("ID");
  57.  
  58. entity.Property(e => e.Name).HasMaxLength();
  59.  
  60. entity.Property(e => e.PersonCode).HasMaxLength();
  61.  
  62. //设置Person实体和Book实体之间的一对多关系,尽管我们并没有在数据库中建立Person表和Book表之间的一对多外键关系,但是我们可以用EF Core的Fluent API在实体层面设置外键关系
  63. entity.HasMany(p => p.Book)//设置Person实体通过属性Book可以找到多个Book实体,表示Person表是一对多关系中的主表
  64. .WithOne(b => b.Person)//设置Book实体通过属性Person可以找到一个Person实体,表示Book表是一对多关系中的从表
  65. .HasPrincipalKey(p => p.PersonCode)//设置Person表的PersonCode列为一对多关系中的主表键
  66. .HasForeignKey(b => b.PersonCode)//设置Book表的PersonCode列为一对多关系中的从表外键
  67. .OnDelete(DeleteBehavior.ClientSetNull);//设置一对多关系的级联删除效果为DeleteBehavior.ClientSetNull
  68.  
  69. });
  70. }
  71. }

示例代码


现在我们来设想下面一个场景:

假设数据库中的Person表有一行数据如下:

数据库中的Book表有三行数据如下:

可以看到Book表三行数据的PersonCode列都为NULL,那么我们怎么在EF Core中更改Book表三行数据的PersonCode列为Person表的PersonCode列值呢?也就是说将Book表三行数据的PersonCode列都改为Person表的值P001,从而表示James这个人拥有三本书。

本例的示例代码都写在了.NET Core控制台项目的Program类中,这里先将代码全部贴出来:

  1. class Program
  2. {
  3. /// <summary>
  4. /// 初始化Person表和Book表的数据,没有设置Book表的外键列PersonCode的值
  5. /// </summary>
  6. static void InitData()
  7. {
  8. //初始化数据库数据
  9. using (var dbContext = new TestDBContext())
  10. {
  11. var james = new Person() { PersonCode = "P001", Name = "James", Age = };
  12.  
  13. dbContext.Person.Add(james);
  14.  
  15. var chineseBook = new Book() { BookCode = "B001", Isbn = "", BookName = "Chinese" };//没有设置Book表中外键列PersonCode的值
  16. var japaneseBook = new Book() { BookCode = "B002", Isbn = "", BookName = "Japanese" };//没有设置Book表中外键列PersonCode的值
  17. var englishBook = new Book() { BookCode = "B003", Isbn = "", BookName = "English" };//没有设置Book表中外键列PersonCode的值
  18.  
  19. //插入三条数据到Book表
  20. dbContext.Book.Add(chineseBook);
  21. dbContext.Book.Add(japaneseBook);
  22. dbContext.Book.Add(englishBook);
  23.  
  24. dbContext.SaveChanges();
  25. }
  26. }
  27.  
  28. /// <summary>
  29. /// 删除Person表和Book表的所有数据
  30. /// </summary>
  31. static void DeleteAllData()
  32. {
  33. using (var dbContext = new TestDBContext())
  34. {
  35. dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Book]");
  36. dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Person]");
  37. }
  38. }
  39.  
  40. /// <summary>
  41. /// 不正确地设置Person表和Book表的关联关系,这种方法会让EF Core错误地生成INSERT语句,而不是UPDATE语句
  42. /// </summary>
  43. static void SetRelationshipIncorrectly()
  44. {
  45. using (var dbContext = new TestDBContext())
  46. {
  47. var james = dbContext.Person.First(e => e.Name == "James");//首先通过DbContext从数据库中查询出要建立关联关系的Person表实体
  48.  
  49. var chineseBook = new Book() { BookCode = "B001" };//只构造Book实体的Key属性即可,根据BookCode值"B001"来构造Chinese Book
  50. var japaneseBook = new Book() { BookCode = "B002" };//只构造Book实体的Key属性即可,根据BookCode值"B002"来构造Japanese Book
  51. var englishBook = new Book() { BookCode = "B003" };//只构造Book实体的Key属性即可,根据BookCode值"B003"来构造English Book
  52.  
  53. Console.WriteLine($"Before adding, chineseBook entity state is :{dbContext.Entry(chineseBook).State.ToString()}");//可以看到由于此时Book实体chineseBook没有被DbContext跟踪,所以状态是Detached
  54. Console.WriteLine($"Before adding, japaneseBook entity state is :{dbContext.Entry(japaneseBook).State.ToString()}");//可以看到由于此时Book实体japaneseBook没有被DbContext跟踪,所以状态是Detached
  55. Console.WriteLine($"Before adding, englishBook entity state is :{dbContext.Entry(englishBook).State.ToString()}");//可以看到由于此时Book实体englishBook没有被DbContext跟踪,所以状态是Detached
  56.  
  57. Console.WriteLine();
  58.  
  59. james.Book = new List<Book>();//由于我们在上面调用dbContext.Person.First(e => e.Name == "James")时,没有用EF Core中Eager Loading的Include方法来加载Book实体集合,所以这里要用List类来构造一个Book实体集合,否则james.Book为null
  60.  
  61. james.Book.Add(chineseBook);//添加chineseBook到Person类的Book实体集合
  62. Console.WriteLine("chineseBook was added into Person.Book collection");
  63.  
  64. james.Book.Add(japaneseBook);//添加japaneseBook到Person类的Book实体集合
  65. Console.WriteLine("japaneseBook was added into Person.Book collection");
  66.  
  67. james.Book.Add(englishBook);//添加englishBook到Person类的Book实体集合
  68. Console.WriteLine("englishBook was added into Person.Book collection");
  69.  
  70. Console.WriteLine();
  71.  
  72. 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
  73. 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
  74. 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
  75.  
  76. dbContext.SaveChanges();//由于此时chineseBook、japaneseBook和englishBook的EntityState都是Added,所以此时DbContext.SaveChanges方法调用后,EF Core生成的是INSERT语句,将chineseBook、japaneseBook和englishBook插入数据库表Book,导致插入了重复值到唯一键列BookCode,所以数据库报错
  77. }
  78. }
  79.  
  80. /// <summary>
  81. /// 正确地设置Person表和Book表的关联关系,这种方法会让EF Core正确地生成UPDATE语句,在数据库中设置Book表的PersonCode列数据
  82. /// </summary>
  83. static void SetRelationshipCorrectly()
  84. {
  85. using (var dbContext = new TestDBContext())
  86. {
  87. var james = dbContext.Person.First(e => e.Name == "James");//首先通过DbContext从数据库中查询出要建立关联关系的Person表实体
  88.  
  89. var chineseBook = new Book() { BookCode = "B001" };//只构造Book实体的Key属性即可,根据BookCode值"B001"来构造Chinese Book
  90. var japaneseBook = new Book() { BookCode = "B002" };//只构造Book实体的Key属性即可,根据BookCode值"B002"来构造Japanese Book
  91. var englishBook = new Book() { BookCode = "B003" };//只构造Book实体的Key属性即可,根据BookCode值"B003"来构造English Book
  92.  
  93. dbContext.Attach(chineseBook);//将chineseBook关联到DbContext,开始跟踪
  94. dbContext.Attach(japaneseBook);//将japaneseBook关联到DbContext,开始跟踪
  95. dbContext.Attach(englishBook);//将englishBook关联到DbContext,开始跟踪
  96.  
  97. Console.WriteLine($"After querying DbContext.Entry(chineseBook), chineseBook entity state is :{dbContext.Entry(chineseBook).State.ToString()}");//由于上面chineseBook被Attach到DbContext开始跟踪了,所以此时chineseBook的实体状态是Unchanged
  98. Console.WriteLine($"After querying DbContext.Entry(japaneseBook), japaneseBook entity state is :{dbContext.Entry(japaneseBook).State.ToString()}");//由于上面japaneseBook被Attach到DbContext开始跟踪了,所以此时japaneseBook的实体状态是Unchanged
  99. Console.WriteLine($"After querying DbContext.Entry(englishBook), englishBook entity state is :{dbContext.Entry(englishBook).State.ToString()}");//由于上面englishBook被Attach到DbContext开始跟踪了,所以此时englishBook的实体状态是Unchanged
  100.  
  101. Console.WriteLine();
  102.  
  103. james.Book = new List<Book>();//由于我们在上面调用dbContext.Person.First(e => e.Name == "James")时,没有用EF Core中Eager Loading的Include方法来加载Book实体集合,所以这里要用List类来构造一个Book实体集合,否则james.Book为null
  104.  
  105. james.Book.Add(chineseBook);//添加chineseBook到Person类的Book实体集合
  106. Console.WriteLine("chineseBook was added into Person.Book collection");
  107.  
  108. james.Book.Add(japaneseBook);//添加japaneseBook到Person类的Book实体集合
  109. Console.WriteLine("japaneseBook was added into Person.Book collection");
  110.  
  111. james.Book.Add(englishBook);//添加englishBook到Person类的Book实体集合
  112. Console.WriteLine("englishBook was added into Person.Book collection");
  113.  
  114. Console.WriteLine();
  115.  
  116. Console.WriteLine($"Berfore querying DbContext.Entry(chineseBook), chineseBook.PersonCode is :{chineseBook.PersonCode ?? "null"}");//此时由于我们还没有调用DbContext.Entry()方法,所以DbContext还无法察觉到chineseBook已经被添加到Person类的Book实体集合了,所以chineseBook.PersonCode为null
  117. 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
  118. Console.WriteLine($"After querying DbContext.Entry(chineseBook), chineseBook.PersonCode is :{chineseBook.PersonCode}");//由于上面我们调用DbContext.Entry(chineseBook)使得DbContext得知了chineseBook被加入到Person.Book集合中了,所以DbContext还将Book实体的外键属性PersonCode也进行了赋值,为P001
  119.  
  120. Console.WriteLine();
  121.  
  122. Console.WriteLine($"Berfore querying DbContext.Entry(japaneseBook),japaneseBook.PersonCode is :{japaneseBook.PersonCode ?? "null"}");//很有意思的是我们上面在chineseBook上调用DbContext.Entry()方法后,japaneseBook的PersonCode属性也不为null了,变为了P001,说明调用一次DbContext.Entry()方法后,会引发DbContext重新检查所有被跟踪实体的状态
  123. 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
  124. Console.WriteLine($"After querying DbContext.Entry(japaneseBook), japaneseBook.PersonCode is :{japaneseBook.PersonCode}");//在上面为chineseBook调用DbContext.Entry()方法时,DbContext得知了japaneseBook也被加入到了Person.Book集合中,所以DbContext将japaneseBook的PersonCode属性也赋值为P001了
  125.  
  126. Console.WriteLine();
  127.  
  128. Console.WriteLine($"Berfore querying DbContext.Entry(englishBook),englishBook.PersonCode is :{englishBook.PersonCode ?? "null"}");//在上面为chineseBook调用DbContext.Entry()方法时,DbContext得知了englishBook也被加入到了Person.Book集合中,所以DbContext将englishBook的PersonCode属性也赋值为P001了
  129. 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
  130. Console.WriteLine($"After querying DbContext.Entry(englishBook), englishBook.PersonCode is :{englishBook.PersonCode}");//在上面为chineseBook调用DbContext.Entry()方法时,DbContext得知了englishBook也被加入到了Person.Book集合中,所以DbContext将englishBook的PersonCode属性也赋值为P001了
  131.  
  132. dbContext.SaveChanges();//由于此时chineseBook、japaneseBook和englishBook的EntityState都是Modified,所以此时DbContext.SaveChanges方法调用后,EF Core生成的是UPDATE语句,通过更新数据库Book表的PersonCode列,将Chinese、Japanese和English三行Book数据同Person表的数据成功关联了起来
  133. }
  134. }
  135.  
  136. static void Main(string[] args)
  137. {
  138. DeleteAllData();//调用DeleteAllData方法删除Person表和Book表的所有数据,防止有脏数据
  139. InitData();//初始化Person表和Book表的数据
  140.  
  141. SetRelationshipCorrectly();//正确地的设置Person表和Book表的关联关系
  142. SetRelationshipIncorrectly();//不正确地的设置Person表和Book表的关联关系,该方法会抛出异常错误
  143.  
  144. Console.WriteLine("Press any key to quit...");
  145. Console.ReadKey();
  146. }
  147. }

示例代码中的DeleteAllData方法,是清表语句,用来删除Person表和Book表的所有数据,防止有脏数据。

InitData方法用来初始化Person表和Book表的数据,Person表插入了一行数据,Book表插入了三行数据且PersonCode列都为NULL,调用InitData方法后数据库Person表和Book表的数据就和上面示例代码前的两个截图相同了。

测试SetRelationshipIncorrectly方法

先将示例代码的Main方法改为如下:

  1. static void Main(string[] args)
  2. {
  3. DeleteAllData();//调用DeleteAllData方法删除Person表和Book表的所有数据,防止有脏数据
  4. InitData();//初始化Person表和Book表的数据
  5.  
  6. //SetRelationshipCorrectly();//正确地的设置Person表和Book表的关联关系
  7. SetRelationshipIncorrectly();//不正确地的设置Person表和Book表的关联关系,该方法会抛出异常错误
  8.  
  9. Console.WriteLine("Press any key to quit...");
  10. Console.ReadKey();
  11. }

SetRelationshipIncorrectly方法用来演示怎么错误地设置Person表和Book表的关联关系,可以看到由于我们在其中新建的三个Book实体

  1. var chineseBook = new Book() { BookCode = "B001" };//只构造Book实体的Key属性即可,根据BookCode值"B001"来构造Chinese Book
  2. var japaneseBook = new Book() { BookCode = "B002" };//只构造Book实体的Key属性即可,根据BookCode值"B002"来构造Japanese Book
  3. 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语句:

  1. =============================== EF Core log started ===============================
  2. SaveChanges starting for 'TestDBContext'.
  3. =============================== EF Core log finished ===============================
  4. =============================== EF Core log started ===============================
  5. DetectChanges starting for 'TestDBContext'.
  6. =============================== EF Core log finished ===============================
  7. =============================== EF Core log started ===============================
  8. DetectChanges completed for 'TestDBContext'.
  9. =============================== EF Core log finished ===============================
  10. =============================== EF Core log started ===============================
  11. Opening connection to database 'TestDB' on server 'localhost'.
  12. =============================== EF Core log finished ===============================
  13. =============================== EF Core log started ===============================
  14. Opened connection to database 'TestDB' on server 'localhost'.
  15. =============================== EF Core log finished ===============================
  16. =============================== EF Core log started ===============================
  17. Beginning transaction with isolation level 'ReadCommitted'.
  18. =============================== EF Core log finished ===============================
  19. =============================== EF Core log started ===============================
  20. Executing update commands individually as the number of batchable commands (3) is smaller than the minimum batch size (4).
  21. =============================== EF Core log finished ===============================
  22. =============================== EF Core log started ===============================
  23. Executing DbCommand [Parameters=[@p0='?' (Size = 20), @p1='?' (Size = 50), @p2='?' (Size = 20), @p3='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
  24. SET NOCOUNT ON;
  25. INSERT INTO [Book] ([BookCode], [BookName], [ISBN], [PersonCode])
  26. VALUES (@p0, @p1, @p2, @p3);
  27. SELECT [ID]
  28. FROM [Book]
  29. WHERE @@ROWCOUNT = 1 AND [BookCode] = @p0;
  30. =============================== EF Core log finished ===============================
  31. =============================== EF Core log started ===============================
  32. Failed executing DbCommand (8ms) [Parameters=[@p0='?' (Size = 20), @p1='?' (Size = 50), @p2='?' (Size = 20), @p3='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
  33. SET NOCOUNT ON;
  34. INSERT INTO [Book] ([BookCode], [BookName], [ISBN], [PersonCode])
  35. VALUES (@p0, @p1, @p2, @p3);
  36. SELECT [ID]
  37. FROM [Book]
  38. WHERE @@ROWCOUNT = 1 AND [BookCode] = @p0;
  39. =============================== EF Core log finished ===============================
  40. =============================== EF Core log started ===============================
  41. Disposing transaction.
  42. =============================== EF Core log finished ===============================
  43. =============================== EF Core log started ===============================
  44. Closing connection to database 'TestDB' on server 'localhost'.
  45. =============================== EF Core log finished ===============================
  46. =============================== EF Core log started ===============================
  47. Closed connection to database 'TestDB' on server 'localhost'.
  48. =============================== EF Core log finished ===============================

在SetRelationshipIncorrectly方法中我们还输出了chineseBook、japaneseBook和englishBook三个Book实体的EntityState,可以看到将chineseBook、japaneseBook和englishBook三个Book实体添加到Person类的Book实体集合后,EntityState发生了相应的变化。

测试SetRelationshipCorrectly方法

先将示例代码的Main方法改为如下:

  1. static void Main(string[] args)
  2. {
  3. DeleteAllData();//调用DeleteAllData方法删除Person表和Book表的所有数据,防止有脏数据
  4. InitData();//初始化Person表和Book表的数据
  5.  
  6. SetRelationshipCorrectly();//正确地的设置Person表和Book表的关联关系
  7. //SetRelationshipIncorrectly();//不正确地的设置Person表和Book表的关联关系,该方法会抛出异常错误
  8.  
  9. Console.WriteLine("Press any key to quit...");
  10. Console.ReadKey();
  11. }

SetRelationshipCorrectly方法用来演示如何正确地设置Person表和Book表的关联关系,首先我们将

  1. var chineseBook = new Book() { BookCode = "B001" };//只构造Book实体的Key属性即可,根据BookCode值"B001"来构造Chinese Book
  2. var japaneseBook = new Book() { BookCode = "B002" };//只构造Book实体的Key属性即可,根据BookCode值"B002"来构造Japanese Book
  3. var englishBook = new Book() { BookCode = "B003" };//只构造Book实体的Key属性即可,根据BookCode值"B003"来构造English Book

这三个新建的Book实体Attach到了DbContext,如下所示:

  1. dbContext.Attach(chineseBook);//将chineseBook关联到DbContext,开始跟踪
  2. dbContext.Attach(japaneseBook);//将japaneseBook关联到DbContext,开始跟踪
  3. dbContext.Attach(englishBook);//将englishBook关联到DbContext,开始跟踪

所以这次最终在调用DbContext.SaveChanges方法时其实体状态都是Modified,那么调用DbContext.SaveChanges方法时,EF Core在数据库中生成的是UPDATE语句,正确地将chineseBook、japaneseBook和englishBook这三个Book实体的PersonCode列值更新到了数据库Book表中,我们可以从EF Core的后台日志中,查看到调用DbContext.SaveChanges方法时生成的是UPDATE语句:

  1. =============================== EF Core log started ===============================
  2. SaveChanges starting for 'TestDBContext'.
  3. =============================== EF Core log finished ===============================
  4. =============================== EF Core log started ===============================
  5. DetectChanges starting for 'TestDBContext'.
  6. =============================== EF Core log finished ===============================
  7. =============================== EF Core log started ===============================
  8. DetectChanges completed for 'TestDBContext'.
  9. =============================== EF Core log finished ===============================
  10. =============================== EF Core log started ===============================
  11. Opening connection to database 'TestDB' on server 'localhost'.
  12. =============================== EF Core log finished ===============================
  13. =============================== EF Core log started ===============================
  14. Opened connection to database 'TestDB' on server 'localhost'.
  15. =============================== EF Core log finished ===============================
  16. =============================== EF Core log started ===============================
  17. Beginning transaction with isolation level 'ReadCommitted'.
  18. =============================== EF Core log finished ===============================
  19. =============================== EF Core log started ===============================
  20. Executing update commands individually as the number of batchable commands (3) is smaller than the minimum batch size (4).
  21. =============================== EF Core log finished ===============================
  22. =============================== EF Core log started ===============================
  23. Executing DbCommand [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
  24. SET NOCOUNT ON;
  25. UPDATE [Book] SET [PersonCode] = @p0
  26. WHERE [BookCode] = @p1;
  27. SELECT @@ROWCOUNT;
  28. =============================== EF Core log finished ===============================
  29. =============================== EF Core log started ===============================
  30. Executed DbCommand (4ms) [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
  31. SET NOCOUNT ON;
  32. UPDATE [Book] SET [PersonCode] = @p0
  33. WHERE [BookCode] = @p1;
  34. SELECT @@ROWCOUNT;
  35. =============================== EF Core log finished ===============================
  36. =============================== EF Core log started ===============================
  37. A data reader was disposed.
  38. =============================== EF Core log finished ===============================
  39. =============================== EF Core log started ===============================
  40. Executing DbCommand [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
  41. SET NOCOUNT ON;
  42. UPDATE [Book] SET [PersonCode] = @p0
  43. WHERE [BookCode] = @p1;
  44. SELECT @@ROWCOUNT;
  45. =============================== EF Core log finished ===============================
  46. =============================== EF Core log started ===============================
  47. Executed DbCommand (4ms) [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
  48. SET NOCOUNT ON;
  49. UPDATE [Book] SET [PersonCode] = @p0
  50. WHERE [BookCode] = @p1;
  51. SELECT @@ROWCOUNT;
  52. =============================== EF Core log finished ===============================
  53. =============================== EF Core log started ===============================
  54. A data reader was disposed.
  55. =============================== EF Core log finished ===============================
  56. =============================== EF Core log started ===============================
  57. Executing DbCommand [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
  58. SET NOCOUNT ON;
  59. UPDATE [Book] SET [PersonCode] = @p0
  60. WHERE [BookCode] = @p1;
  61. SELECT @@ROWCOUNT;
  62. =============================== EF Core log finished ===============================
  63. =============================== EF Core log started ===============================
  64. Executed DbCommand (4ms) [Parameters=[@p1='?' (Size = 20), @p0='?' (Size = 20)], CommandType='Text', CommandTimeout='30']
  65. SET NOCOUNT ON;
  66. UPDATE [Book] SET [PersonCode] = @p0
  67. WHERE [BookCode] = @p1;
  68. SELECT @@ROWCOUNT;
  69. =============================== EF Core log finished ===============================
  70. =============================== EF Core log started ===============================
  71. A data reader was disposed.
  72. =============================== EF Core log finished ===============================
  73. =============================== EF Core log started ===============================
  74. Committing transaction.
  75. =============================== EF Core log finished ===============================
  76. =============================== EF Core log started ===============================
  77. Closing connection to database 'TestDB' on server 'localhost'.
  78. =============================== EF Core log finished ===============================
  79. =============================== EF Core log started ===============================
  80. Closed connection to database 'TestDB' on server 'localhost'.
  81. =============================== EF Core log finished ===============================
  82. =============================== EF Core log started ===============================
  83. Disposing transaction.
  84. =============================== EF Core log finished ===============================
  85. =============================== EF Core log started ===============================
  86. An 'Book' entity tracked by 'TestDBContext' changed from 'Modified' to 'Unchanged'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
  87. =============================== EF Core log finished ===============================
  88. =============================== EF Core log started ===============================
  89. An 'Book' entity tracked by 'TestDBContext' changed from 'Modified' to 'Unchanged'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
  90. =============================== EF Core log finished ===============================
  91. =============================== EF Core log started ===============================
  92. An 'Book' entity tracked by 'TestDBContext' changed from 'Modified' to 'Unchanged'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
  93. =============================== EF Core log finished ===============================
  94. =============================== EF Core log started ===============================
  95. SaveChanges completed for 'TestDBContext' with 3 entities written to the database.
  96. =============================== 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两个方法,有一段描述有点问题,如下:

  1. 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中如何正确地设置两张表之间的关联关系的更多相关文章

  1. 一起学Hadoop——实现两张表之间的连接操作

    ---恢复内容开始--- 之前我们都是学习使用MapReduce处理一张表的数据(一个文件可视为一张表,hive和关系型数据库Mysql.Oracle等都是将数据存储在文件中).但是我们经常会遇到处理 ...

  2. mysql 如何找出两张表之间的关系

    分析步骤: #1.先站在左表的角度去找 是否左表的多条记录可以对应右表的一条记录,如果是,则证明左表的一个字段foreign key 右表一个字段(通常是id) #2.再站在右表的角度去找 是否右表的 ...

  3. EF Core中Fluent Api如何删除指定数据表中的行

    这两天一直在研究在code first下如何删除数据表中的指定行,于是开始搜狗,后来百度,压根就找不到资料,后来一想可能我的搜索关键字有问题,而且ef core命令与ef的命令差不多,于是从这两个方面 ...

  4. JS之document例题讲解1(两张表之间数据转移、日期时间选择、子菜单下拉、用div做下拉菜单、事件总结)

    作业一:两个列表之间数据从一个列表移动到另一个列表 <div style="width:600px; height:500px; margin-top:20px"> & ...

  5. EF Core中怎么实现自动更新实体的属性值到数据库

    我们在开发系统的时候,经常会遇到这种需求数据库表中的行被更新时需要自动更新某些列. 数据库 比如下面的Person表有一列UpdateTime,这列数据要求在行被更新后自动更新为系统的当前时间. Pe ...

  6. EF Core中如何通过实体集合属性删除从表的数据

    假设在数据库中有两个表:Person表和Book表,Person和Book是一对多关系 Person表数据: Book表数据: 可以看到数据库Book表中所有的数据都属于Person表中"F ...

  7. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

  8. EF Core中避免贫血模型的三种行之有效的方法(翻译)

    Paul Hiles: 3 ways to avoid an anemic domain model in EF Core 1.引言 在使用ORM中(比如Entity Framework)贫血领域模型 ...

  9. EF Core 中DbContext不会跟踪聚合方法和Join方法返回的结果,及FromSql方法使用讲解

    EF Core中: 如果调用Queryable.Count等聚合方法,不会导致DbContext跟踪(track)任何实体. 此外调用Queryable.Join方法返回的匿名类型也不会被DbCont ...

随机推荐

  1. 分数规划(Bzoj1486: [HNOI2009]最小圈)

    题面 传送门 分数规划 分数规划有什么用? 可以把带分数的最优性求解式化成不带除发的运算 假设求max{\(\frac{a}{b},b>0\)} 二分一个权值\(k\) 令\(\frac{a}{ ...

  2. Sublime Text3之安裝Emmet及使用技巧

    首先准备工作: 如果你的Sublime Text3没有Package Control组件先看一下这里,如果以安装请忽略: 1.按Ctrl+`调出sublime text的console 2.粘贴以下代 ...

  3. 【MySQL数据库】一些bug的解决

    往往碰到mysql配置好后,第二天就登不上,也运行不了服务. 在cmd中输入 net start mysql  报mysql无法启动系统错误1067等 解决方案: 以前搞到最后没办法,重装了,后来找到 ...

  4. HTML5 Canvas中绘制椭圆的几种方法

    1.canvas自带的绘制椭圆的方法 ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)是后来 ...

  5. Node.js 的安装

    Node.js 是一个基于 Chrome V8 引擎的 JavaScript 的运行环境,简单的说就是运行在服务端的 JavaScript.所以学起来还是比较容易接受的. Node.js 使用事件驱动 ...

  6. MUI框架-13-使用百度地图 API(图文教程)

    MUI框架-13-使用百度地图 API(图文教程) 后面有实例,转载请注明出处 一.申请百度地图权限 1.打开 百度地图开放平台:http://lbsyun.baidu.com/apiconsole/ ...

  7. c# 调用 c dll 例子

    // case 1 传递 int* ///////////////////////////////////////////// extern “C” __declspec(dllexport) int ...

  8. Java BigDecimal初探

    更新时间:2016-03-17 一.引言 <Effactive Java>中有这样的描述:float和double类型的主要设计目标是为了科学计算和工程计算.他们执行二进制浮点运算,这是为 ...

  9. Android开发精彩博文收藏——UI界面类

    本文收集整理Android开发中关于UI界面的相关精华博文,共大家参考!本文不定期更新! 1. Android使用Fragment来实现TabHost的功能(解决切换Fragment状态不保存)以及各 ...

  10. vs 编译error1083

    1)右键查看该项目的属性 2)点击配置属性——〉  C/C++  ——〉  常规  ——〉 附加包含目录——〉将报错文件所在目录添加进去 3) 将项目的本地路径替换为工程相对路径 一般来说,打不开文件 ...