最近在做报表统计方面的需求,涉及到行转列报表。根据以往经验使用sql可以比较容易完成,这次决定挑战一下直接通过代码方式完成行转列。期间遇到几个问题和用到的新知识这里整理记录一下。
阅读目录
问题介绍 动态linq system.linq.dynamic其它用法 总结问题介绍
以家庭月度费用为例,可以在[name,area,month]三个维度上随意组合进行分组,三个维度中选择一个做为列显示。
/// <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>行转列功能。
/// <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的功能了。
/// <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;
}
/// <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( "0" ) && datetime.tostring( "hh:mm:ss" ) == "0" )
{
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
dy("nrwz");
查看更多关于C# List实现行转列的通用方案的详细内容...