《Entity Framework 6 Recipes》中文翻譯系列 (12) -----第三章 查詢之使用SQL語句


翻譯的初衷以及為什么選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇

3-2使用原生SQL語句更新

問題

  你想在實體框架中使用原生的SQL語句,來更新底層數據存儲。

解決方案

  假設你有一張如圖3-2所示的Payment數據庫表,使用實體框架設計器工具創建了一個如圖3-2所示的模型。

圖3-2 Payment表,包含一個供應商的付款信息

圖3-3 包含一個Payment實體的模型

 

   為了在底層的Payment表中執行一句和多句SQL語句,可以使用在DbContext類中的屬性Database中的ExecuteSQlCommand()方法。 雖然我們能在模型中查詢Payment實體,ExecuteSqlCommand方法能讓我們直接查詢底層的數據庫,放棄實體框架的某些特性,比如變化跟蹤。我們需要一個簡單的模型對象,它包含一個用於執行SQL命令的上下文對象。

  下面代碼清單3-4執行一句或多句SQL語句

代碼清單3-4 執行查詢語句

 1  // 刪除之前的測試數據
 2             using (var context = new EFRecipesEntities())
 3             {
 4                 context.Database.ExecuteSqlCommand("delete from chapter3.payment");
 5             }
 6             //插入兩行數據
 7             using (var context = new EFRecipesEntities())
 8             {
 9                 var sql = @"insert into Chapter3.Payment(Amount, Vendor)
10                    values (@Amount, @Vendor)";    //這里可以使用@p0這樣的參數占位符,ado.net為自動為我們創建參數對象
11                 var parameters = new DbParameter[]
12                     {
13                         new SqlParameter {ParameterName = "Amount", Value = 99.97M},
14                         new SqlParameter {ParameterName = "Vendor", Value = "Ace Plumbing"}
15                     };
16 
17                 var rowCount = context.Database.ExecuteSqlCommand(sql, parameters);
18 
19                 parameters = new DbParameter[]
20                     {
21                         new SqlParameter {ParameterName = "Amount", Value = 43.83M},
22                         new SqlParameter
23                             {
24                                 ParameterName = "Vendor",
25                                 Value = "Joe's Trash Service"
26                             }
27                     };
28 
29                 rowCount += context.Database.ExecuteSqlCommand(sql, parameters);
30                 Console.WriteLine("{0} rows inserted", rowCount.ToString());
31             }
32 
33             // 獲取並顯示數據
34             using (var context = new EFRecipesEntities())
35             {
36                 Console.WriteLine("Payments");
37                 Console.WriteLine("========");
38                 foreach (var payment in context.Payments)
39                 {
40                     Console.WriteLine("Paid {0} to {1}", payment.Amount.ToString(),
41                                       payment.Vendor);
42                 }
43             }
44 
45             Console.WriteLine("\nPress <enter> to continue...");
46             Console.ReadLine();

代碼清單3-4的輸出如下:

