擴展LINQ to Entity:使用Lambda Expression批量刪除數據------讓微軟幫我們生成T-SQL語句


  我們在使用EF(ADO.NET Entity Framework)的時候,大部分的的是查詢操作,當然Insert,Update,Delete是沒有問題的。但如果我們想要批量刪除數據,那該怎么做呢?然后就嘗試着去尋找一些方法,而后看到老趙的《擴展LINQ to SQL:使用Lambda Expression批量刪除數據》,從中獲益匪淺,學到不少東西,非常感謝。老趙的方法中需要解析Lambda Expression(當然也是正規的必經之路),來生成where條件及整個T-SQL語句,那么解析Expression需要考慮到很多方面,所以要實現一個比較完善的解析方法,目前來說比較復雜,要花費大量的時間和精力。就在前幾天尋找方法的時候,偶得靈感,故此文記之,希望對大家有所幫助,如有不足,歡迎提出來討論。

  下面就已知的方法列舉出來,以供比較。

第一種解決辦法:使用EF的DeleteObject方法

 1 NorthwindEntities db = new NorthwindEntities();
 2             //1
 3             var deleteQuery = db.Products.Where(p => p.ProductID < -10
 4                                   && p.Categories.CategoryName.Contains("test"));
 5 
 6             foreach (var item in deleteQuery)
 7             {
 8                 db.DeleteObject(item);
 9             }
10             db.SaveChanges();

這種方法,對於業務中使用(少量的數據),應該沒有大的問題,但對於大量的批量刪除就有很大的性能問題。因為它是將所有符合條件的對象都查詢出來到內存,然后標記刪除,在進行提交數據庫執行T-SQL刪除的。換句話說,對於有些同學認為,第一步查詢出對象,完全沒有必要,但EF是需要管理這些對象的。

第二種解決辦法:直接使用ADO.NET來執行T-SQL刪除。

這種方法,會直接寫刪除的SQL語句,就像老趙所說“在程序里出現直接的SQL語句是一件很丑陋的事情”(我也比較認同,但有些特殊情況,用用也不錯,哈哈)。同時因為是SQL

字符串,就損失了編譯時的錯誤檢測(某個字母寫錯誤,直到運行時才會檢測到),而且可讀性或者維護性降低了,寫起來不像linq那么爽了。

 

第三種解決方法:就是老趙給出的解決方案——使用解析Expression的方式,生成T-SQL語句,然后通過ADO.NET執行,它直接綜合了前兩種方法的優點。其中關鍵就是解析表達式,可解析Lambda Expression,沒有一定的功力是寫不出比較完善的解析的,而且需要花費大量的時間精力。前天靈光一閃,想到微軟不是給我們實現好生成T-SQL的代碼了嗎,我們可以直接使用ObjectQuery的ToTraceString方法來得到SQL語句呀,同時使用ObjectQuery的Where方法來過濾條件,然后生成刪除的SQL語句

比如:var selectSql = (source.Where(predicate) as ObjectQuery).ToTraceString();

那么這個查詢的SQL語句,如何轉化成刪除的呢?看到這個語句后,你或許就有辦法了,如:

 1 SELECT 
 2 1 AS [C1], 
 3 [Extent1].[ProductID] AS [ProductID], 
 4 [Extent1].[ProductName] AS [ProductName], 
 5 [Extent1].[SupplierID] AS [SupplierID], 
 6 [Extent1].[QuantityPerUnit] AS [QuantityPerUnit], 
 7 [Extent1].[UnitPrice] AS [UnitPrice], 
 8 [Extent1].[UnitsInStock] AS [UnitsInStock], 
 9 [Extent1].[UnitsOnOrder] AS [UnitsOnOrder], 
10 [Extent1].[ReorderLevel] AS [ReorderLevel], 
11 [Extent1].[Discontinued] AS [Discontinued], 
12 [Extent1].[CategoryID] AS [CategoryID]
13 FROM [dbo].[Products] AS [Extent1]
14 WHERE [Extent1].[ProductID] < -10

