欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# List实现行转列的通用方案

程序员文章站 2023-11-18 20:07:58
最近在做报表统计方面的需求,涉及到行转列报表。根据以往经验使用sql可以比较容易完成,这次决定挑战一下直接通过代码方式完成行转列。期间遇到几个问题和用到的新知识这里整理记录...

最近在做报表统计方面的需求,涉及到行转列报表。根据以往经验使用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;
 }

C# List实现行转列的通用方案

标红部分使用了system.linq.dynamic动态分组功能,传入字符串即可分组。使用了dynamic类型,关于dynamic介绍可以参考其它文章介绍哦。

system.linq.dynamic其它用法

上面行转列代码见识了system.linq.dynamic的强大,下面再介绍一下会在开发中用到的方法。

where过滤

list.where("name=@0", "张三")

C# List实现行转列的通用方案

上面用到了参数化查询,实现了查找姓名是张三的数据,通过这段代码你或许感受不到它的好处。但是和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("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

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!