分享一个动态生成RDLC报表的类
在实际工作中,当需要进行大批量查询和生成报表的时候,可以使用我写的类。
特点:
- 无需报表设计器、无需为报表设置数据集
- 只需要传入查询结果就可以全自动生成报表,传入的对象为Dynamic(目前支持DataTable和IEnumable<T>的传入参数)
- 文字、数据表可以无限添加
- 支持图表 ( 2014-5-28 v0.4 增加参数类,完成图表显示功能)
- 支持数据分组(2014-5-19 v0.3 添加表格内分组)
我没有采用使用操纵微软报表Schema的方法,而是用了拼接字符串:(
将来想到的扩展功能有:
- 支持子报表
更新日志:
2014-5-15 更新,将匿名类转换成DataTable,然后去除0和0.00%等字段。
* 2014-5-19 v0.3 添加表格内分组
* 2014-5-28 v0.4 增加参数类,开始做图表显示功能
源码:
接口:
using System.Text; using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; using Microsoft.Reporting.WebForms; public interface IDynamicReport { void SetReport(ReportViewer reportViewer); void AddData<T>(IEnumerable<T> data); void AddData(DataTable dataTable); void ShowReport(); void LoadReport(string reportPath); void SetColoumStyle(List<ReportColoumStyle> coloumStyle); void AddText(string title); }
辅助类和枚举:
public class ReportColoumStyle { public string ColoumName { get; set; } public float ColoumWidth { get; set; } public TextAlign TextAlign { get; set; } public ReportColoumStyle() { ColoumWidth = DynamicReport.ColoumWidth; } } public enum TextAlign { Left, Center, Right } public enum ReportType { Tables, Chart, Finally } internal enum DataType { DataTable, Enumerable } internal class ReportItemPattern { public string DataSetName { get; set; } public string DataSetString { get; set; } public string TablixString { get; set; } public dynamic Data { get; set; } public string DataSetPattern { get { return " <DataSet Name=\"@DataSetNameData\">" + " <Fields>@Fields</Fields>" + " <Query>" + " <DataSourceName>DummyDataSource</DataSourceName>" + " <CommandText />" + " </Query>" + " </DataSet>"; } } public string TablixPattern { get { return " <Tablix Name=\"Tablix@DataSetName\">" + " <TablixBody>" + " <TablixColumns>@TablixColumns</TablixColumns>" + " <TablixRows>" + " <TablixRow>" + " <Height>0.23622in</Height>" + " <TablixCells>@TablixHeader</TablixCells>" + " </TablixRow>" + " <TablixRow>" + " <Height>0.23622in</Height>" + " <TablixCells>@TablixCells</TablixCells>" + " </TablixRow>" + " </TablixRows>" + " </TablixBody>" + " <TablixColumnHierarchy>" + " <TablixMembers>@TablixMember</TablixMembers>" + " </TablixColumnHierarchy>" + " <TablixRowHierarchy>" + " <TablixMembers>" + " <TablixMember>" + " <KeepWithGroup>After</KeepWithGroup>" + " </TablixMember>" + " <TablixMember>" + " <Group Name=\"详细信息@DataSetName\" />" + " </TablixMember>" + " </TablixMembers>" + " </TablixRowHierarchy>" + " <DataSetName>@DataSetNameData</DataSetName>" + " <Top>@TopPositioncm</Top>" + " <Left>@LeftPostioncm</Left>" + " <Height>1.2cm</Height>" + " <Width>14.35207cm</Width>" + " <Style>" + " <Border>" + " <Style>None</Style>" + " </Border>" + " </Style>" + "</Tablix>"; } } } internal static class DynamicReportExtension { public static dynamic RemoveZeroData(this object data) { if (data is DataTable) { return ((DataTable)data).ChangeEachColumnTypeToString(); } else if (data is IEnumerable) { var _data = ((IEnumerable) data).Cast<object>(); return _data.CopyToDataTable().RemoveZeroData(); } return data; } public static DataTable ChangeEachColumnTypeToString(this DataTable dt) { DataTable tempdt = new DataTable(); foreach (DataColumn dc in dt.Columns) { DataColumn tempdc = new DataColumn(); tempdc.ColumnName = dc.ColumnName; tempdc.DataType = typeof (String); tempdt.Columns.Add(tempdc); } int coloumCount = dt.Columns.Count; foreach (DataRow dr in dt.Rows) { var newrow = tempdt.NewRow(); for (int i = 0; i < coloumCount; i ++) { var value = dr[i].ToString(); switch (value) { case "0": case "0.00%": newrow[i] = "-"; break; default: newrow[i] = value; break; } } tempdt.Rows.Add(newrow); } return tempdt; } } internal static class DataSetLinqOperators { public static DataTable CopyToDataTable<T>(this IEnumerable<T> source) { return new ObjectShredder<T>().Shred(source, null, null); } public static DataTable CopyToDataTable<T>(this IEnumerable<T> source, DataTable table, LoadOption? options) { return new ObjectShredder<T>().Shred(source, table, options); } } internal class ObjectShredder<T> { private FieldInfo[] _fi; private PropertyInfo[] _pi; private Dictionary<string, int> _ordinalMap; private Type _type; public ObjectShredder() { _type = typeof (T); _fi = _type.GetFields(); _pi = _type.GetProperties(); _ordinalMap = new Dictionary<string, int>(); } public DataTable Shred(IEnumerable<T> source, DataTable table, LoadOption ? options) { if (typeof (T).IsPrimitive) { return ShredPrimitive(source, table, options); } if (table == null) { table = new DataTable(typeof (T).Name); } // now see if need to extend datatable base on the type T + build ordinal map table = ExtendTable(table, typeof (T)); table.BeginLoadData(); using (IEnumerator<T> e = source.GetEnumerator()) { while (e.MoveNext()) { if (options != null) { table.LoadDataRow(ShredObject(table, e.Current), (LoadOption) options); } else { table.LoadDataRow(ShredObject(table, e.Current), true); } } } table.EndLoadData(); return table; } public DataTable ShredPrimitive(IEnumerable<T> source, DataTable table, LoadOption ? options) { if (table == null) { table = new DataTable(typeof (T).Name); } if (!table.Columns.Contains("Value")) { table.Columns.Add("Value", typeof (T)); } table.BeginLoadData(); using (IEnumerator<T> e = source.GetEnumerator()) { Object[] values = new object[table.Columns.Count]; while (e.MoveNext()) { values[table.Columns["Value"].Ordinal] = e.Current; if (options != null) { table.LoadDataRow(values, (LoadOption) options); } else { table.LoadDataRow(values, true); } } } table.EndLoadData(); return table; } public DataTable ExtendTable(DataTable table, Type type) { // value is type derived T, may need to extend table. foreach (FieldInfo f in type.GetFields()) { if (!_ordinalMap.ContainsKey(f.Name)) { DataColumn dc = table.Columns.Contains(f.Name) ? table.Columns[f.Name] : table.Columns.Add(f.Name, f.FieldType); _ordinalMap.Add(f.Name, dc.Ordinal); } } foreach (PropertyInfo p in type.GetProperties()) { if (!_ordinalMap.ContainsKey(p.Name)) { DataColumn dc = table.Columns.Contains(p.Name) ? table.Columns[p.Name] : table.Columns.Add(p.Name, p.PropertyType); _ordinalMap.Add(p.Name, dc.Ordinal); } } return table; } public object[] ShredObject(DataTable table, T instance) { FieldInfo[] fi = _fi; PropertyInfo[] pi = _pi; if (instance.GetType() != typeof (T)) { ExtendTable(table, instance.GetType()); fi = instance.GetType().GetFields(); pi = instance.GetType().GetProperties(); } Object[] values = new object[table.Columns.Count]; foreach (FieldInfo f in fi) { values[_ordinalMap[f.Name]] = f.GetValue(instance); } foreach (PropertyInfo p in pi) { values[_ordinalMap[p.Name]] = p.GetValue(instance, null); } return values; } }
核心类:
public class DynamicReport : IDynamicReport { #region 空白文档 /// <summary> /// 空白文档的xml文件 /// </summary> protected string _docTemplate = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Report xmlns:rd=\"http://schemas.microsoft.com/SQLServer/reporting/reportdesigner\" xmlns=\"http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition\">" + "<DataSources>" + " <DataSource Name=\"DummyDataSource\">" + " <ConnectionProperties>" + " <DataProvider>SQL</DataProvider>" + " <ConnectString />" + " </ConnectionProperties>" + " <rd:DataSourceID>3eecdab9-6b4b-4836-ad62-95e4aee65ea8</rd:DataSourceID>" + " </DataSource>" + "</DataSources>" + "<DataSets>@DataSets</DataSets>" + "<Body>" + "<ReportItems>@Title@Tablix" + "</ReportItems>" + "<Style />" + "<Height>8cm</Height>" + "</Body>" + "<Width>17cm</Width>" + "<Page>" + "<PageHeight>29.7cm</PageHeight>" + "<PageWidth>21cm</PageWidth>" + "<LeftMargin>1.8cm</LeftMargin>" + "<RightMargin>1.8cm</RightMargin>" + "<TopMargin>1.8cm</TopMargin>" + "<BottomMargin>1.8cm</BottomMargin>" + "<ColumnSpacing>0.13cm</ColumnSpacing>" + "<Style />" + "</Page>" + "<rd:ReportID>809f16cf-ea78-4469-bf43-965c4afe69d0</rd:ReportID>" + "<rd:ReportUnitType>Cm</rd:ReportUnitType>" + "</Report>"; protected string TitlePattern = " <Textbox Name=\"Textbo@TextboxName\"> " + @"<CanGrow>true</CanGrow> <KeepTogether>true</KeepTogether> <Paragraphs> <Paragraph> <TextRuns> <TextRun> <Value>@Title</Value> <Style>@FontStyle</Style> </TextRun> </TextRuns> <Style>@Style</Style> </Paragraph> </Paragraphs> <rd:DefaultName>Textbo@TextboxName</rd:DefaultName> <Top>@TopPositioncm</Top> <Left>1cm</Left> <Height>0.83813cm</Height> <Width>14.35207cm</Width> <ZIndex>1</ZIndex> <Style> <Border> <Style>None</Style> </Border> <PaddingLeft>2pt</PaddingLeft> <PaddingRight>2pt</PaddingRight> <PaddingTop>2pt</PaddingTop> <PaddingBottom>2pt</PaddingBottom> </Style> </Textbox>"; #endregion private ReportViewer _report; private List<ReportColoumStyle> _coloumStyle = new List<ReportColoumStyle>(); private List<ReportItemPattern> _reportItemPatterns = new List<ReportItemPattern>(); private List<string> _reportTitlePatterns = new List<string>(); private List<ReportItemPattern> _reportHeadPatterns = new List<ReportItemPattern>(); internal const float ColoumWidth = 1.6F; //行宽 public ReportType ReportType { get; set; } public DynamicReport() { } /// <summary> /// 从现有报表中加载报表并进行修改 /// </summary> /// <param name="url"></param> public void LoadReport(string url) { try { _docTemplate = File.ReadAllText(url); } catch (Exception ex) { } } public void SetReport(ReportViewer reportViewer) { this._report = reportViewer; } public void SetColoumStyle(List<ReportColoumStyle> coloumStyle) { this._coloumStyle = coloumStyle; } public void AddText(string text) { if (!string.IsNullOrEmpty(text)) { var pos = CaculatePlacePostion(); var titlePattern = TitlePattern .Replace("@Title", text) .Replace("@TopPosition", pos.ToString()) .Replace("@TextboxName", _reportTitlePatterns.Count.ToString()) .Replace("@FontStyle", "<FontFamily>微软雅黑</FontFamily><FontSize>12pt</FontSize>") .Replace("@Style", "<TextAlign>Center</TextAlign>"); _reportTitlePatterns.Add(titlePattern); } } public void AddText(string text, int chapterGrade) { if (!string.IsNullOrEmpty(text)) { var pos = CaculatePlacePostion(); var titlePattern = TitlePattern .Replace("@Title", text) .Replace("@TopPosition", pos.ToString()) .Replace("@TextboxName", _reportTitlePatterns.Count.ToString()); switch (chapterGrade) { case 1: titlePattern = titlePattern.Replace("@FontStyle", "<FontFamily>宋体</FontFamily><FontSize>18pt</FontSize><Color>#000000</Color>") .Replace("@Style", "<TextAlign>Center</TextAlign>"); break; case 2: titlePattern = titlePattern.Replace("@FontStyle", "<FontFamily>黑体</FontFamily><FontSize>14pt</FontSize><Color>#000000</Color>") .Replace("@Style", "<TextAlign>Left</TextAlign>"); break; case 3: titlePattern = titlePattern.Replace("@FontStyle", "<FontFamily>宋体</FontFamily><FontSize>12pt</FontSize><FontWeight>Bold</FontWeight>") .Replace("@Style", "<TextAlign>Left</TextAlign>"); break; default: case 10: titlePattern = titlePattern.Replace("@FontStyle", "<FontFamily>宋体</FontFamily><FontSize>12pt</FontSize>") .Replace("@Style", "<LineHeight>22pt</LineHeight>"); break; } _reportTitlePatterns.Add(titlePattern); } } public void AddData<T>(IEnumerable<T> data) { if (data.Count() != 0) { var properites = typeof (T).GetProperties(); //得到实体类属性的集合 AddReportItemPattern(properites.Select(p => p.Name).ToArray(), data); } } public void AddData(DataTable dataTable) { if (dataTable != null) { var coloumNames = new List<string>(); foreach (DataColumn dataColumn in dataTable.Columns) { var protertyName = dataColumn.ColumnName; coloumNames.Add(protertyName); } AddReportItemPattern(coloumNames.ToArray(), dataTable); } } /// <summary> /// 计算开始摆放的位置 /// </summary> /// <returns></returns> protected float CaculatePlacePostion() { //每个标题的高度 float titleCount = _reportTitlePatterns.Count*1f; //每个数据表的高度 float itemCount = _reportItemPatterns.Count*2f; // 每个空表头的高度 float emptyItemCount = _reportHeadPatterns.Count*0.5f; switch (ReportType) { case ReportType.Tables: return titleCount + itemCount + emptyItemCount + 0.5f; case ReportType.Chart: case ReportType.Finally: return titleCount + itemCount + emptyItemCount + 25.7f; } return 0f; } /// <summary> /// 增加一个报表 /// </summary> /// <param name="coloumNames"></param> /// <param name="data"></param> /// <param name="dataType"></param> protected void AddReportItemPattern(string[] coloumNames, dynamic data) { var fields = new StringBuilder(); var coloums = new StringBuilder(); var tablixHearders = new StringBuilder(); var tablixCells = new StringBuilder(); var tablixMembers = new StringBuilder(); var currentNamePrefix = _reportItemPatterns.Count + _reportHeadPatterns.Count + 1; var tableWidth = 0F; var dataRows = GetDataRowsCount(data); //数据行数 foreach (var coloumName in coloumNames) { var coloumWidth = ColoumWidth; var textAlign = TextAlign.Right; var reportColoumStyle = _coloumStyle.FirstOrDefault(r => r.ColoumName == coloumName); if (reportColoumStyle != null) { textAlign = reportColoumStyle.TextAlign; coloumWidth = reportColoumStyle.ColoumWidth; } tableWidth += coloumWidth; var bottomBorder = string.Empty; //每个单元格底部border if (dataRows == 0) { bottomBorder = "<BottomBorder><Style>None</Style></BottomBorder>"; } var coloumValue = coloumName; //例外,如果coloumName包含Coloum之类的字段,则将value设成空 if (coloumName.IndexOf("Column", System.StringComparison.Ordinal) > -1) { coloumValue = " "; } fields.AppendFormat( "<Field Name=\"{0}\"><DataField>{0}</DataField><rd:TypeName>System.String</rd:TypeName></Field>", coloumName); coloums.AppendFormat("<TablixColumn><Width>{0}cm</Width></TablixColumn>", coloumWidth); tablixHearders.AppendFormat("<TablixCell><CellContents>" + "<Textbox Name=\"Textbox{0}{1}\"><CanGrow>true</CanGrow><KeepTogether>true</KeepTogether><Paragraphs><Paragraph>" + "<TextRuns><TextRun><Value>{2}</Value><Style /></TextRun></TextRuns><Style><TextAlign>Center</TextAlign></Style></Paragraph></Paragraphs>" + "<rd:DefaultName>Textbox{0}{1}</rd:DefaultName><Style><Border><Color>LightGrey</Color><Style>Solid</Style></Border>{3}" + "<PaddingLeft>2pt</PaddingLeft><PaddingRight>2pt</PaddingRight><PaddingTop>2pt</PaddingTop><PaddingBottom>2pt</PaddingBottom></Style></Textbox></CellContents></TablixCell>", coloumName, currentNamePrefix, coloumValue, bottomBorder); tablixCells.AppendFormat( "<TablixCell><CellContents><Textbox Name=\"{0}{1}1\"><CanGrow>true</CanGrow><KeepTogether>true</KeepTogether>" + "<Paragraphs><Paragraph><TextRuns><TextRun><Value>=Fields!{0}.Value</Value><Style /></TextRun></TextRuns><Style><TextAlign>{2}</TextAlign></Style></Paragraph></Paragraphs>" + "<rd:DefaultName>{0}{1}1</rd:DefaultName><Style><Border><Color>LightGrey</Color><Style>Solid</Style></Border>" + "<PaddingLeft>2pt</PaddingLeft><PaddingRight>2pt</PaddingRight><PaddingTop>2pt</PaddingTop><PaddingBottom>2pt</PaddingBottom></Style></Textbox></CellContents></TablixCell>", coloumName, currentNamePrefix, textAlign); tablixMembers.AppendFormat("<TablixMember />"); } //计算表格应该离左边多少距离 var leftPosition = 0F; if (tableWidth < 17) { leftPosition = (17F - tableWidth)/2; } var dataSetName = string.Format("Data{0}", _reportItemPatterns.Count + _reportHeadPatterns.Count + 1); var reportItemPattern = new ReportItemPattern(); reportItemPattern.Data = DynamicReportExtension.RemoveZeroData(data); reportItemPattern.DataSetName = dataSetName; reportItemPattern.DataSetString = reportItemPattern.DataSetPattern .Replace("@DataSetName", dataSetName) .Replace("@Fields", fields.ToString()); reportItemPattern.TablixString = reportItemPattern.TablixPattern .Replace("@DataSetName", dataSetName) .Replace("@TablixColumns", coloums.ToString()) .Replace("@TablixHeader", tablixHearders.ToString()) .Replace("@TablixCells", tablixCells.ToString()) .Replace("@TablixMember", tablixMembers.ToString()) .Replace("@TopPosition", CaculatePlacePostion().ToString()) .Replace("@LeftPostion", leftPosition.ToString()); //读取行数,如果是空行就加到新的 if (dataRows == 0) { _reportHeadPatterns.Add(reportItemPattern); } else { _reportItemPatterns.Add(reportItemPattern); } } /// <summary> /// 得到某种类型数据的数量 /// </summary> /// <param name="data"></param> /// <returns></returns> private int GetDataRowsCount(dynamic data) { if (data is DataTable) { return ((DataTable) data).Rows.Count; } else if (data is IEnumerable) { return Enumerable.Count(data); } else return 0; } /// <summary> /// 最终显示报表 /// </summary> public void ShowReport() { //将每一个patter转换 if (_reportItemPatterns.Count > 0 || _reportTitlePatterns.Count > 0) { var dataSetsString = new StringBuilder(); var tablixString = new StringBuilder(); foreach (var reportItemPattern in _reportItemPatterns) { dataSetsString.Append(reportItemPattern.DataSetString); tablixString.Append(reportItemPattern.TablixString); } foreach (var reportItemPattern in _reportHeadPatterns) { dataSetsString.Append(reportItemPattern.DataSetString); tablixString.Append(reportItemPattern.TablixString); } var reportTitleString = new StringBuilder(); foreach (var reportTitlePattern in _reportTitlePatterns) { reportTitleString.Append(reportTitlePattern); } //把文档中的文字替换掉 switch (ReportType) { case ReportType.Tables: _docTemplate = _docTemplate.Replace("@DataSets", dataSetsString.ToString()) .Replace("@Tablix", tablixString.ToString()) .Replace("@Title", reportTitleString.ToString()); break; case ReportType.Chart: break; case ReportType.Finally: //替换datasetstring var pos = _docTemplate.IndexOf("<Body>", StringComparison.Ordinal); _docTemplate = _docTemplate.Insert(pos, string.Format( "<DataSources><DataSource Name=\"DummyDataSource\"><ConnectionProperties><DataProvider>SQL</DataProvider><ConnectString /></ConnectionProperties><rd:DataSourceID>3eecdab9-6b4b-4836-ad62-95e4aee65ea8</rd:DataSourceID></DataSource></DataSources><DataSets>{0}</DataSets>", dataSetsString)); //替换Tablix pos = _docTemplate.IndexOf("<ReportItems>", StringComparison.Ordinal); _docTemplate = _docTemplate.Insert(pos + 13, tablixString.ToString()); //替换title _docTemplate = _docTemplate.Insert(pos + 13, reportTitleString.ToString()); break; } var doc = new XmlDocument(); doc.LoadXml(_docTemplate); Stream stream = GetRdlcStream(doc); //加载报表定义 _report.LocalReport.LoadReportDefinition(stream); _report.LocalReport.DataSources.Clear(); foreach (var reportItemPattern in _reportItemPatterns) { _report.LocalReport.DataSources .Add(new ReportDataSource(reportItemPattern.DataSetName + "Data", reportItemPattern.Data)); } foreach (var reportItemPattern in _reportHeadPatterns) { _report.LocalReport.DataSources .Add(new ReportDataSource(reportItemPattern.DataSetName + "Data", reportItemPattern.Data)); } _report.LocalReport.Refresh(); } } /// <summary> /// 序列化到内存流 /// </summary> /// <returns></returns> protected Stream GetRdlcStream(XmlDocument xmlDoc) { Stream ms = new MemoryStream(); XmlSerializer serializer = new XmlSerializer(typeof (XmlDocument)); serializer.Serialize(ms, xmlDoc); ms.Position = 0; return ms; } }
调用方法一(使用内置的空报表模板):
//new一个报表类对象 var dynamicReport = new DynamicReport {ReportType = ReportType.Tables}; //设置报表为本地报表 dynamicReport.SetReport(this.ReportViewer1); //设置列宽度和对齐方式(可选) dynamicReport.SetColoumStyle(new List<ReportColoumStyle>() { new ReportColoumStyle(){ColoumName = "专业名称", ColoumWidth = 4F}, new ReportColoumStyle() {ColoumName = "学院", ColoumWidth = 3.5F}, new ReportColoumStyle(){ColoumName = "就业去向", ColoumWidth = 4F}, }); //加入标题 dynamicReport.AddText(module.ModuleName); //加入数据 dynamicReport.AddData(data); //设置导出报表的名称 ReportViewer1.LocalReport.DisplayName = module.ModuleName; //处理报表数据并显示 dynamicReport.ShowReport();
调用方法二(载入已有的报表,并且添加相关数据):
//new一个报表类对象 var dynamicReport = new DynamicReport (); dynamicReport.ReportType = ReportType.Finally; //读取报表模板(模板内不能有其他数据源等) dynamicReport.LoadReport(Server.MapPath("~/ChapterReport/ReportView.rdlc")); //设置报表为本地报表 dynamicReport.SetReport(this.ReportViewer1); //设置列宽度和对齐方式(可选) dynamicReport.SetColoumStyle(new List<ReportColoumStyle>() { new ReportColoumStyle(){ColoumName = "专业名称", ColoumWidth = 4F}, new ReportColoumStyle() {ColoumName = "学院", ColoumWidth = 3.5F}, new ReportColoumStyle(){ColoumName = "就业去向", ColoumWidth = 4F}, }); //加入标题 dynamicReport.AddText(module.ModuleName); //加入数据 dynamicReport.AddData(data); //加入其它标题 dynamicReport.AddTitle("其它文字", 1); //加入其它数据 dynamicReport.AddData(data2); ................ //设置导出报表的名称 ReportViewer1.LocalReport.DisplayName = "......"; //处理报表数据并显示 dynamicReport.ShowReport();
应园友要求,该代码已上传至GitHub,大家可以自行下载(源码不再更新):
https://github.com/thanksqq/DynamicReport/
作者:thanks
微信:-
QQ:305380844
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。