Quantcast
Channel: 黑暗執行緒
Viewing all articles
Browse latest Browse all 2311

Reporting Service 報表 List 區塊使用多資料表

$
0
0

Reporting Service RDLC 報表設計進階議題一枚。

先說情境,假設有技能專長與擅長語言兩個資料表,其中有每個人的資料,想在 RDLC 報表採以下形式呈現:先印出姓名,接著以表格形式分別列出技能清單與語言清單:

這類需求,最直覺有效的做法是使用子報表!很不幸,同事嘗試用子報表解決卻踼到鐵板:明細資料總筆數約 2000 筆,拆成 500 個子報表,產生報表耗時七分鐘,志玲姐姐都護完一生了報表還出不來,想當然爾被使用者狠狠打槍!

查了文獻,有文章指出包太多 SubReport 註定快不起來:(但資料都在記憶體, 500 個子報表慢到七八分鐘讓人意外)

總而言之,得想想繞路的方法。我優先想到的武器是 List,以使用者分群,在方格中放入兩個資料表格,一個顯示技能,一個顯示語言,像這樣:

不幸地,再踼到 List 限制的鐵板!List 資料區只能套用單一資料來源:

Because the List contains a grouping level, you can use the List data region only with a single dataset. 參考

最後,想到一個很可恥卻有用的方法-把兩個資料表合併成一個,額外加入 IsSkill 及 IsLang 布林欄位區隔是那一種資料(或用TableSrc string 識別也成),把兩種資料合併在同一個資料表中:

接著,在 Table1 套用 「IsSkill = True」Filter、Table2 套用 「IsLang = true」Filter,就成功在一個 List 顯示兩種資料!(灑花)

最後補上一些實作細節。

首先,將兩個 DataTable 合併成一個應該難不倒大家,但若資料來源是物件陣列,要將多個物件陣列合併成一個 DataTable 就要靠點技巧。分享一則密技:將物件陣列先轉成 JSON 字串,再用 Json.NET JsonConvert.DeserializeObject<DataTable>() 就能瞬間轉成 DataTable。我將合併邏輯寫成 DataTable 型別的擴充方式,合併資料來源則 IEnumerable<object> 與 DataTable 通吃,合併時還要傳入segName 方便加入 IsXXX 欄位。

publicstaticvoid MergeData(this DataTable table, IEnumerable<object> data, string segName)
{
    var json = JsonConvert.SerializeObject(data);
    var t = JsonConvert.DeserializeObject<DataTable>(json);
    table.MergeData(t, segName);
}
 
publicstaticvoid MergeData(this DataTable table, DataTable toAdd, string segName)
{
    var segFldName = $"Is{segName}";
    toAdd.Columns.Add(segFldName, typeof(bool));
foreach (DataColumn c in toAdd.Columns)
    {
if (!table.Columns.Contains(c.ColumnName))
            table.Columns.Add(c.ColumnName, c.DataType);
    }
foreach (DataRow row in toAdd.Rows)
    {
        row[segFldName] = true;
        var newRow = table.NewRow();
foreach (DataColumn c in toAdd.Columns)
            newRow[c.ColumnName] = row[c.ColumnName];
        table.Rows.Add(newRow);
    }
}

資料來源範例如下:

publicclass Skill
{
publicstring UserId { get; set; }
publicstring SkillName { get; set; }
publicint Level { get; set; }
public Skill(string userId, string skillName, int level)
    {
        UserId = userId;
        SkillName = skillName;
        Level = level;
    }
}
 
publicclass Language
{
publicstring UserId { get; set; }
publicstring Lang { get; set; }
publicint Level { get; set; }
public Language(string userId, string lang, int level)
    {
        UserId = userId;
        Lang = lang;
        Level = level;
    }
}
 
publicstaticclass SkillDataStore
{
publicstatic List<Skill> GetSkillData()
    {
returnnew List<Skill>()
        {
new Skill("Jeffrey", "爆破", 5),
new Skill("Jeffrey", "嘴砲", 4),
new Skill("Jeffrey", "嘲諷", 3),
new Skill("Darkthread", "發廢文", 5),
        };
    }
 
publicstatic List<Language> GetLangData()
    {
returnnew List<Language>()
        {
new Language("Jeffrey", "C#", 4),
new Language("Jeffrey", "JavaScript", 3),
new Language("Darkthread", "T-SQL", 3),
new Language("Darkthread", "PL/SQL", 2)
        };
    }
}

處理資料時,先建立空白 DataTable,再使用 MergeData() 合併 List<Skill> 及  List<Language>:

    DataTable t = new DataTable("Data");
    t.MergeData(Models.SkillDataStore.GetSkillData(), "Skill");
    t.MergeData(Models.SkillDataStore.GetLangData(), "Lang");
    rptViewer.LocalReport.DataSources.Add(
new Microsoft.Reporting.WebForms.ReportDataSource("DataSet1", t));

由於 DataSet 是動態組裝的 DataTable,沒有現成的資料模型範本,設計報表時看不到可用欄位無法拖拉設定。

有兩種解決方法,第一種是手動修改 RDLC XML 加上欄位:

<DataSets>
<DataSetName="DataSet1">
<Query>
<DataSourceName>RDLCTestModels</DataSourceName>
<CommandText>/* Local Query */</CommandText>
</Query>
<Fields>
<FieldName="UserId">
<DataField>UserId</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<FieldName="SkillName">
<DataField>SkillName</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<FieldName="Lang">
<DataField>Lang</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<FieldName="Level">
<DataField>Level</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<FieldName="IsSkill">
<DataField>IsSkill</DataField>
<rd:TypeName>System.Boolean</rd:TypeName>
</Field>
<FieldName="IsLang">
<DataField>IsLang</DataField>
<rd:TypeName>System.Boolean</rd:TypeName>
</Field>
</Fields>
<rd:DataSetInfo>
<rd:DataSetName>DynamicDataTable</rd:DataSetName>
<rd:TableName>Data</rd:TableName>
</rd:DataSetInfo>
</DataSet>
</DataSets>

或者先跑程式取得 DataTable 再匯出 XSD 也成,我選擇手工修改 RDLC XML 了事。

就醬,再度靠著奧步驚險過關~(煙)


Viewing all articles
Browse latest Browse all 2311

Trending Articles