翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇

7-7  标识关系中使用依赖实体

问题

  你想在标识关系中插入,更新和删除一个依赖实体。

解决方案

  假设你有如图7-8所示的模型。实体LineItem的实体键是一个复合键。由InvoiceNumber和ItemNumber复合而成。InvoiceNumber同是也是一个外键。

图7-8. Invoie和LineItem是一个标识关系,这是因为实体LineItem的复合实体键

  当实体的一个属性,既是实体键又是外键时,就可以说这个实体在一个标识关系中。在我们的模型里,实体LineItem的实体键,它的标识,同时也是相对于实体Invoice的外键。实体LineItem被称作依赖实体(depaendent entity),Invoice被称作主实体(principal entity)。

  实体框架在处理如何删除标识关系中的依赖实体时,有所不同。因为依赖实体不能离开标识关系而存在。简单地从主实体的集合中删除依赖实体,在实体框架中的结果是,删除的依赖实体被标记为删除状态。另外,删除主实体,会连同依赖实体一直被标记为删除。这让我们想起数据库中级联删除。当然,实体框架还允许显式删除依赖实体。 代码清单7-5演示了这三种不同的场景。

代码清单7-5. 删除依赖实体

  1. public static class Recipe7Program
  2. {
  3. public static void Run()
  4. {
  5. using (var context = new Recipe7Context())
  6. {
  7.  
  8. var invoice1 = new Invoice
  9. {
  10. BilledTo = "Julie Kerns",
  11. InvoiceDate = DateTime.Parse("9/19/2013")
  12. };
  13. var invoice2 = new Invoice
  14. {
  15. BilledTo = "Jim Stevens",
  16. InvoiceDate = DateTime.Parse("9/21/2013")
  17. };
  18. var invoice3 = new Invoice
  19. {
  20. BilledTo ="Juanita James",
  21. InvoiceDate = DateTime.Parse("9/23/2013")
  22. };
  23. context.LineItems.Add(new LineItem
  24. {
  25. Cost = 99.29M,
  26. Invoice = invoice1
  27. });
  28. context.LineItems.Add(new LineItem
  29. {
  30. Cost = 29.95M,
  31. Invoice = invoice1
  32. });
  33. context.LineItems.Add(new LineItem
  34. {
  35. Cost = 109.95M,
  36. Invoice = invoice2
  37. });
  38. context.LineItems.Add(new LineItem
  39. {
  40. Cost = 49.95M,
  41. Invoice = invoice3
  42. });
  43. context.SaveChanges();
  44.  
  45. // 显示LineItems
  46. Console.WriteLine("Original set of line items...");
  47. DisplayLineItems();
  48.  
  49. //从invoice1的集合中移除一个LineItem
  50. var item = invoice1.LineItems.ToList().First();
  51. invoice1.LineItems.Remove(item);
  52. context.SaveChanges();
  53. Console.WriteLine("\nAfter removing a line item from an invoice...");
  54. DisplayLineItems();
  55.  
  56. // 移除 invoice2
  57. context.Invoices.Remove(invoice2);
  58. context.SaveChanges();
  59. Console.WriteLine("\nAfter removing an invoice...");
  60. DisplayLineItems();
  61.  
  62. //单独移除一个LineItem
  63. context.LineItems.Remove(invoice1.LineItems.First());
  64. context.SaveChanges();
  65. Console.WriteLine("\nAfter removing a line item...");
  66. DisplayLineItems();
  67.  
  68. //单独更新一个LineItem
  69. var item2 = invoice3.LineItems.ToList().First();
  70. item2.Cost = 39.95M;
  71. context.SaveChanges();
  72. Console.WriteLine("\nAfter updating a line item from an invoice …");
  73. DisplayLineItems();
  74. }
  75. }
  76.  
  77. static void DisplayLineItems()
  78. {
  79. bool found = false;
  80. using (var context = new Recipe7Context())
  81. {
  82. foreach (var lineitem in context.LineItems)
  83. {
  84. Console.WriteLine("Line item: Cost {0}",
  85. lineitem.Cost.ToString("C"));
  86. found = true;
  87. }
  88. }
  89. if (!found)
  90. Console.WriteLine("No line items found!");
  91. }
  92.  
  93. }

代码清单7-5的输出如下:

  1. Original set of line items...
  2. Line item: Cost $99.29
  3. Line item: Cost $29.95
  4. Line item: Cost $109.95
  5. Line item: Cost $49.95
  6. After removing a line item from an invoice...
  7. Line item: Cost $29.95
  8. Line item: Cost $109.95
  9. Line item: Cost $49.95
  10. After removing an invoice...
  11. Line item: Cost $29.95
  12. After removing a line item...
  13. Line item: Cost $49.95
  14. After updating a line item...
  15. Line item: Cost $39.95

