雜七雜八——使用LINQ檢索重複值
日常工作的時候,我們經常遇到需要檢索一組數據中是否有重複值的情況,再根據具體情況進行相應的操作。如果不使用LINQ,那我們就得使用一層一層的foreach循環來做,不但麻煩、工作效率低(因爲你得繞啊~~繞啊~~~),而且容易出bug。使用LINQ就方便得多。
舉個我前天在工作中遇到的例子吧。當時是這樣的——我需要查看一下用戶新插入的值是否已經存在於數據庫當中,如果有,就提醒客戶不能插入這個值(實際上這個值是PK)。因爲在進入頁面的時候,我已經把目前數據庫裏所有的值都讀出來、放在一個DataTable裏了,所以在用戶保存的時候,我沒必要連接數據庫進行查詢,只需要檢索當前這個DataTable就OK了。
假設這個DataTable有3列,分別是ID、Name、Age,ID是PK。使用foreach的笨辦法,是這樣:
- foreach (DataRow r1 in table.Rows)
- {
- foreach (DataRow r2 in table.Rows)
- {
- if (r2 != r1 && r2["ID"].ToString() == r1["ID"].ToString())
- {
- Console.WriteLine("Warning!");
- return false; // 檢驗失敗
- }
- }
- }
如果寫成這樣,需要注意兩點:
- 必需要有r2 != r1這個條件,不然當一個DataRow“自己遇到自己”的時候,無論如何都會return false的
- 必需把r1["ID"]和r2["ID"]轉換成string(如果你確定它是int,那轉成int也行)再進行比較,不然r1["ID"]和r2["ID"]是兩個object,調用==操作符,比較的是這兩個對象是否是同一個對象——當然不是!所以,永遠也不會return false
上面這種笨辦法適用於兩種情況:
- 需要比較簡單、只要有重複值立刻撤退的情況
- 公司把代碼行數與程序工資掛鉤的情況
使用LINQ可以更簡單地完成上面的任務,而且還可以衍生出很多附加功能:
我們把需求稍微改動一下,改成檢驗Age有沒有重複的,如果有、每個值有幾個重複。如果用foreach循環,那我們就要在循環內部加上一個Dictionay,以Age爲Key,並對每個Key進行計數了。
使用LINQ中的GroupBy操作,就能輕鬆解決這個問題。源碼如下:
- // 水之真諦
- // http://blog.csdn.net/FantasiaX
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Data;
- namespace ConsoleApplication1
- {
- class Program
- {
- static void Main(string[] args)
- {
- DataTable table = new DataTable();
- table.Columns.Add(new DataColumn("ID", typeof(int)));
- table.Columns.Add(new DataColumn("Name", typeof(string)));
- table.Columns.Add(new DataColumn("Age", typeof(int)));
- int[] ids = new int[] { 1, 2, 3, 4, 5, 6 };
- string[] names = new string[] { "Tim", "Yan", "Xiao Chen", "Miao", "Big Ma", "Little Ma" };
- int[] ages = new int[] { 28, 24, 28, 22, 27, 22 };
- for (int i = 0; i < 6; i++)
- {
- DataRow row = table.NewRow();
- row["ID"] = ids[i];
- row["Name"] = names[i];
- row["Age"] = ages[i];
- table.Rows.Add(row);
- }
- // 使用foreach
- Dictionary<int, int> dic = new Dictionary<int, int>();
- foreach (DataRow row in table.Rows)
- {
- if (!dic.Keys.Contains(Convert.ToInt32(row["Age"])))
- {
- dic.Add(Convert.ToInt32(row["Age"]), 1);
- }
- else
- {
- dic[Convert.ToInt32(row["Age"])]++;
- }
- }
- foreach (var item in dic)
- {
- Console.WriteLine("{0}, {1}", item.Key, item.Value);
- }
- Console.WriteLine("=========================================");
- // 使用LINQ (隱式數據類型)
- var ageGroups = table.Rows.Cast<DataRow>().GroupBy(row => Convert.ToInt32(row["Age"]));
- foreach (var group in ageGroups)
- {
- Console.WriteLine("{0}, {1}", group.Key.ToString(), group.Count().ToString());
- }
- Console.WriteLine("=========================================");
- // 使用LINQ (顯式數據類型)
- IEnumerable<IGrouping<int, DataRow>> ageGroups2 = table.Rows.Cast<DataRow>().GroupBy(row => Convert.ToInt32(row["Age"]));
- foreach (IGrouping<int, DataRow> group in ageGroups2)
- {
- Console.WriteLine("{0}, {1}", group.Key.ToString(), group.Count().ToString());
- }
- Console.WriteLine("=========================================");
- // 很酷的寫法
- var ageGroups3 = from row in table.Rows.Cast<DataRow>() group row by Convert.ToInt32(row["Age"]) into resultCollection select resultCollection;
- foreach (var group in ageGroups3)
- {
- Console.WriteLine("{0}, {1}", group.Key.ToString(), group.Count().ToString());
- }
- Console.WriteLine("=========================================");
- // 比較BT的寫法
- foreach (var group in from row in table.Rows.Cast<DataRow>() group row by Convert.ToInt32(row["Age"]) into resultCollection select resultCollection)
- {
- Console.WriteLine("{0}, {1}", group.Key.ToString(), group.Count().ToString());
- }
- }
- }
- }
其中最核心的一句是:var ageGroups = table.Rows.Cast<DataRow>().GroupBy(row => Convert.ToInt32(row["Age"]));
意思是告訴LINQ解析器說“請以Convert.ToInt32(row["Age"])爲Key,對row們進行分組,並把這些組放在名爲ageGroups的集合中去”。爲了不讓程序記憶太多的數據類型,C# 3.0提供了var隱式數據類型語法——程序員可以不知道是什麼類型、但編譯器對類型卻清清楚楚。實際上,GroupBy()操作後的結果是一個以IGrouping<int, DataRow> 爲元素的IEnumerable<IGrouping<int, DataRow>> 集合。
因爲GroupBy()操作正好對應有LINQ關鍵字,所以纔有最近兩種改寫。並不是每個LINQ操作都有對應的LINQ關鍵字,不知道C# 4.0會不會有所擴展。
BTW,因爲LINQ操作只能應用在可枚舉的集合類型上,而DataTable.Rows集合是個普通集合、不具有可枚舉性,所以需要Cast一下。
如果這時候客戶的需求再改成:列出Age相同的人的Name,那麼使用foreach循環的複雜度就有點兒失控了(估計程序員的情緒也比較失控)……而使用LINQ則只需要對每個group進行一下枚舉。
LINQ語法適合與以下兩種情況:
- 懶人,就像我一樣
- 工資與效率掛鉤
OVER