2024年1月16日 星期二

[C#]LINQ寫法與SQL的對應

        以前寫LINQ時總是沒辦法寫得很順,一直覺得寫SQL還較來的方便快速。但最近碰到客戶因為在DB端塞太多商業邏輯(SP、FUN等),導致DB Server效能有點差,因此希望盡量減少SQL的複雜程度,讓AP端也幫忙處理一些邏輯。藉此我練習許多LINQ寫法,也多虧這次經驗我開始對LINQ熟絡起來。

簡單案例:where、orderby

//目標:抓出大於5的數並排序
int[] iArray = { 2, 6, 4, 3, 1, 0, 5, 7, 9, 8 };
//Query syntax:
IEnumerable<int> EnuIResult1 = from n in iArray
                           where n > 5
                           orderby n
                           select n;
                            
//Method syntax:
var EnuIResult2 = iArray.Where(i => (i > 5 )).OrderBy(i => i); 

雖然以上兩種寫法(EnuIResult1、EnuIResult2)得到的結果是一樣,但我個人比較喜歡Method syntax,且在處理複雜的邏輯也比較方便。


複雜案例:GroupJoin(Left Join)、GroupBy、Select
首先是測試資料

DataTable dt1 = new DataTable();
dt1.Columns.Add("學生ID", typeof(int));
dt1.Columns.Add("小考科目", typeof(string));
dt1.Columns.Add("小考分數", typeof(int));

dt1.Rows.Add(new object[] { 1, "國文", 90 });
dt1.Rows.Add(new object[] { 1, "數學", 86 });
dt1.Rows.Add(new object[] { 1, "自然", 78 });
dt1.Rows.Add(new object[] { 2, "國文", 83 });
dt1.Rows.Add(new object[] { 2, "數學", 99 });
dt1.Rows.Add(new object[] { 2, "自然", 88 });

DataTable dt2 = new DataTable();
dt2.Columns.Add("學生ID", typeof(int));
dt2.Columns.Add("姓名", typeof(string));

dt2.Rows.Add(new object[] { 1, "張三"});
dt2.Rows.Add(new object[] { 2, "李四" });

DataTable dtResult = new DataTable();
dtResult.Columns.Add("學生ID", typeof(int));
dtResult.Columns.Add("姓名", typeof(string));
dtResult.Columns.Add("十進制", typeof(decimal));

//目標:整理並列出每位學生平均
var data = dt1.AsEnumerable()
.GroupJoin(dt2.AsEnumerable(), m=> m.Field<int>("學生ID"), d=> d.Field<int>("學生ID"), (m,d)  =>
{ //類似 LEFT JOIN(dt1為左表、dt2為右表)
    DataRow row = dtResult.NewRow();

    row["學生ID"] = m.Field<int>("學生ID");
    row["姓名"] = d.FirstOrDefault()?.Field<string>("姓名") ?? "";  //在對應dt2資料時都是用Group概念
    row["十進制"] = Convert.ToDecimal(m.Field<int>("小考分數"));
    return row;
    
})
.GroupBy(r => new {ID = r.Field<int>("學生ID") }) //把資料依照條件塞進Group裡
.Select(r =>{
    var row = dtResult.NewRow();
    row["學生ID"] = r.Key.ID;
    row["姓名"] = r.FirstOrDefault()?.Field<string>("姓名") ?? "";
    row["十進制"] = Math.Round(r.Sum(r1 => r1.Field<decimal>("十進制")) / r.Count(),2);   //算出平均
    return row;
});

dtResult = data.CopyToDataTable<DataRow>(); //轉回Table

還有較少用到 SelectMany:用途和GroupBy相反(把集合的資料展開)。

結語:之前寫LINQ不順主要還是因為把它當作SQL寫才會有卡住的感覺。
但如果用物件的方式去理解LINQ每一段就會發現合理處,寫起來也會比較順。
SQL處理的是表、LINQ處理的是物件,只要能夠去體會這點就會發現LINQ也不過是資料整理的語法糖。



沒有留言:

張貼留言