背景:
最近給客戶做了一個小工具,主要是為了減輕客戶那邊的工作壓力,一般公司都有很多自己的業務數據需要定期發給自己的員工,比如各種指標數據,績效評比等, 大多都是按公司的組織結構來,一級一級往下發,領導接收的數據一般會包含自己下一級人員的數據,所以客戶這邊也不例外,每次到了一定的周期(每周,每月或者每季度),就會由專業人員將一份完整的數據分別按組織結構一級一級往下發,全體領導基本都需要做這件事。
於時,我們需要給領導做一個自動分發數據的工具,將這些數據按一定要求自動的發送到各自人的郵箱中,需求給簡單,就是發郵件,從程序功能上講也就分三部分:
1:解析數據源並加載到數據庫中
2:提取數據並發送郵件
3:將發送結果反饋給指定的人
之前我們一致認為數據源是穩定的,數據源格式是excel,里面的sheet名稱及數量是固定的,字段也是固定的(字段數量以及名稱),所以我們有一種非常簡單的做法,就是將excel的每個sheet映射成數據庫中的一張表,表字段和excel字段一一對應即可,最后根據不同的業務數據需求,構建不同的郵件模塊,最后實現郵件發送。
問題:
但有一天,我們的客戶負責人發生了變更,他提出了一個問題:如果以后發送的數據發生變化,你們的軟件能支持嗎,比如今年我們的excel發送的是10個指標數據,明年如果想再增加5個或者重新替換原來的指標。
由於我們對此工具的定位非常低,及不用花太大代價在小工具上,盡早用上盡早減輕客戶工作量,同時也會降低開發成本,當時我們理解為數據源不會輕易發生變化,所以設計了上面的解決方案,及不支持數據的動態變更。但新的負責人提出了我們的方案不夠靈活,可擴展性,可配置性稍弱,為此我們不得不重新設計方案以支持指標數據的動態變化需求,哎,怎么說呢,如果我們給客戶想的多想的全,他們也有可能說,就一小工具不用這么復雜吧,總之既然問題提出我們就需要想辦法及解決呀。
習慣性思維產生的解決方案:
動態構建數據表,及首先解析每個sheet有多少列,然后將這些列信息映射為數據庫表,這個方案有如下缺點:
缺點一:sheet的列名需要特殊處理才行,我們需要在前面增加 []標識,以防止字符不符合表字段要求;
缺點二:理論上數據庫表的列最大數沒有excel的大,即在某種極端的情況下,無法容納所有excel列數據;
缺點三:在數據庫查詢上不方便,因為字段都是未知的;
缺點四:在綁定email模板(email模板是用xlst來做的)上也是個問題,同樣也是字段都是未知的;
缺點五:每次導入數據都需要全部刪除數據庫中的表,然后重建,這會帶來一些不可控的因素。
我同事最終設計了一套表結構,能夠非常完善的解決我們所遇到的問題,且結構非常簡單。
完美解決方案:
將每個sheet的數據分解為三部分:
1:列信息表Column,主要記錄列名稱
2:行信息表Row,主要記錄行號
3:數值信息Data,以列坐標以及行坐標能夠在sheet上唯一確定一個數據,這里記錄數據信息。
數據庫表結構圖如下:
方案分析:
1:將sheet中的所以列信息分別插入到Column表中,多少列就插入多少行記錄,這樣就能夠無限支持列數據的增加,而不會受到表字段數量的限制。
2:將行信息,對excel左側對應的行號數據插入到Row中,有多少行就插入多少。
3:最后是數據的存儲,主要是利用二維坐標定位邏輯來確定數據。
4:盡管看起來一個sheet的數據分成三個表來存儲,但每個表存儲的內容都相當簡單,邏輯清晰,所以不會帶來更大的復雜性問題。
如何將數據庫中的數據加載到內存中,我這里構建一個對象,此對象代表excel中的一行數據,如果是sheet所有數據可以用List<SheetData>來表示。ColumnInfo中的Key就是列名稱,Value就是某一列的某一行的數據。按照我們的表結構,構建下面的對象模型也是比較容易的。
{
public List<ColumnInfo> Columns { get; set; }
}
public class ColumnInfo
{
public string Key{ get; set;}
public string Value{ get; set;}
}
如何綁定Email模板,既然我們有了明確的對象結構,那么應用xsl語法就更加容易了:
<tr>
<xsl: for-each select= " Email/SheetData/Columns/ColumnInfo ">
<td style= " background-color:#FFFF00;width:85px; ">
<xsl:value-of select= " Key "/>
</td >
</xsl: for-each>
</tr>
<tr>
<xsl: for-each select= " Email/SheetData/Columns/ColumnInfo ">
<td>
<xsl:value-of select= " Value "/>
</td >
</xsl: for-each>
</tr>
</table>
上面的方案解決了之前方案的所有問題,由於存儲數據的表結構是可知的,所以在數據庫查詢以及email模板的綁定上都不會有任何問題。