《Entity Framework 6 Recipes》中文翻譯系列 (14) -----第三章 查詢之查詢中設置默認值和存儲過程返回多結果集


翻譯的初衷以及為什么選擇《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/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM