最近在做報表統計方面的需求,涉及到行轉列報表。根據以往經驗使用sql可以比較容易完成,這次決定挑戰一下直接通過代碼方式完成行轉列。期間遇到幾個問題和用到的新知識這里整理記錄一下。
閱讀目錄
- 問題介紹
- 動態linq
- system.linq.dynamic其它用法
- 總結
問題介紹
以家庭月度費用為例,可以在[name,area,month]三個維度上隨意組合進行分組,三個維度中選擇一個做為列顯示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/// <summary> /// 家庭費用情況 /// </summary> public class house { /// <summary> /// 戶主姓名 /// </summary> public string name { get ; set ; } /// <summary> /// 所屬行政區域 /// </summary> public string area { get ; set ; } /// <summary> /// 月份 /// </summary> public string month { get ; set ; } /// <summary> /// 電費金額 /// </summary> public double dfmoney { get ; set ; } /// <summary> /// 水費金額 /// </summary> public double sfmoney { get ; set ; } /// <summary> /// 燃氣金額 /// </summary> public double rqfmoney { get ; set ; } } |
戶主-月明細報表 | ||||||
戶主姓名 | 2016-01 | 2016-02 | ||||
---|---|---|---|---|---|---|
電費 | 水費 | 燃氣費 | 電費 | 水費 | 燃氣費 | |
張三 | 240.9 | 30 | 25 | 167 | 24.5 | 17.9 |
李四 | 56.7 | 24.7 | 13.2 | 65.2 | 18.9 | 14.9 |
區域-月明細報表 | ||||||
戶主姓名 | 2016-01 | 2016-02 | ||||
---|---|---|---|---|---|---|
電費 | 水費 | 燃氣費 | 電費 | 水費 | 燃氣費 | |
江夏區 | 2240.9 | 330 | 425 | 5167 | 264.5 | 177.9 |
洪山區 | 576.7 | 264.7 | 173.2 | 665.2 | 108.9 | 184.9 |
區域月份-戶明細報表 | |||||||
區域 | 月份 | 張三 | 李四 | ||||
---|---|---|---|---|---|---|---|
燃氣費 | 電費 | 水費 | 燃氣費 | 電費 | 水費 | ||
江夏區 | 2016-01 | 2240.9 | 330 | 425 | 5167 | 264.5 | 177.9 |
洪山區 | 2016-01 | 576.7 | 264.7 | 173.2 | 665.2 | 108.9 | 184.9 |
江夏區 | 2016-02 | 3240.9 | 430 | 525 | 6167 | 364.5 | 277.9 |
洪山區 | 2016-02 | 676.7 | 364.7 | 273.2 | 765.2 | 208.9 | 284.9 |
現在后臺查出來的數據是list<house>類型,前臺傳過來分組維度和動態列字段。
第1個表格前臺傳給后臺參數
{dimensionlist:['name'],dynamiccolumn:'month'}
第2個表格前臺傳給后臺參數
{dimensionlist:['area'],dynamiccolumn:'month'}
第3個表格前臺傳給后臺參數
{dimensionlist:['area','month'],dynamiccolumn:'name'}
問題描述清楚后,仔細分析后你就會發現這里的難題在于動態分組,也就是怎么根據前臺傳過來的多個維度對list進行分組。
動態linq
下面使用system.linq.dynamic完成行轉列功能,nuget上搜索system.linq.dynamic即可下載該包。
代碼進行了封裝,實現了通用的list<t>行轉列功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
/// <summary> /// 動態linq方式實現行轉列 /// </summary> /// <param name="list">數據</param> /// <param name="dimensionlist">維度列</param> /// <param name="dynamiccolumn">動態列</param> /// <returns>行轉列后數據</returns> private static list<dynamic> dynamiclinq<t>(list<t> list, list< string > dimensionlist, string dynamiccolumn, out list< string > alldynamiccolumn) where t : class { //獲取所有動態列 var columngroup = list.groupby(dynamiccolumn, "new(it as vm)" ) as ienumerable<igrouping<dynamic, dynamic>>; list< string > allcolumnlist = new list< string >(); foreach (var item in columngroup) { if (! string .isnullorempty(item.key)) { allcolumnlist.add(item.key); } } alldynamiccolumn = allcolumnlist; var dictfunc = new dictionary< string , func<t, bool >>(); foreach (var column in allcolumnlist) { var func = dynamicexpression.parselambda<t, bool >( string .format( "{0}==\"{1}\"" , dynamiccolumn, column)).compile(); dictfunc[column] = func; } //獲取實體所有屬性 dictionary< string , propertyinfo> propertyinfodict = new dictionary< string , propertyinfo>(); type type = typeof (t); var propertyinfos = type.getproperties(bindingflags.instance | bindingflags. public ); //數值列 list< string > allnumberfield = new list< string >(); foreach (var item in propertyinfos) { propertyinfodict[item.name] = item; if (item.propertytype == typeof ( int ) || item.propertytype == typeof ( double ) || item.propertytype == typeof ( float )) { allnumberfield.add(item.name); } } //分組 var datagroup = list.groupby( string .format( "new ({0})" , string .join( "," , dimensionlist)), "new(it as vm)" ) as ienumerable<igrouping<dynamic, dynamic>>; list<dynamic> listresult = new list<dynamic>(); idictionary< string , object > itemobj = null ; t vm2 = default (t); foreach (var group in datagroup) { itemobj = new expandoobject(); var listvm = group.select(e => e.vm as t).tolist(); //維度列賦值 vm2 = listvm.firstordefault(); foreach (var key in dimensionlist) { itemobj[key] = propertyinfodict[key].getvalue(vm2); } foreach (var column in allcolumnlist) { vm2 = listvm.firstordefault(dictfunc[column]); if (vm2 != null ) { foreach ( string name in allnumberfield) { itemobj[name + column] = propertyinfodict[name].getvalue(vm2); } } } listresult.add(itemobj); } return listresult; } |
標紅部分使用了system.linq.dynamic動態分組功能,傳入字符串即可分組。使用了dynamic類型,關于dynamic介紹可以參考其它文章介紹哦。
system.linq.dynamic其它用法
上面行轉列代碼見識了system.linq.dynamic的強大,下面再介紹一下會在開發中用到的方法。
where過濾
list.where("name=@0", "張三")
上面用到了參數化查詢,實現了查找姓名是張三的數據,通過這段代碼你或許感受不到它的好處。但是和entityframework結合起來就可以實現動態拼接sql的功能了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
/// <summary> /// ef實體查詢封裝 /// </summary> /// <typeparam name="t">實體類型</typeparam> /// <param name="query">iqueryable對象</param> /// <param name="gridparam">過濾條件</param> /// <returns>查詢結果</returns> public static efpaginationresult<t> pagequery<t>( this iqueryable<t> query, querycondition gridparam) { //查詢條件 effilter filter = getparametersql<t>(gridparam); var query = query.where(filter.filter, filter.listargs.toarray()); //查詢結果 efpaginationresult<t> result = new efpaginationresult<t>(); if (gridparam.ispagination) { int pagesize = gridparam.pagesize; int pageindex = gridparam.pageindex < 0 ? 0 : gridparam.pageindex; //獲取排序信息 string sort = getsort(gridparam, typeof (t).fullname); result.data = query.orderby(sort).skip(pageindex * pagesize).take(pagesize).tolist<t>(); if (gridparam.iscalctotal) { result.total = query.count(); result.totalpage = convert.toint32(math.ceiling(result.total * 1.0 / pagesize)); } else { result.total = result.data.count(); } } else { result.data = query.tolist(); result.total = result.data.count(); } return result; } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
/// <summary> /// 通過查詢條件,獲取參數化查詢sql /// </summary> /// <param name="gridparam">過濾條件</param> /// <returns>過濾條件字符</returns> private static effilter getparametersql<t>(querycondition gridparam) { effilter result = new effilter(); //參數值集合 list< object > listargs = new list< object >(); string filter = "1=1" ; #region "處理動態過濾條件" if (gridparam.filterlist != null && gridparam.filterlist.count > 0) { stringbuilder sb = new stringbuilder(); int paramcount = 0; datetime datetime; //操作符 string stroperator = string .empty; foreach (var item in gridparam.filterlist) { //字段名稱為空則跳過 if ( string .isnullorempty(item.fieldname)) { continue ; } //匹配枚舉,防止sql注入 operator operatorenum = ( operator ) enum .parse( typeof ( operator ), item. operator , true ); //跳過字段值為空的 if (operatorenum != operator . null && operatorenum != operator .notnull && string .isnullorempty(item.fieldvalue)) { continue ; } stroperator = operatorenum.getdescription(); if (item.ignorecase && !item.isdatetime) { //2016-07-19添加查詢時忽略大小寫比較 item.fieldvalue = item.fieldvalue.tolower(); item.fieldname = string .format( "{0}.tolower()" , item.fieldname); } switch (operatorenum) { //等于,不等于,小于,大于,小于等于,大于等于 case operator .eq: case operator .ne: case operator .gt: case operator .ge: case operator .lt: case operator .le: if (item.isdatetime) { if (datetime.tryparse(item.fieldvalue, out datetime)) { if (!item.fieldvalue.contains( "00:00:00" ) && datetime.tostring( "hh:mm:ss" ) == "00:00:00" ) { if (operatorenum == operator .le) { listargs.add(datetime.parse(datetime.tostring( "yyyy-mm-dd" ) + " 23:59:59" )); } else { listargs.add(datetime); } } else { listargs.add(datetime); } sb.appendformat( " and {0} {1} @{2}" , item.fieldname, stroperator, paramcount); } } else { listargs.add(converttotype(item.fieldvalue, getproptype<t>(item.fieldname))); sb.appendformat( " and {0} {1} @{2}" , item.fieldname, stroperator, paramcount); } paramcount++; break ; case operator .like: case operator .notlike: case operator .llike: case operator .rlike: listargs.add(item.fieldvalue); if (operatorenum == operator .like) { sb.appendformat( " and {0}.contains(@{1})" , item.fieldname, paramcount); } else if (operatorenum == operator .notlike) { sb.appendformat( " and !{0}.contains(@{1})" , item.fieldname, paramcount); } else if (operatorenum == operator .llike) { sb.appendformat( " and {0}.endswith(@{1})" , item.fieldname, paramcount); } else if (operatorenum == operator .rlike) { sb.appendformat( " and {0}.startswith(@{1})" , item.fieldname, paramcount); } paramcount++; break ; case operator . null : listargs.add(item.fieldvalue); sb.appendformat( " and {0}=null" , item.fieldname); paramcount++; break ; case operator .notnull: listargs.add(item.fieldvalue); sb.appendformat( " and {0}!=null" , item.fieldname); paramcount++; break ; case operator . in : sb.appendformat( " and (" ); foreach (var schar in item.fieldvalue.split( ',' )) { listargs.add(schar); sb.appendformat( "{0}=@{1} or " , item.fieldname, paramcount); paramcount++; } sb.remove(sb.length - 3, 3); sb.appendformat( " )" ); break ; case operator .notin: sb.appendformat( " and (" ); foreach (var schar in item.fieldvalue.split( ',' )) { listargs.add(schar); sb.appendformat( "{0}!=@{1} and " , item.fieldname, paramcount); paramcount++; } sb.remove(sb.length - 3, 3); sb.appendformat( " )" ); break ; } if (sb.tostring().length > 0) { filter = sb.tostring().substring(4, sb.length - 4); } } #endregion } result.filter = filter; result.listargs = listargs; return result; } |
總結
本篇通過行轉列引出了system.linq.dynamic,并且介紹了過濾功能,其實它的用處還有很多,等待大家發掘。下面給出本文示例代碼:dynamiclinq
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持服務器之家!
原文鏈接:http://www.cnblogs.com/yanweidie/p/6485957.html