1 2 rows inserted
2 Payments
3 ========
4 Paid $99.97 to Ace Plumbing
5 Paid $43.83 to Joe's Trash Service

 

   在代碼清單3-4中,我們先刪除之前的測試數據,然后使用在DbContext類中的屬性Database中的ExecuteSQlCommand()方法,請注意這里是如何把一個原生的SQL語句傳給這個方法的。

  然后,我們創建一個字符串形式的SQL插入語句,這個語句包含了兩個參數@Amout和@Vendor。它們只是一個占位符,當SQL語句執行時,會被具體的值給替換掉。

  接下來,我們創建了兩個DbParameter類型的參數對象,它們把參數占位符和具體的值綁定在一起。第一個插入,我將值99.97綁定到了占位符Amount上,將值"Ace Plumbing"綁定到占位符Vendor上。隨后,我們創建了另一個記錄。這里需要注意,這兩個參數對象是如何分配到一個DbParameter類型的數組上的。為了執行SQL語句,我們傳遞包含SQL語句的字符串和DbParameter類型的參數數組給方法ExecuteSqlCommand(),方法ExecueSqlCommand返回SQL語句所影響的行數。在我們的示例中,我們每次調用ExecuteSqlCommand()方法插入一行數據。

  如果你的SQL語句沒有任何參數,ExecuteSqlCommand方法有另一個重載方法,它只包含一個接收字符串形式的SQL語句參數。

    代碼清單3-4中的模式跟我們在ADO.NET框架中使用SqlClient對象查詢數據的方式有點相同。不同的是,我們不需要構造一個連接字符串和顯式打開一個數據庫連接。實體框架上下文對象會自動完成這項工作。需要注意的是,這里有兩個版本的上下文對象:實體框架5、6和4.x中用於Code-First的DbContext上下文對象,和早期版本中的ObjectContext上下文對象。

  需要記住的是,DbContext只是一個簡單的包裝器或者 “Façade,”(譯注:這是法語,”外觀模式“)它包裝了ObjectContext上下文對象,使上下文對象更直接和更易於使用。ObjectContext的所有功能仍然有效。

  表示SQL命令和參數的方式也有不同。在ADO.NET中的ExecuteNonQuery()方法中,命令文本和參數被設置到Command對象上,但在實體框架中傳遞給ExecuteSqlCommand()方法的是簡單類型的參數。

  細心的讀者可能已經注意到(這是重點),我們沒有查詢模型。事實上,正如我們前面提到的那樣,我們不需要如圖3-3所示的Payment實體。方法ExceuteSqlCommand()只使用DbContext上下文對象,以及連接字符串。

 

最佳實踐

  使用參數化SQL語句和不使用參數化SQL語句是一個問題.....。你應該使用參數化的SQL語句,還是動態創建字符串形式的SQL語句呢?最佳實踐是,盡可能地使用參數化的SQL語句。原因如下:

  1、參數化SQL語句能幫助阻止SQL注入攻擊。如果你使用用戶界面上文本框控件中的輸入字符,通過動態方式拼接SQL語句。那么你可能無意中就把自己暴露給了SQL注入語句,它會嚴重地損害你的數據庫和泄露敏感信息。當你使用參數化的SQL語句時,它會幫你阻止這種情況的發生。

  2、如果我們在示例中所示,參數化SQL語句,允許重用SQL語句。重用能讓我們的代碼更加簡潔且易於閱讀。

  3、很多企業級數據庫,像Oracle、DB2以及SQL Server 在某些情況下,它們能憑借參數化查詢,重用被解析過的查詢語句,即使是參數發生了改變。通過重用,提升了數據庫的性能和降低了處理過程。

  4、參數化的SQ語句能讓代碼更有可維護性和可配置性。例如,SQL語句可以來至配置文件中,這可以讓你不修改代碼就能改變應用程序。

 

3-3使用原生SQL語句獲取對象

問題

  你想使用原生SQL語句從數據庫獲取對象。

解決方案

  假設你有如圖3-4所示的一個擁有Student實體類型的模型。

圖3-4 一個擁有Student實體類型的模型

  你想通過執行原生的SQL語句返回實體類型Student的實例集合。正如在前面小節中看到的那樣,實體框架中的ExecuteSqlCommand()方法和ADO.NET中SQLCommand的ExcuteNonQuery方法有點相似。它執行給定的SQL語句,返回受影響的行數。為了讓實體框架實現無類型數據到強類型的實體轉換,我可以使用方法SqlQuery()。

  作為開始,本示例憑借實體框架中的Code-First。代碼清單3-5,創建了一個Student實體類。

