内容简介:網友發問:「 LINQ 可以對物件陣列進行 Pivot 轉換嗎?」 Sure, Of Course, Why Not?對 Pivot 印象模糊的同學可先溫習這篇:假設原始資料長這樣:
網友發問:「 LINQ 可以對物件陣列進行 Pivot 轉換嗎?」 Sure, Of Course, Why Not?
對 Pivot 印象模糊的同學可先溫習這篇: Using PIVOT In SQL 2005 ,而本文也直接用該文的 Log 統計當例子。
假設原始資料長這樣:
LogTime StatusCode Cnt -------- ---------- ----------- 06:40:05 200 5 06:40:05 302 1 06:40:06 304 1 06:40:06 200 10 06:40:07 200 5 06:40:07 500 3 06:40:08 200 11 06:40:08 404 1
我們希望以時間為 Key,將資料整合成一秒鐘一筆,欄位則為 200、302、304... 等各狀態出現的次數。
LogTime 200 302 304 401 404 500 -------- ----------- ----------- ----------- ----------- ----------- ----------- 06:40:05 5 1 NULL NULL NULL NULL 06:40:06 10 NULL 1 NULL NULL NULL 06:40:07 5 NULL NULL NULL NULL 3 06:40:08 11 NULL NULL NULL 1 NULL
要用 LINQ 達成此一效果,可先 GroupBy(o => o.LogTime) 以 LogTime 分群,再找出該群資料中狀態分別為 200、320、304... 之次數加總,例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace LinqPivot
{
class Program
{
class LogEntry
{
public string LogTime { get; set; }
public string StatusCode { get; set; }
public int Count { get; set; }
public LogEntry(string logTime, string statusCode, int count)
{
LogTime = logTime;
StatusCode = statusCode;
Count = count;
}
}
static List<LogEntry> rawData = new List<LogEntry>()
{
new LogEntry("06:40:05", "200", 5),
new LogEntry("06:40:05", "302", 1),
new LogEntry("06:40:06", "304", 1),
new LogEntry("06:40:06", "200", 10),
new LogEntry("06:40:07", "200", 5),
new LogEntry("06:40:07", "500", 3),
new LogEntry("06:40:08", "200", 11),
new LogEntry("06:40:08", "404", 1),
};
static void Main(string[] args)
{
var res = rawData.GroupBy(o => o.LogTime)
.Select(o => new
{
LogTime = o.Key,
S200 = o.Where(p => p.StatusCode == "200").Sum(p => p.Count),
S302 = o.Where(p => p.StatusCode == "302").Sum(p => p.Count),
S304 = o.Where(p => p.StatusCode == "304").Sum(p => p.Count),
S401 = o.Where(p => p.StatusCode == "401").Sum(p => p.Count),
S404 = o.Where(p => p.StatusCode == "404").Sum(p => p.Count),
S500 = o.Where(p => p.StatusCode == "500").Sum(p => p.Count)
}).ToArray();
Console.WriteLine(JsonConvert.SerializeObject(res, Formatting.Indented));
Console.ReadLine();
}
}
}
這樣就能輕鬆完成樞紐轉換:
[
{
"LogTime": "06:40:05",
"S200": 5,
"S302": 1,
"S304": 0,
"S401": 0,
"S404": 0,
"S500": 0
},
{
"LogTime": "06:40:06",
"S200": 10,
"S302": 0,
"S304": 1,
"S401": 0,
"S404": 0,
"S500": 0
},
{
"LogTime": "06:40:07",
"S200": 5,
"S302": 0,
"S304": 0,
"S401": 0,
"S404": 0,
"S500": 3
},
{
"LogTime": "06:40:08",
"S200": 11,
"S302": 0,
"S304": 0,
"S401": 0,
"S404": 1,
"S500": 0
}
]
若嫌寫死 SXXX = o.Where(p => p.StatusCode == "XXX").Sum(p => p.Count) 太醜,且項目一有變動得改程式太麻煩。沒關係,C# 的武器很多,我們可以招喚 ExpandoObject 上場殺敵。(延伸閱讀: 既然要動態就動個痛快 - ExpandoObject )
static void Main(string[] args)
{
var allCols = rawData.Select(o => o.StatusCode).Distinct().OrderBy(o => o).ToList();
var res = rawData.GroupBy(o => o.LogTime)
.Select(o =>
{
dynamic d = new ExpandoObject();
d.LogTime = o.Key;
var dict =
d as IDictionary<string, object>;
allCols.ForEach(c =>
{
dict["S" + c] = o.Where(p => p.StatusCode == c).Sum(p => p.Count);
});
return d;
}).ToArray();
Console.WriteLine(JsonConvert.SerializeObject(res, Formatting.Indented));
Console.ReadLine();
}
這樣子,SXXX 項目會依資料裡出現過的種類自動生成,讓程式再清爽一些,日後若增加新項目也不用改程式。(但這做法有個缺點,因資料未出現 StatusCode = '404',故結果不會包含 S404。如規格要求不管有無資料都要維持固定項目,可改成 var allCols = "200,302,304,401,404,500".Split(',').ToList()
)
[
{
"LogTime": "06:40:05",
"S200": 5,
"S302": 1,
"S304": 0,
"S404": 0,
"S500": 0
},
{
"LogTime": "06:40:06",
"S200": 10,
"S302": 0,
"S304": 1,
"S404": 0,
"S500": 0
},
{
"LogTime": "06:40:07",
"S200": 5,
"S302": 0,
"S304": 0,
"S404": 0,
"S500": 3
},
{
"LogTime": "06:40:08",
"S200": 11,
"S302": 0,
"S304": 0,
"S404": 1,
"S500": 0
}
]
等等,用 JSON 顯示就想了事,別打混,給我老老實實劇一張像 SQL 查詢結果的表格!
好吧,請看示範,一樣靠 LINQ 打通關。DumpResult() 函式接收 IEnumerable<object> 作為輸入參數,強型別類別或 ExpandoObject 通知,強型別物件用 Reflection .GetProperties() 列舉屬性名及取得屬性內容,ExpandoObject 則轉為 IDictionary<string, object> 進行操作。 為了使結果更美觀,我還加入依欄位最長內容自動調欄寬功能,邏輯有點小複雜,但照樣難不倒 LINQ:
static void Main(string[] args)
{
var allCols = rawData.Select(o => o.StatusCode).Distinct().OrderBy(o => o).ToList();
var res1 = rawData.GroupBy(o => o.LogTime)
.Select(o => new
{
LogTime = o.Key,
S200 = o.Where(p => p.StatusCode == "200").Sum(p => p.Count),
S302 = o.Where(p => p.StatusCode == "302").Sum(p => p.Count),
S304 = o.Where(p => p.StatusCode == "304").Sum(p => p.Count),
S401 = o.Where(p => p.StatusCode == "401").Sum(p => p.Count),
S404 = o.Where(p => p.StatusCode == "404").Sum(p => p.Count),
S500 = o.Where(p => p.StatusCode == "500").Sum(p => p.Count)
}).ToArray();
DumpResult(res1);
Console.WriteLine();
var res2 = rawData.GroupBy(o => o.LogTime)
.Select(o =>
{
dynamic d = new ExpandoObject();
d.LogTime = o.Key;
var dict =
d as IDictionary<string, object>;
allCols.ForEach(c =>
{
dict["S" + c] = o.Where(p => p.StatusCode == c).Sum(p => p.Count);
});
return d;
}).ToArray();
DumpResult(res2);
Console.ReadLine();
}
static void DumpResult(IEnumerable<object> result)
{
if (result == null || !result.Any())
Console.WriteLine("No Data");
var isExpando = result.First() is IDictionary<string, object>;
Dictionary<string, int> cols;
Func<object, string, string> getPropValue;
if (isExpando)
{
cols = (result.First() as IDictionary<string, object>)
.Keys.ToDictionary(o => o, o => o.Length);
getPropValue = (obj, propName) =>
(obj as IDictionary<string, object>)[propName].ToString();
}
else
{
var props = result.First().GetType().GetProperties().ToDictionary(o => o.Name, o => o);
cols = props.ToDictionary(o => o.Key, o => o.Key.Length);
getPropValue = (obj, propName) => props[propName].GetValue(obj).ToString();
}
//巡迴欄位值找出最大寬度
var data = result.Select(o =>
{
return cols.Keys.ToList().Select(c =>
{
var value = getPropValue(o, c);
cols[c] = Math.Max(cols[c], value.Length);
return value;
}).ToArray();
}).ToList();
Console.WriteLine(string.Join(" ", cols.Select(o => o.Key.PadLeft(o.Value))));
Console.WriteLine(string.Join(" ", cols.Select(o => new string('-', o.Value))));
var colWidths = cols.Values.ToArray();
foreach (var row in data)
{
var idx = 0;
Console.WriteLine(string.Join(" ",
row.Select(o => o.PadLeft(colWidths[idx++])).ToArray()));
}
}
實測結果,完美!
LogTime S200 S302 S304 S401 S404 S500 -------- ---- ---- ---- ---- ---- ---- 06:40:05 5 1 0 0 0 0 06:40:06 10 0 1 0 0 0 06:40:07 5 0 0 0 0 3 06:40:08 11 0 0 0 1 0 LogTime S200 S302 S304 S404 S500 -------- ---- ---- ---- ---- ---- 06:40:05 5 1 0 0 0 06:40:06 10 0 1 0 0 06:40:07 5 0 0 0 3 06:40:08 11 0 0 1 0
前一篇 LINQ 推坑文發表後,不少讀者迴響都集中在用 LINQ 查詢資料庫的效能問題,我還是想強調,把 LINQ 侷限於查詢資料庫是件很可惜的事,接連這兩篇文章的重點都在如何活用 LINQ 操作 .NET 物件、陣列取代繁瑣的 for 迴圈及資料轉換比, 寫出更簡潔的程式。跟 Regular Expression 一樣,LINQ 有點難度需要學習,但上手後絕對值回票價。
回到 LINQ 查詢資料庫的效能上,我認為 LINQ 查資料庫效能不佳的狀況並非無法避免,只是得累積足夠的知識經驗(意思是要知道很多眉角),我個人偏好直接控制 SQL 語法而非透過 LINQ 操作,基本上是一種取捨,犠牲跨資料庫優勢及學習底層 SQL 語法的心力換取對 SQL 效能的直接掌控。這部分在舊文 閒聊:用 LINQ 還是自己寫 SQL? 有更詳細的闡述,有興趣的朋友可以參考。
該不該用 LINQ 查詢資料庫見仁見智,但放著 LINQ 不用徒手寫迴圈搞物件肯定不智,推薦大家趕快上車。
To answering reader's question, this article demostrates how to use LINQ to convert object array to pivot table.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
创业之初你不可不知的融资知识
桂曙光 / 机械工业出版社 / 2010-6-1 / 48.00元
从零到精通 成功融资必读书 像小说一样好看的趣味融资书 手把手教你找到VC拿到钱 本书以创业者寻找风险投资的逻辑顺序为主线,运用理论分析和实例剖析相结合的手法,将简洁、通俗的语言与丰富的图表工具相结合,辅以中肯的建议,同时运用大量鲜活的、有代表性的成败案例,为读者解读创业之初企业有效成功融资的途径和方法,帮助你的企业开创新的辉煌。一起来看看 《创业之初你不可不知的融资知识》 这本书的介绍吧!