原理

  代码清单7-5使用三种方法删除LineItem。第一种从Invoice的集合中删除。因为一个LineItem依赖Invoice的标识,实体框架标记引用的LineItme对象为删除状态。第二种是删除invoice对象,实体框架会标记所有的依赖于此对象的lineItme为删除状态。最后一种,是直接调用Remove()方法从上下文对象中的LineItems实体集中删除最后剩下的一个lineItem对象。

  你可以修改依赖实体的,除了标识关系中的属性以外的所有属性。在我们的模型中,可以修改实体LineItem中的Cost属性。但不能修改Invoice导航属性。

  当我们保存一个标识关系中的主实体对象时,它的实体键值是从数据库中产生(由存储层产生值),并被写回到主实体对象,以及它的所有依赖实体中的。这得确保所有的操作在数据库上下文中都是同步进行的。

7-8  使用数据库上下文插入实体

问题

  你想使用数据上下文将模型中实体插入到数据库中。

解决方案

  假设你有如图7-9所示的模型。

图7-9 一个包含实体employee和task的模型

  图7-9中的模型表示员工和他们的任务。你想插入一个新的员工和它的任务到数据库中。为了插入一个员工,创建一个Employee的实例并调用上下文对象中Employees实体集中的方法Add()。为了添加员工的一个任务,创建一个Task的实例,并将它添加到employee对象的Tasks集合中。 你还必须调用Add()方法将employee和task添加到数据库上下文中。最后,调用SaveChanges()方法,将这些变化持久化到数据库中。

  代码清单7-6演示了,使用Add()方法添加新对象到上下文中,并调用SaveChanges()方法持久化到数据库中。

代码清单7-6. 插入新实体到数据库

  1. public static class Recipe8Program
  2. {
  3. public static void Run()
  4. {
  5. using (var context = new Recipe8Context())
  6. {
  7. var employee1 = new Employee
  8. {
  9. EmployeeNumber = ,
  10. Name = "Robin Rosen",
  11. Salary = 106000M
  12. };
  13. var employee2 = new Employee
  14. {
  15. EmployeeNumber = ,
  16. Name = "Bill Moore",
  17. Salary = 62500M
  18. };
  19. var task1 = new Task { Description = "Report 3rd Qtr Accounting" };
  20. var task2 = new Task { Description = "Forecast 4th Qtr Sales" };
  21. var task3 = new Task { Description = "Prepare Sales Tax Report" };
  22.  
  23. //在Employees实体集上使用Add()方法
  24. context.Employees.Add(employee1);
  25.  
  26. //添加两个新的task到employee1的Tasks中
  27. employee1.Tasks.Add(task1);
  28. employee1.Tasks.Add(task2);
  29.  
  30. // 添加一个task到employee2的Tasks中,并使用Add()方法添加task到上下文中
  31. employee2.Tasks.Add(task3);
  32. context.Tasks.Add(task3);
  33.  
  34. //持久化到数据库
  35. context.SaveChanges();
  36. }
  37.  
  38. using (var context = new Recipe8Context())
  39. {
  40. foreach (var employee in context.Employees)
  41. {
  42. Console.WriteLine("Employee: {0}'s Tasks", employee.Name);
  43. foreach (var task in employee.Tasks)
  44. {
  45. Console.WriteLine("\t{0}", task.Description);
  46. }
  47. }
  48. }
  49.  
  50. }
  51. }

代码清单7-6的输出如下:

  1. Employee: Bill Moore's Tasks
  2. Prepare Sales Tax Repor
  3. Employee: Robin Rosen's Tasks
  4. Report 3rd Qtr Accounti
  5. Forecast 4th Qtr Sales

原理

  在代码清单7-6中,我们使用实体集Employees和Tasks中的Add()方法,添加实体到数据库上下文中。

  当你添加一个实体到上下文中,实体框架会为这个新加入的实体,创建一个临时实体键。 实体框架使用这个临时的键来唯一标识该实体。当实体被持久化到数据库后,这个临时的实体键,会被一个真正的键值给替换。如果添加到数据库中的两个实体被分配相同的实体键,实体框架会抛出一个异常。这种情况一般发生在客户端或者存储生成过程,给实体分配了相同的键值。

  对于外键关联,你可以使用关联实体的实体键分配给实体的外键属性。虽然涉及临时键值,但实体框架会在实体被保存到数据库后,正确地修正键和关系。

  你还可以使用Attach()方法来添加一个实体到上下文中。这是一个分为两步的过程,首先是调用Attach()方法。它将添加实体到上下文中,但是变化跟踪器一开始将实体标记为Unchanged状态。如果这时调用SaveChanges()方法,它不会将实体保存到数据库。第二步是,将实体递给上下文的Entry()方法,获得一个DBEntityEntry实例,并设置它的属性State为新的状态:EntityState.Added。这时调用SaveChanges()方法会将新实体保存到数据库。