代碼清單3-5 Student 實體類

 public class Student
    {
        public int StudentId { get; set; }
        public string Degree { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

 

  接下來,代碼清單3-6,創建了用於Code-First的上下文對象。

代碼清單3-6 上下文對象DbContext

 1  public class EFRecipesEntities : DbContext
 2     {
 3         public EFRecipesEntities()
 4             : base("ConnectionString")
 5         {
 6         }
 7 
 8         public DbSet<Student> Students { get; set; }
 9 
10         protected override void OnModelCreating(DbModelBuilder modelBuilder)
11         {
12             modelBuilder.Entity<Student>().ToTable("Chapter3.Student");
13             base.OnModelCreating(modelBuilder);
14         }
15     }

  

  為了執行SQL語句以及返回實體類型Student的實例集合,使用代碼清單3-7的模式。

代碼清單3-7 使用ExecuteStoreQuery()(譯注:應該是使用SqlQuery()方法)方法執行SQL語句並返回對象。

 1 using (var context = new EFRecipesEntities())
 2             {
 3                 // 刪除出測試數據
 4                 context.Database.ExecuteSqlCommand("delete from chapter3.student");
 5 
 6                 // 插入數據
 7                 context.Students.Add(new Student
 8                     {
 9                         FirstName = "Robert",
10                         LastName = "Smith",
11                         Degree = "Masters"
12                     });
13                 context.Students.Add(new Student
14                     {
15                         FirstName = "Julia",
16                         LastName = "Kerns",
17                         Degree = "Masters"
18                     });
19                 context.Students.Add(new Student
20                     {
21                         FirstName = "Nancy",
22                         LastName = "Stiles",
23                         Degree = "Doctorate"
24                     });
25                 context.SaveChanges();
26             }
27 
28             using (var context = new EFRecipesEntities())
29             {
30                 var sql = "select * from Chapter3.Student where Degree = @Major";
31                 var parameters = new DbParameter[]
32                     {
33                         new SqlParameter {ParameterName = "Major", Value = "Masters"}
34                     };
35                 var students = context.Database.SqlQuery<Student>(sql, parameters);
36                 Console.WriteLine("Students...");
37                 foreach (var student in students)
38                 {
39                     Console.WriteLine("{0} {1} is working on a {2} degree",
40                                       student.FirstName, student.LastName, student.Degree);
41                 }
42             }
43 
44             Console.WriteLine("\nPress <enter> to continue...");
45             Console.ReadLine();
46         }

  代碼清單3-7的輸出如下:

1 Students...
2 Robert Smith is working on a Masters degree
3 Julia Kerns is working on a Masters degree

 

原理

  在代碼清單3-7中,我們添加了3個Students到DbContext上下文中並使用SaveChanges()方法保存到數據庫。

  為了獲取正在攻讀碩士學位的學生,我們使用了SqlQuery()方法、一個參數化的SQL語句,一個被設置成“Masters.“值的參數。我們枚舉返回的Stuendts集合並打印輸出。注意相關的上下文對象為這些值實現的變化跟蹤。

  這里在查詢語句中使用“*”表示所有的列名,實體框架會將返回的列匹配到合適的屬性上。一般情況下,這會工作得很好。但是,查詢中只有部分列返回時,實體框架會在實例化對象時拋出一個異常。一個更好的方法和最佳實踐是,在你的查詢語句中顯式枚舉所有列(也就是說,指定所有的列名)。

  如果你的SQL語句返回的列多於實例化實體所需數量(也就是說,列值數量多於實體對象屬性數量),實體框架會忽略掉多於的列。如果你仔細想想,這不是一個令人滿意的行為。再一次重申,在SQL語句中顯式枚舉你所期望返回的列名,確保它們與實體類型匹配

  SqlQuery()方法有很多限制,如果你在使用TPH繼承映射,你的SQL語句返回的行要映射到不同的派生類型上,實體框架不能使用鑒別列來將行映射到正確的派生類型。你可能會得到一個運行時異常,因為行中可能不包含正在實例化類型所需的值。

  有趣的是,我們可以使用SqlQuery()方法實例化根本就不是實體的類型。例如,我們創建一個StudentName類,它只包含姓,和名兩個屬性民。如果我們的SQL語句也只返回這兩個列,我們可以使用SqlQuery<StudentName>()方法和指定的SQL語句獲取類型StudentName的實例集合。

  我們很小心地使用短語,SQL語句,而不是查詢語句,是因為SqlQuery()方法可以接受任何返回行集合的SQL語句。這當然包含查詢語句,但也包含執行存儲過程的SQL語句。

 

 

 

實體框架交流QQ群:  458326058,歡迎有興趣的朋友加入一起交流

謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

 


免責聲明!

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



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