然后發現這個語句中,給表起了個別名,如果去掉別名,可以,不去掉可以嗎?於是google下,發現delete語句是可以起別名的,比如:delete tab from table1 tab where tab.Field1=1 and tab.Field2...。這下好辦了,直接得到這個語句中的別名,然后不久可以生成刪除的SQL語句了。

 

下面給出完整的實現,請查考。

 1 namespace SoftLibrary.Extensions
 2 {
 3     public static class ObjectQueryExtension
 4     {
 5         /// <summary>
 6         /// 執行批量刪除操作。直接使用 ado.net
 7         /// </summary>
 8         /// <typeparam name="TEntity"></typeparam>
 9         /// <param name="source"></param>
10         /// <param name="predicate">條件</param>
11         /// <returns></returns>
12         public static int Delete<TEntity>(this ObjectQuery<TEntity> source, Expression<Func<TEntity, bool>> predicate) 
13                                                                where TEntity : global::System.Data.Objects.DataClasses.EntityObject
14         {
15             var selectSql = (source.Where(predicate) as ObjectQuery).ToTraceString();
16 
17             //
18             int startIndex = selectSql.IndexOf(",");
19             int endIndex = selectSql.IndexOf(".");
20             string tableAlias = selectSql.Substring(startIndex + 1, endIndex - startIndex - 1);//get table alias
21             startIndex = selectSql.IndexOf("FROM");
22             string deleteSql = "DELETE " + tableAlias + " " + selectSql.Substring(startIndex);
23             //Gets the string used to open a SQL Server database
24             string connectionString = ((source as ObjectQuery).Context.Connection as EntityConnection).StoreConnection.ConnectionString;
25 
26             return ExecuteNonQuery(connectionString, deleteSql);
27         }
28 
29         /// <summary>
30         /// 執行T-sql 語句
31         /// </summary>
32         /// <param name="connectionString"></param>
33         /// <param name="sql"></param>
34         /// <param name="parameters"></param>
35         /// <returns></returns>
36         private static int ExecuteNonQuery(string connectionString, string sql, params SqlParameter[] parameters)
37         {
38             using (SqlConnection conn = new SqlConnection(connectionString))
39             {
40                 SqlCommand cmd = conn.CreateCommand();
41                 cmd.Parameters.AddRange(parameters);
42                 cmd.CommandType = System.Data.CommandType.Text;
43                 cmd.CommandText = sql;
44 
45                 if (conn.State != System.Data.ConnectionState.Open)
46                 {
47                     conn.Open();
48                 }
49                 return cmd.ExecuteNonQuery();
50             }
51         }
52     }
53 }

調用示例代碼:

NorthwindEntities db = new NorthwindEntities();
db.Products.Delete(p => p.ProductID < -10);

db.Products.Delete(p => p.ProductID < -10
                                  && p.Categories.CategoryName.Contains("test"));

這樣以來,就兼得了前兩種方法的優點,是不是寫批量刪除很爽了。至於這種方法有什么優點,有什么缺點,請各位發表意見吧。

對於如果像這樣封裝了一個業務層基類

1 public class BLLBase<TEntity, TObjectContext>
2         where TObjectContext : global::System.Data.Objects.ObjectContext
3         where TEntity : global::System.Data.Objects.DataClasses.EntityObject
4     {}

那么其中如何實現這個批量的Delete呢?我想你應該是沒有問題的,去嘗試下吧。

 

從網上還看到另一種做法,從Metadata中獲取表名,然后生成T-SQL,思路還是和第三種思路類似。有興趣的可以看一看,學習一下。

 

  由於時間關系,先寫到這里吧。希望大家踴躍留言,討論發表觀點,有不足之處,還請指正,謝謝!

 

 


免責聲明!

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



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