問題html
你想在標識關係中插入,更新和刪除一個依賴實體。數據庫
解決方案多線程
假設你有如圖7-8所示的模型。實體LineItem的實體鍵是一個複合鍵。由InvoiceNumber和ItemNumber複合而成。InvoiceNumber同是也是一個外鍵。框架
圖7-8. Invoie和LineItem是一個標識關係,這是由於實體LineItem的複合實體鍵異步
當實體的一個屬性,既是實體鍵又是外鍵時,就能夠說這個實體在一個標識關係中。在咱們的模型裏,實體LineItem的實體鍵,它的標識,同時也是相對於實體Invoice的外鍵。實體LineItem被稱做依賴實體(depaendent entity),Invoice被稱做主實體(principal entity)。async
實體框架在處理如何刪除標識關係中的依賴實體時,有所不一樣。由於依賴實體不能離開標識關係而存在。簡單地從主實體的集合中刪除依賴實體,在實體框架中的結果是,刪除的依賴實體被標記爲刪除狀態。另外,刪除主實體,會連同依賴實體一直被標記爲刪除。這讓咱們想起數據庫中級聯刪除。固然,實體框架還容許顯式刪除依賴實體。 代碼清單7-5演示了這三種不一樣的場景。ide
代碼清單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的輸出以下:ui
Original set of line items... Line item: Cost $99.29 Line item: Cost $29.95 Line item: Cost $109.95 Line item: Cost $49.95 After removing a line item from an invoice... Line item: Cost $29.95 Line item: Cost $109.95 Line item: Cost $49.95 After removing an invoice... Line item: Cost $29.95 After removing a line item... Line item: Cost $49.95 After updating a line item... Line item: Cost $39.95
原理spa
代碼清單7-5使用三種方法刪除LineItem。第一種從Invoice的集合中刪除。由於一個LineItem依賴Invoice的標識,實體框架標記引用的LineItme對象爲刪除狀態。第二種是刪除invoice對象,實體框架會標記全部的依賴於此對象的lineItme爲刪除狀態。最後一種,是直接調用Remove()方法從上下文對象中的LineItems實體集中刪除最後剩下的一個lineItem對象。
你能夠修改依賴實體的,除了標識關係中的屬性之外的全部屬性。在咱們的模型中,能夠修改實體LineItem中的Cost屬性。但不能修改Invoice導航屬性。
當咱們保存一個標識關係中的主實體對象時,它的實體鍵值是從數據庫中產生(由存儲層產生值),並被寫回到主實體對象,以及它的全部依賴實體中的。這得確保全部的操做在數據庫上下文中都是同步進行的。
問題
你想使用數據上下文將模型中實體插入到數據庫中。
解決方案
假設你有如圖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 = 629, 10 Name = "Robin Rosen", 11 Salary = 106000M 12 }; 13 var employee2 = new Employee 14 { 15 EmployeeNumber = 147, 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的輸出以下:
Employee: Bill Moore's Tasks Prepare Sales Tax Repor Employee: Robin Rosen's Tasks Report 3rd Qtr Accounti Forecast 4th Qtr Sales
原理
在代碼清單7-6中,咱們使用實體集Employees和Tasks中的Add()方法,添加實體到數據庫上下文中。
當你添加一個實體到上下文中,實體框架會爲這個新加入的實體,建立一個臨時實體鍵。 實體框架使用這個臨時的鍵來惟一標識該實體。當實體被持久化到數據庫後,這個臨時的實體鍵,會被一個真正的鍵值給替換。若是添加到數據庫中的兩個實體被分配相同的實體鍵,實體框架會拋出一個異常。這種狀況通常發生在客戶端或者存儲生成過程,給實體分配了相同的鍵值。
對於外鍵關聯,你能夠使用關聯實體的實體鍵分配給實體的外鍵屬性。雖然涉及臨時鍵值,但實體框架會在實體被保存到數據庫後,正確地修正鍵和關係。
你還能夠使用Attach()方法來添加一個實體到上下文中。這是一個分爲兩步的過程,首先是調用Attach()方法。它將添加實體到上下文中,可是變化跟蹤器一開始將實體標記爲Unchanged狀態。若是這時調用SaveChanges()方法,它不會將實體保存到數據庫。第二步是,將實體遞給上下文的Entry()方法,得到一個DBEntityEntry實例,並設置它的屬性State爲新的狀態:EntityState.Added。這時調用SaveChanges()方法會將新實體保存到數據庫。
問題
你想在執行查詢和持久化變動時,保持應用程序的響應性。
解決方案
假設你有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 93 private static async Task SaveAccountTransactionsAsync(int accountNumber, ICollection<Transaction> transactions) 94 { 95 using (var context = new Recipe9Context()) 96 { 97 var account = new Account { AccountNumber = accountNumber }; 98 context.Accounts.Attach(account); 99 context.Entry(account).Collection(a => a.Transactions).Load(); 100 101 foreach (var transaction in transactions.OrderBy(t => t.TransactionDate)) 102 { 103 account.Transactions.Add(transaction); 104 } 105 106 await context.SaveChangesAsync(); 107 108 } 109 110 } 111 112 private static async Task ShowAccountTransactionsAsync(int accountNumber) 113 { 114 Console.WriteLine("TxNumber\tDate\tAmount"); 115 using (var context = new Recipe9Context()) 116 { 117 var transactions = context.Transactions.Where(t => t.AccountNumber == accountNumber); 118 await transactions.ForEachAsync(t => Console.WriteLine("{0}\t{1}\t{2}", t.TransactionNumber, t.TransactionDate, t.Amount)); 119 } 120 } 121 }
原理
示例中使用的異步結構是.NET4.5引入的,用來下降日常編寫異步代碼的複雜性。當咱們調用SaveAccountTransactionsAsync()方法時,咱們將它分配給Task對象。它調用方法並向調用者返回執行權。同時,異步部分,SaveAccounTransactionsAsync()也在執行。調用ShowAccountTransactionsAsync()方法的代碼與調用SaveAccountTransactionsAsync()方法相似。當等待兩個方法的調用返回時,執行權返回到await語句的下一行代碼。
重要的是,要知道.NET4.5中的異步模型是單線程,不是多線程,因此,代碼 await SaveAccountTransactionsAsync()會被掛起,直到這個方法返回。另外須要知道的是 ,任何方法在調用一個async方法時,它本身必須被async修改符標記,而且返回一個Task類型或者Task<T>類型。
代碼清單7-10的輸出以下:
Account Transactions for the account belonging to Robert Dewey (acct# 1) TxNumber Date Amount 1 7/5/2013 12:00:00 AM 104.00 2 7/12/2013 12:00:00 AM 104.00 3 7/19/2013 12:00:00 AM 104.00 7 8/9/2013 12:00:00 AM -5.00 8 8/9/2013 12:00:00 AM -2.00 Account Transactions for the account belonging to James Cheatham (acct# 2) TxNumber Date Amount 4 8/1/2013 12:00:00 AM 900.00 5 8/2/2013 12:00:00 AM -42.00 9 8/9/2013 12:00:00 AM -5.00 10 8/9/2013 12:00:00 AM -2.00 Account Transactions for the account belonging to Thurston Howe (acct# 3) TxNumber Date Amount 6 8/5/2013 12:00:00 AM 100.00 11 8/9/2013 12:00:00 AM -5.00 12 8/9/2013 12:00:00 AM -2.00
至此,第七章結束,下篇咱們開始第八章。感謝你的閱讀。
實體框架交流QQ羣: 458326058,歡迎有興趣的朋友加入一塊兒交流
謝謝你們的持續關注,個人博客地址:http://www.cnblogs.com/VolcanoCloud/