7-9  异步查询和保存

问题

  你想在执行查询和持久化变更时,保持应用程序的响应性。

解决方案

  假设你有POCO实体,Account和Transaction,你想使用Code-First建模,如代码清单7-7所示。

  1. public class Account
  2. {
  3. public int AccountNumber { get; set; }
  4. public string AccountHolder { get; set; }
  5.  
  6. public virtual ICollection<Transaction> Transactions { get; set; }
  7. }
  8.  
  9. public class Transaction
  10. {
  11. public int AccountNumber { get; set; }
  12. public int TransactionNumber { get; set; }
  13. public DateTime TransactionDate { get; set; }
  14. public decimal Amount { get; set; }
  15. }

  实体Transaction显然是实体Account的依赖实体,所以我们通过创建一个EntityTypeConfiguration子类来为每个实体配置关系。如代码清单7-8所示。

代码清单7-8. 配置实体Account和Transaction

  1. public class AccountTypeConfiguration : EntityTypeConfiguration<Account>
  2. {
  3. public AccountTypeConfiguration()
  4. {
  5. HasKey(a => a.AccountNumber);
  6.  
  7. Property(a => a.AccountNumber)
  8. .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
  9.  
  10. HasMany(a => a.Transactions)
  11. .WithRequired();
  12. }
  13. }
  14.  
  15. public class TransactionTypeConfiguration : EntityTypeConfiguration<Transaction>
  16. {
  17. public TransactionTypeConfiguration()
  18. {
  19. HasKey(t => new {t.AccountNumber, t.TransactionNumber});
  20.  
  21. Property(t => t.TransactionNumber)
  22. .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
  23. }
  24. }

  最后,在代码清单7-9中,我们创建DbContext的子类并重写OnModelCreating方法,在这个方法中,我们添加实体配置到模型构建器的配置集合中。

代码清单7-9. 创建DbContext的子类

  1. public class Recipe9Context : DbContext
  2. {
  3. public DbSet<Account> Accounts { get; set; }
  4. public DbSet<Transaction> Transactions { get; set; }
  5.  
  6. public Recipe9Context() : base("name=EF6CodeFirstRecipesContext")
  7. {
  8.  
  9. }
  10.  
  11. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  12. {
  13. base.OnModelCreating(modelBuilder);
  14.  
  15. modelBuilder.Configurations.Add(new AccountTypeConfiguration());
  16. modelBuilder.Configurations.Add(new TransactionTypeConfiguration());
  17. }
  18. }

  为了异步查询和保存,我们将分别使用LINQ to Entities方法ForEachAsync(),和上下文DbContext的方法SaveChangesAsync()。代码清单7-10演示了这两个方法的用法。

