翻譯的初衷以及為什么選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇
3-6在查詢中設置默認值
問題
你有這樣一個用例,當查詢返回null值時,給相應屬性設置默認值。在我們示例中,當數據庫中返回null值時,用‘0’作為YearsWorked屬性的默認值。
解決方案
假設你有如圖3-7所示的模型,你想通過模型查詢employees。在數據庫中,代表employees的表包含一可為空的YearsWorked列。該列映射到Employee實體中的YearsWorked屬性。你想把返加行中包含null值的YearsWorked設置成默認值0。
圖3-7 包含一個Employee實體類型的模型,實體類型包含一個EmployeeId屬性、一個Name屬性和一個YearsWorked屬性
示例使用實體框架中的code-first,在代碼清單3-11,我們創建了一個Employee類。
代碼清單3-11. Employee 實體類
1 public class Employee 2 { 3 public int EmployeeId { get; set; } 4 public string Name { get; set; } 5 public int? YearsWorked { get; set; } 6 }
接下來,代碼清單3-12創建上下文對象
1 public class EFRecipesEntities : DbContext 2 { 3 public EFRecipesEntities() 4 : base("ConnectionString") {} 5 6 public DbSet<Employee> Employees { get; set; } 7 8 protected override void OnModelCreating(DbModelBuilder modelBuilder) 9 { 10 modelBuilder.Entity<Employee>().ToTable("Chapter3.Employee"); 11 base.OnModelCreating(modelBuilder); 12 } 13 }
因為我們使用的是Code-First的方式,所以能以代碼清單3-13所示的方式設值默認值。注意,代碼清單3-13中的方式不能真正的實現(從數據庫中返回)Employee實體類型的實例的默認值。相反,查詢的結果是一個匿名類型的集合,當數據庫表中YearsWorked列值為null時,匿名類型的屬性YearsWorked被以編程的方式設置成0.因此,數據庫中相應的列仍然保持null值,但在實體框架的結果集中我們使用0作為它的默認值。記住,代碼清單3-13所示的匿名類型,它是一個在運行時,依據new關鍵字后邊大括號內的屬性動態創建的類。
代碼清單3-13. 使用LINQ和Entity SQL給Null值填充默認值
1 using (var context = new EFRecipesEntities()) 2 { 3 // 刪除之前的測試數據 4 context.Database.ExecuteSqlCommand("delete from chapter3.employee"); 5 // 添加新的測試數據 6 context.Employees.Add(new Employee 7 { 8 Name = "Robin Rosen", 9 YearsWorked = 3 10 }); 11 context.Employees.Add(new Employee {Name = "John Hancock"}); 12 context.SaveChanges(); 13 } 14 15 using (var context = new EFRecipesEntities()) 16 { 17 Console.WriteLine("Employees (using LINQ)"); 18 var employees = from e in context.Employees 19 select new { Name = e.Name, YearsWorked = e.YearsWorked ?? 0 }; 20 foreach (var employee in employees) 21 { 22 Console.WriteLine("{0}, years worked: {1}", employee.Name, 23 employee.YearsWorked); 24 } 25 } 26 27 using (var context = new EFRecipesEntities()) 28 { 29 Console.WriteLine("\nEmployees (using ESQL w/named constructor)"); 30 var esql = @"select value Recipe3_6.Employee(e.EmployeeId, 31 e.Name, 32 case when e.YearsWorked is null then 0 33 else e.YearsWorked end) 34 from Employees as e"; 35 36 37 var employees = ((IObjectContextAdapter) context).ObjectContext.CreateQuery<Employee>(esql); 38 foreach (var employee in employees) 39 { 40 Console.WriteLine("{0}, years worked: {1}", employee.Name, 41 employee.YearsWorked.ToString()); 42 } 43 } 44 45 Console.WriteLine("\nPress <enter> to continue..."); 46 Console.ReadLine();
代碼清單3-13的輸出如下:
Employees (using LINQ) Robin Rosen, years worked: 3 John Hancock, years worked: 0 Employees (using ESQL w/named constructor) Robin Rosen, years worked: 3 John Hancock, years worked: 0
原理
我們在這里使用的方法是,使用LINQ和eSQL將結果投影到一個匿名類型集合,當YearsWorked在底層數據庫中為null值時,查詢將其設置成0。
在LINQ方法中,我們使用了C#中的 null值合並(null-coalescing) 操作符??,當YearsWorded在數據庫中的值為null時,將0分配給它。我們將結果投影到一個匿名類型集合。
在Entity SQL方法中,當YearsWorded在數據庫中的值為null時,我們使用了case語句來分配0給YearsWorked。我們這里演示了,如何使用Entity SQL,在不設置默認值的情況下,實例化Employee實體類型的實例。為此,我們使用了實體類型的命名構造函數(named constructor)。這個構造函數,使用實體類型中屬性定義的順序從參數中為屬性賦值。在我們示例中,Employee實體的屬性定義順序為:EmployeeId,Name,YearsWorked,從eSQL 查詢中傳遞給構造函數參數的順序與此一至。不幸的是,在LINQ to Entiytes中沒有合適的命名構造函數語法支持。
3-7從存儲過程中返回多結果集
問題
你有一個存儲過程,它返回多個結果集。你想從每個結果集實體化到實體實例。
解決方案
假設你有如圖3-8所示的模型和一個代碼清單3-14所示的存儲過程,存儲過程返回job和bid集合。
圖3-8 一個代碼job和bid的模型
代碼清單3-14. 返回多結果集的存儲過程
1 create procedure Chapter3.GetBidDetails 2 as 3 begin 4 select * from Chapter3.Job 5 select * from Chapter3.Bid 6 end
在我們的模型中,每個job有零個或是多個bids。我們的存儲過程返回所有的jobs和bids。我們想執行存儲過程並實例化兩個結果集中的所有jobs和bids。按代碼清單3-15實現此需求。
代碼清單3-15. 從存儲過程返回的多結果集實例化Jobs和Bids
1 using (var context = new EFRecipesEntities()) 2 { 3 var job1 = new Job {JobDetails = "Re-surface Parking Log"}; 4 var job2 = new Job {JobDetails = "Build Driveway"}; 5 job1.Bids.Add(new Bid {Amount = 948M, Bidder = "ABC Paving"}); 6 job1.Bids.Add(new Bid {Amount = 1028M, Bidder = "TopCoat Paving"}); 7 job2.Bids.Add(new Bid {Amount = 502M, Bidder = "Ace Concrete"}); 8 context.Jobs.Add(job1); 9 context.Jobs.Add(job2); 10 context.SaveChanges(); 11 } 12 13 using (var context = new EFRecipesEntities()) 14 { 15 var cs = @"Data Source=.;Initial Catalog=EFRecipes;Integrated Security=True"; 16 var conn = new SqlConnection(cs); 17 var cmd = conn.CreateCommand(); 18 cmd.CommandType = System.Data.CommandType.StoredProcedure; 19 cmd.CommandText = "Chapter3.GetBidDetails"; 20 conn.Open(); 21 var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); 22 var jobs = ((IObjectContextAdapter) context).ObjectContext.Translate<Job>(reader, "Jobs", 23 MergeOption.AppendOnly).ToList(); 24 reader.NextResult(); 25 ((IObjectContextAdapter) context).ObjectContext.Translate<Bid>(reader, "Bids", MergeOption.AppendOnly) 26 .ToList(); 27 foreach (var job in jobs) 28 { 29 Console.WriteLine("\nJob: {0}", job.JobDetails); 30 foreach (var bid in job.Bids) 31 { 32 Console.WriteLine("\tBid: {0} from {1}", 33 bid.Amount.ToString(), bid.Bidder); 34 } 35 } 36 37 Console.WriteLine("\nPress <enter> to continue..."); 38 Console.ReadLine(); 39 }
代碼清單3-15輸出如下:
Job: Re-surface Parking Log Bid: $948.00 from ABC Paving Bid: $1,028.00 from TopCoat Paving Job: Build Driveway Bid: $502.00 from Ace Concrete
原理
一開始,我添加了兩個jobs和一些與之相對的bids,然后將他們添加到上下文中,最新調用SaveChanges()函數保存至數據庫。
實體框架5.0就已經提供了對存儲過程返回多結果集的支持。然后,要使用此功能的話,你得使用遺留的ObjectContext對象,因為最新的DbContext對象對此不提供直接的支持。 為了解決這個問題,我們使用了SqlClient方式來讀取存儲過程的返回。此模式需要創建SqlConnection,SqlCommand.將存儲過程的名稱設置成SqlCommand的命令文本,最后調用ExecuteReader()方法得到一個DataReader對象。
有了reader對象后,我們就可以使用ObjectContext對象中的Translate()方法從reader對象中實例化Job實體。這個方法需要以下三個參數:reader,實體集的名稱和一個合並選項。需要實體集名稱是因為,一個實體可能存在包含多個實體集的結果集中。實體框架需要知道你想使用哪個實體集。
合並選項有一些需要注意的地方,我們使用MergeOption.AppendOnly選項,會讓實體的實例被添加到上下文對象中並被跟蹤。使用這個選項的原因是,讓實體框架自動關聯jobs和bids。為了實現這個目的,只要簡單地把jobs和bids添加到上下文中,實體框架就會幫我們自動關聯它們。這為我們省去了大量的冗余代碼。
方法Translate()的另一個簡單點的版本,不需要MergeOption. 它將離開上下文對象來實例化對象。這兩個版本的方法略有不同,在上下文對象之外創建的對象將不被跟蹤。如果你使用這個簡單版本的Translate()方法讀取jobs,那么你將不能在上下文中實例化一個新的bits。因為實體框架沒有任何有關jobs關聯對象的引用。這些jobs是在上下文對象之外創建的。另外你不能修改這些實例的屬性並期待實體框架幫你保存這些改變。
我們使用ToList()方法強制枚舉每個查詢,這是因為Translate()方法返回的是ObjectResult<T>,它不會真正的從reader中讀取結果。我需要在使用NextResult()方法處理下一個結果集前,強制從reader中讀取結果。在實踐中,我們多數會在代碼中使用NextResult()方法來繼續查找存儲過程返回的結果集。
雖然我們沒有在這個示例中見到它,但需要引起注意的是,Translate()方法繞過了映射層模型。如果你想使用繼承映射,或者使用一個包含復合類型屬性的實體,Translate()方法會失敗。Translate()方法需要DbDataReader對象提供與實體屬性匹配的列。匹配過程簡單地使用名稱進行。如果一列名不能與一個屬性匹配上,Translate()方法也會失敗。
實體框架交流QQ群: 458326058,歡迎有興趣的朋友加入一起交流
謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/