热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

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

本篇通过行转列引出了System.Linq.Dynamic,并且介绍了过滤功能,具有很好的参考价值。下面跟着小编一起来看下吧

最近在做报表统计方面的需求,涉及到行转列报表。根据以往经验使用SQL可以比较容易完成,这次决定挑战一下直接通过代码方式完成行转列。期间遇到几个问题和用到的新知识这里整理记录一下。

阅读目录

  • 问题介绍
  • 动态Linq
  • System.Linq.Dynamic其它用法
  • 总结

问题介绍

以家庭月度费用为例,可以在[Name,Area,Month]三个维度上随意组合进行分组,三个维度中选择一个做为列显示。

/// 
 /// 家庭费用情况
 /// 
 public class House
 {
 /// 
 /// 户主姓名
 /// 
 public string Name { get; set; }
 /// 
 /// 所属行政区域
 /// 
 public string Area { get; set; }
 /// 
 /// 月份
 /// 
 public string Month { get; set; }
 /// 
 /// 电费金额
 /// 
 public double DfMoney { get; set; }
 /// 
 /// 水费金额
 /// 
 public double SfMoney { get; set; }
 /// 
 /// 燃气金额
 /// 
 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类型,前台传过来分组维度和动态列字段。

第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行转列功能。

/// 
 /// 动态Linq方式实现行转列
 /// 
 /// 数据
 /// 维度列
 /// 动态列
 /// 行转列后数据
 private static List DynamicLinq(List list, List DimensionList, string DynamicColumn, out List AllDynamicColumn) where T : class
 {
 //获取所有动态列
 var columnGroup = list.GroupBy(DynamicColumn, "new(it as Vm)") as IEnumerable>;
 List AllColumnList = new List();
 foreach (var item in columnGroup)
 {
 if (!string.IsNullOrEmpty(item.Key))
 {
  AllColumnList.Add(item.Key);
 }
 }
 AllDynamicColumn = AllColumnList;
 var dictFunc = new Dictionary>();
 foreach (var column in AllColumnList)
 {
 var func = DynamicExpression.ParseLambda(string.Format("{0}==\"{1}\"", DynamicColumn, column)).Compile();
 dictFunc[column] = func;
 }
 //获取实体所有属性
 Dictionary PropertyInfoDict = new Dictionary();
 Type type = typeof(T);
 var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
 //数值列
 List AllNumberField = new List();
 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>;
 List listResult = new List();
 IDictionary 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的功能了。

/// 
 /// EF实体查询封装 
 /// 
 /// 实体类型
 /// IQueryable对象
 /// 过滤条件
 /// 查询结果
 public static EFPaginationResult PageQuery(this IQueryable Query, QueryCondition gridParam)
 {
 //查询条件
 EFFilter filter = GetParameterSQL(gridParam);
 var query = Query.Where(filter.Filter, filter.ListArgs.ToArray());
 //查询结果
 EFPaginationResult result = new EFPaginationResult();
 if (gridParam.IsPagination)
 {
 int PageSize = gridParam.PageSize;
 int PageIndex = gridParam.PageIndex <0 &#63; 0 : gridParam.PageIndex;
 //获取排序信息
 string sort = GetSort(gridParam, typeof(T).FullName);
 result.Data = query.OrderBy(sort).Skip(PageIndex * PageSize).Take(PageSize).ToList();
 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;
 }
/// 
 /// 通过查询条件,获取参数化查询SQL
 /// 
 /// 过滤条件
 /// 过滤条件字符
 private static EFFilter GetParameterSQL(QueryCondition gridParam)
 {
 EFFilter result = new EFFilter();
 //参数值集合
 List listArgs = new List();
 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(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

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


推荐阅读
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 延迟注入工具(python)的SQL脚本
    本文介绍了一个延迟注入工具(python)的SQL脚本,包括使用urllib2、time、socket、threading、requests等模块实现延迟注入的方法。该工具可以通过构造特定的URL来进行注入测试,并通过延迟时间来判断注入是否成功。 ... [详细]
  • 本文介绍了一个免费的asp.net控件,该控件具备数据显示、录入、更新、删除等功能。它比datagrid更易用、更实用,同时具备多种功能,例如属性设置、数据排序、字段类型格式化显示、密码字段支持、图像字段上传和生成缩略图等。此外,它还提供了数据验证、日期选择器、数字选择器等功能,以及防止注入攻击、非本页提交和自动分页技术等安全性和性能优化功能。最后,该控件还支持字段值合计和数据导出功能。总之,该控件功能强大且免费,适用于asp.net开发。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 本文详细介绍了Mybatis中#与$的区别及其作用。#{}可以防止sql注入,拼装sql时会自动添加单引号,适用于单个简单类型的形参。${}则将拿到的值直接拼装进sql,可能会产生sql注入问题,需要手动添加单引号,适用于动态传入表名或字段名。#{}可以实现preparedStatement向占位符中设置值,自动进行类型转换,有效防止sql注入,提高系统安全性。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 乐山市计算机学校2017—2018学年度第一学期开学典礼隆重举行
    乐山市计算机学校于2017—2018学年度第一学期举行了隆重的开学典礼,全体教职工和学生参加了此次典礼。乐山市计算机学校自建校以来一直秉承着追求崇高、抓住机遇、回报社会的办学宗旨,取得了累累硕果。在典礼上,常务副校长梁志明发表了致辞,鼓励全体新生用自己的智慧和勤奋去创造优秀的业绩。同时,苏稽镇派出所所长、市计算机学校法制副校长邹学斌提出了关于遵守法律法规和社会公共道德规范、树立自尊、自律、自强意识以及相信和依靠法律的建议,以维护校园秩序的平安和谐。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 本文介绍了互联网思维中的三个段子,涵盖了餐饮行业、淘品牌和创业企业的案例。通过这些案例,探讨了互联网思维的九大分类和十九条法则。其中包括雕爷牛腩餐厅的成功经验,三只松鼠淘品牌的包装策略以及一家创业企业的销售额增长情况。这些案例展示了互联网思维在不同领域的应用和成功之道。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
author-avatar
boybeta
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有