代码清单7-10. 异步查询和保存

  1. public static class Recipe9Program
  2. {
  3. public static async Task Run()
  4. {
  5. using (var context = new Recipe9Context())
  6. {
  7. var account1 = new Account
  8. {
  9. AccountHolder = "Robert Dewey",
  10. Transactions = new HashSet<Transaction>()
  11. {
  12. new Transaction
  13. {
  14. TransactionDate = Convert.ToDateTime("07/05/2013"),
  15. Amount = 104.00M
  16. },
  17. new Transaction
  18. {
  19. TransactionDate = Convert.ToDateTime("07/12/2013"),
  20. Amount = 104.00M
  21. },
  22. new Transaction
  23. {
  24. TransactionDate = Convert.ToDateTime("07/19/2013"),
  25. Amount = 104.00M
  26. }
  27. }
  28. };
  29. var account2 = new Account
  30. {
  31. AccountHolder = "James Cheatham",
  32. Transactions = new List<Transaction>
  33. {
  34. new Transaction
  35. {
  36. TransactionDate = Convert.ToDateTime("08/01/2013"),
  37. Amount = 900.00M
  38. },
  39. new Transaction
  40. {
  41. TransactionDate = Convert.ToDateTime("08/02/2013"),
  42. Amount = -42.00M
  43. }
  44. }
  45. };
  46. var account3 = new Account
  47. {
  48. AccountHolder = "Thurston Howe",
  49. Transactions = new List<Transaction>
  50. {
  51. new Transaction
  52. {
  53. TransactionDate = Convert.ToDateTime("08/05/2013"),
  54. Amount = 100.00M
  55. }
  56. }
  57. };
  58.  
  59. context.Accounts.Add(account1);
  60. context.Accounts.Add(account2);
  61. context.Accounts.Add(account3);
  62. context.SaveChanges();
  63.  
  64. //为每个account添加每月的服务费
  65. foreach (var account in context.Accounts)
  66. {
  67. var transactions = new List<Transaction>
  68. {
  69. new Transaction
  70. {
  71. TransactionDate = Convert.ToDateTime("08/09/2013"),
  72. Amount = -5.00M
  73. },
  74. new Transaction
  75. {
  76. TransactionDate = Convert.ToDateTime("08/09/2013"),
  77. Amount = -2.00M
  78. }
  79. };
  80.  
  81. Task saveTask = SaveAccountTransactionsAsync(account.AccountNumber, transactions);
  82.  
  83. Console.WriteLine("Account Transactions for the account belonging to {0} (acct# {1})", account.AccountHolder, account.AccountNumber);
  84. await saveTask;
  85. await ShowAccountTransactionsAsync(account.AccountNumber);
  86. }
  87.  
  88. }
  89.  
  90. }
  91.  
  92. private static async Task SaveAccountTransactionsAsync(int accountNumber, ICollection<Transaction> transactions)
  93. {
  94. using (var context = new Recipe9Context())
  95. {
  96. var account = new Account { AccountNumber = accountNumber };
  97. context.Accounts.Attach(account);
  98. context.Entry(account).Collection(a => a.Transactions).Load();
  99.  
  100. foreach (var transaction in transactions.OrderBy(t => t.TransactionDate))
  101. {
  102. account.Transactions.Add(transaction);
  103. }
  104.  
  105. await context.SaveChangesAsync();
  106.  
  107. }
  108.  
  109. }
  110.  
  111. private static async Task ShowAccountTransactionsAsync(int accountNumber)
  112. {
  113. Console.WriteLine("TxNumber\tDate\tAmount");
  114. using (var context = new Recipe9Context())
  115. {
  116. var transactions = context.Transactions.Where(t => t.AccountNumber == accountNumber);
  117. await transactions.ForEachAsync(t => Console.WriteLine("{0}\t{1}\t{2}", t.TransactionNumber, t.TransactionDate, t.Amount));
  118. }
  119. }
  120. }

