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

阅读目录

问题介绍

  以家庭月度费用为例,可以在[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("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

C#List实现行转列的更多相关文章

  1. SQL Server 动态行转列(参数化表名、分组列、行转列字段、字段值)

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现代码(SQL Codes) 方法一:使用拼接SQL,静态列字段: 方法二:使用拼接SQL, ...

  2. T-SQL 实现行转列

    问题: 我正在寻找一种有效的方式将行转换为SQL服务器中的列 例如,通过下表如何构建出预期结果表. Id  Value   ColumnName 1   John    FirstName 2   2 ...

  3. Oracle行转列、列转行的Sql语句总结

    多行转字符串 这个比较简单,用||或concat函数可以实现  SQL Code  12    select concat(id,username) str from app_userselect i ...

  4. sql的行转列(PIVOT)与列转行(UNPIVOT)

    在做数据统计的时候,行转列,列转行是经常碰到的问题.case when方式太麻烦了,而且可扩展性不强,可以使用 PIVOT,UNPIVOT比较快速实现行转列,列转行,而且可扩展性强 一.行转列 1.测 ...

  5. 做图表统计你需要掌握SQL Server 行转列和列转行

    说在前面 做一个数据统计和分析的项目,每天面对着各种数据,经过存储过程从源表计算汇总后需要写入中间结果表以提高数据使用效率,那么此时就需要用到行转列和列转行. 1.列转行 数据经过计算加工后会直接生成 ...

  6. SQL SERVER特殊行转列案列一则

    今天有个同事找我,他说他有个需求,需要进行行转列,但是又跟一般的行转列有些区别,具体需求如下所说,需要将表1的数据转换为表2的显示格式. 我想了一下,给出了一个解决方法,具体如下所示(先给出测试数据) ...

  7. SQL Server中使用PIVOT行转列

    使用PIVOT行转列 1.建表及插入数据 USE [AdventureDB] GO /****** Object: Table [dbo].[Score] Script Date: 11/25/201 ...

  8. oracle行转列与列转行

    一.行转列 在有些应用场景中,如学生成绩.学生所有科目的成绩都保存到一张表里面,当我们需要以列表的形式显示出学生所对应的每一科目的成绩的时候,需要使用到行转列. 示例 -- 学生成绩表 create ...

  9. Oracle行转列、列转行的Sql语句总结(转)

    多行转字符串 这个比较简单,用||或concat函数可以实现 select concat(id,username) str from app_userselect id||username str f ...

  10. ORACLE行转列通用过程

    create or replace procedure row_to_col(tabname in varchar2,                                   group_ ...

随机推荐

  1. (转)c++ 中的using namespace std是什么意思,什么时候用

    使用std命名空间 98年以后的c++语言提供一个全局的命名空间namespace,可以避免导致全局命名冲突问题.举一个实例,请注意以下两个头文件: // one.hchar func(char);c ...

  2. (转)const char to LPCTSTR不能转化问题

    转: const char to LPCTSTR不能转化问题 Visual C++ 2008里cannot convert parameter 1 from 'const char [13]' to ...

  3. 9.6 Binder系统_驱动情景分析_server的多线程实现

    当多个client对server发出请求的时候,如果server忙不过来的时候会创建多线程来处理请求 那么忙不过来由谁来判断? server进程有个binder_proc结构体,其里面有todo链表( ...

  4. 5、regulator系统的概念及测试

    概念:Regulator : 电源芯片, 比如电压转换芯片Consumer : 消费者,使用电源的部件, Regulator是给Consumer供电的machine : 单板,上面焊接有Regulat ...

  5. 35、在JZ2440上使用3G上网卡

    1. 简单使用:1.1 选型:中国联通:E网时空 EW65 (64元), ZTE中兴 MF637U (160多)中国电信:Benton/本腾 EQ10B (35元)中国移动:华为 ET128 (99元 ...

  6. Python爬虫之路——简单的网页抓图

    转载自我自己的博客:http://www.mylonly.com/archives/1401.html 用Python的urllib2库和HTMLParser库写了一个简单的抓图脚本.主要抓的是htt ...

  7. 如何知道刚刚插入数据库那条数据的id

    如何知道刚刚插入数据库那条数据的id 一.总结 一句话总结:这些常见功能各个框架里面都有,可以查看手册,thinkphp里面是$userId = Db::name('user')->getLas ...

  8. 从Lua调用C

    从Lua调用C: 方式:C函数从栈中获取函数參数(第一个參数总是局部栈的索引1),将结果压入栈中,C函数须要返回结果数量. 每一个函数都有自己的局部私有栈 样例: static int l_sin(l ...

  9. [RxJS] Convert RxJS Subjects to Observables

    The use of RxJS Subjects is common, but not without problems. In this lesson we will see how they ca ...

  10. java生成UUID通用唯一识别码 (Universally Unique Identifier) 分类: B1_JAVA 2014-08-22 16:09 331人阅读 评论(0) 收藏

    转自:http://blog.csdn.net/carefree31441/article/details/3998553 UUID含义是通用唯一识别码 (Universally Unique Ide ...