我們在使用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,思路還是和第三種思路類似。有興趣的可以看一看,學習一下。
由於時間關系,先寫到這里吧。希望大家踴躍留言,討論發表觀點,有不足之處,還請指正,謝謝!