原理

  示例中使用的异步结构是.NET4.5引入的,用来降低平常编写异步代码的复杂性。当我们调用SaveAccountTransactionsAsync()方法时,我们将它分配给Task对象。它调用方法并向调用者返回执行权。同时,异步部分,SaveAccounTransactionsAsync()也在执行。调用ShowAccountTransactionsAsync()方法的代码与调用SaveAccountTransactionsAsync()方法类似。当等待两个方法的调用返回时,执行权返回到await语句的下一行代码。

  重要的是,要知道.NET4.5中的异步模型是单线程,不是多线程,所以,代码 await SaveAccountTransactionsAsync()会被挂起,直到这个方法返回。另外需要知道的是 ,任何方法在调用一个async方法时,它自己必须被async修改符标记,并且返回一个Task类型或者Task<T>类型。

  代码清单7-10的输出如下:

  1. Account Transactions for the account belonging to Robert Dewey (acct# )
  2. TxNumber Date Amount
  3. // :: AM 104.00
  4. // :: AM 104.00
  5. // :: AM 104.00
  6. // :: AM -5.00
  7. // :: AM -2.00
  8. Account Transactions for the account belonging to James Cheatham (acct# )
  9. TxNumber Date Amount
  10. // :: AM 900.00
  11. // :: AM -42.00
  12. // :: AM -5.00
  13. // :: AM -2.00
  14. Account Transactions for the account belonging to Thurston Howe (acct# )
  15. TxNumber Date Amount
  16. // :: AM 100.00
  17. // :: AM -5.00
  18. // :: AM -2.00

至此,第七章结束,下篇我们开始第八章。感谢你的阅读。

实体框架交流QQ群:  458326058,欢迎有兴趣的朋友加入一起交流

谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

《Entity Framework 6 Recipes》中文翻译系列 (41) ------ 第七章 使用对象服务之标识关系中使用依赖实体与异步查询保存的更多相关文章

  1. 《Entity Framework 6 Recipes》中文翻译系列 (38) ------ 第七章 使用对象服务之动态创建连接字符串和从数据库读取模型

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第七章 使用对象服务 本章篇幅适中,对真实应用中的常见问题提供了切实可行的解决方案. ...

  2. 《Entity Framework 6 Recipes》中文翻译系列 (39) ------ 第七章 使用对象服务之配置模型和使用单复数服务

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 7-3  配置模型 问题 你想了解配置模型中的各种选项. 解决方案 当你添加一个AD ...

  3. 《Entity Framework 6 Recipes》中文翻译系列 (40) ------ 第七章 使用对象服务之从跟踪器中获取实体与从命令行生成模型(想解决EF第一次查询慢的,请阅读)

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 7-5  从跟踪器中获取实体 问题 你想创建一个扩展方法,从跟踪器中获取实体,用于数 ...

  4. 《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述

    微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF ...

  5. 《Entity Framework 6 Recipes》翻译系列 (4) -----第二章 实体数据建模基础之从已存在的数据库创建模型

    不知道对EF感兴趣的并不多,还是我翻译有问题(如果是,恳请你指正),通过前几篇的反馈,阅读这个系列的人不多.不要这事到最后成了吃不讨好的事就麻烦了,废话就到这里,直奔主题. 2-2 从已存在的数据库创 ...

  6. 《Entity Framework 6 Recipes》翻译系列(2) -----第一章 开始使用实体框架之使用介绍

    Visual Studio 我们在Windows平台上开发应用程序使用的工具主要是Visual Studio.这个集成开发环境已经演化了很多年,从一个简单的C++编辑器和编译器到一个高度集成.支持软件 ...

  7. 《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型

    第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以 ...

  8. 《Entity Framework 6 Recipes》翻译系列 (5) -----第二章 实体数据建模基础之有载荷和无载荷的多对多关系建模

    2-3 无载荷(with NO Payload)的多对多关系建模 问题 在数据库中,存在通过一张链接表来关联两张表的情况.链接表仅包含连接两张表形成多对多关系的外键,你需要把这两张多对多关系的表导入到 ...

  9. 《Entity Framework 6 Recipes》中文翻译系列 目录篇 -持续更新

    为了方便大家的阅读和学习,也是响应网友的建议,在这里为这个系列做一个目录.在目录开始这前,我先来回答之前遇到的几个问题. 1.为什么要学习EF? 这个问题很简单,项目需要.这不像学校,没人强迫你学习! ...

随机推荐

  1. [leetcode] 小心成环

    156. Binary Tree Upside Down Given a binary tree where all the right nodes are either leaf nodes wit ...

  2. FragmentActivity_左右滑动的碎片

    test1.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:an ...

  3. Day 2 T1

    题目描述 组合数表示的是从n个物品中选出m个物品的方案数.举个例子,从(1,2,3) 三个物品中选择两个物品可以有(1,2),(1,3),(2,3)这三种选择方法.根据组合数的定 义,我们可以给出计算 ...

  4. Js的typeof和Js的基本数据类型

    本文将从以下几个方面介绍Js的typeof和Js的基本数据类型: ** Js的typeof的用法 ** Js的基本数据类型 ** 使用基本类型使用typeof的返回结果 ** Js的typeof的用法 ...

  5. uoj98未来程序改 纯暴力不要想了

    暴力模拟A了,数据还是良(shui)心(shui)的 90分的地方卡了半天最后发现一个局部变量被我手抖写到全局去了,,, 心碎*∞ 没什么好解释的,其实只要写完表达式求值(带函数和变量的),然后处理一 ...

  6. Code of Conduct

    v

  7. hdu 2037简单贪心--活动安排问题

    活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合,是可以用贪心算法有效求解的很好例子.该问题要求高效地安排一系列争用某一公共资源的活动.贪心算法提供了一个简单.漂亮的方法使得尽可能多的活动 ...

  8. git上传文件出错的时候

    $ git pull --rebase origin master 运行这个基本OK!

  9. 利用DOS批处理实现定时关机操作

    10月1放假回来,寝室晚上10:30就停电了,最无法让人理解的是第二天早上8:00才来电.原来晚上电脑都是不关机的,开着WiFi一直到天亮,可是现在不行了,电脑如果一直开着第二天早上起来电脑肯定没电, ...

  10. asp.net中的<%%> <%#%> <%=%>形式的详细用法 (转载)

    博客分类: ASP.NET   一. <%%>这种格式实际上就是和asp的用法一样的,只是asp中里面是vbscript或者javascript代码,而在asp.net中是.net平台下支 ...