DevExpress擁有.NET開發需要的所有平臺控件,包含600多個UI控件、報表平臺、DevExpress Dashboard eXpressApp 框架、適用於 Visual Studio的CodeRush等一系列輔助工具。屢獲大獎的軟件開發平臺DevExpress 近期正式發佈了v21.1,最新版擁有衆多新產品和數十個具有高影響力的功能,可爲桌面、Web和移動應用提供直觀的解決方案,全面解決各種使用場景問題。
2021中國區首發 · DevExpress v21.1新版發佈會報名開啓,名額有限先到先得哦~
在很多系統,我們都知道,Excel數據的導入導出操作是必不可少的一個功能,這種功能能夠給使用者和外部進行數據交換,也能批量迅速的錄入數據到系統中;但在一些系統中,爲了方便,可能把很多個基礎表或者相關的數據綜合到一個Excel表格文件裏面,然後希望通過接口進行導入,這種需求處理就顯得比較複雜一點了。本文探討在我的客戶關係管理系統中,對於單個Excel表格中,集合了客戶基礎數據及相關數據的導入和導出操作的處理。
一、導入導出的需求分析
本隨筆主要介紹如何在系統中,導入單一文件中的數據到系統中,這個文件包含了基礎數據和相關數據的導入和導出操作,一般來說這樣的操作對於導入數據已經足夠簡便了,但是,有時候數據很多的情況下,我們可能需要每次選定文件也是一個麻煩的事情。因此指定目錄進行批量數據的導入操作也是一個好的需求,可以進一步簡化用戶的數據導入操作。
下面我們就來介紹,導入、批量導入和導出的三個重要的操作,如圖所示。
導入的數據,是一個Excel,它要求包含幾個不同表的數據,導入操作一次性完成數據的導入,Excel文件的格式如下所示。
二、數據導入操作的界面設計及處理
我們知道,要一次性導入幾個表的數據,需要先讀取Excel獲取各個Sheet(工作表)的數據,然後把它轉換爲DataTable的數據對象,這樣我們就可以根據它的字段賦值給對應的實體類,然後調用業務邏輯處理將數據寫入數據庫即可。
爲了直觀的給使用者查看將要導入的數據,我們把需要導入到數據庫的數據,展現在界面上,供客戶確認,如果沒有問題,就可以進行導入操作。由於我們需要操作多個數據表,因此有效讀取Excel裏面的Sheet就是第一步工作。
查看Excel數據的操作代碼如下所示,主要的邏輯就是調用Apose.Cell的封裝類進行處理。
AsposeExcelTools.ExcelFileToDataSet(this.txtFilePath.Text, out myDs, out error);
把Excel文件裏面多個Sheet的數據轉換爲DataSet,然後每個進行依次的處理,展示代碼如下所示。
private void ViewData() { if (this.txtFilePath.Text == "") { MessageDxUtil.ShowTips("請選擇指定的Excel文件"); return; } try { myDs.Tables.Clear(); myDs.Clear(); this.gridCustomer.DataSource = null; string error = ""; AsposeExcelTools.ExcelFileToDataSet(this.txtFilePath.Text, out myDs, out error); this.gridCustomer.DataSource = myDs.Tables[0]; this.gridView1.PopulateColumns(); this.gridFollow.DataSource = myDs.Tables[1]; this.gridView2.PopulateColumns(); this.gridContact.DataSource = myDs.Tables[2]; this.gridView3.PopulateColumns(); this.gridSupplier.DataSource = myDs.Tables[3]; this.gridView4.PopulateColumns(); } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } }
由於導入過程中需要耗費一定的時間,因此我們可以通過後臺線程結合進度條的方式提示用戶,界面設計效果如下效果所示。
剛纔說到,保存數據,我們把它放到後臺線程BackgroudWorker進行處理即可,處理代碼如下所示。
private void btnSaveData_Click(object sender, EventArgs e) { if (worker.IsBusy) return; if (this.txtFilePath.Text == "") { MessageDxUtil.ShowTips("請選擇指定的Excel文件"); return; } if (MessageDxUtil.ShowYesNoAndWarning("該操作將把數據導入到系統數據庫中,您確定是否繼續?") == DialogResult.Yes) { if (myDs != null && myDs.Tables[0].Rows.Count > 0) { this.progressBar1.Visible = true; worker.RunWorkerAsync(); } } }
後臺線程操作的主要業務邏輯代碼如下所示,就是依次把不同的數據進行解析,並保存即可。
void worker_DoWork(object sender, DoWorkEventArgs e) { if (myDs != null && myDs.Tables.Count >= 4 && myDs.Tables[0].Rows.Count > 0) { try { ImportCustomerDataHelper helper = new ImportCustomerDataHelper(); helper.LoginUserInfo = LoginUserInfo; //寫入或更新客戶信息 string customerID = helper.UpdateCustomer(myDs.Tables[0]); if (!string.IsNullOrEmpty(customerID)) { helper.AddFollow(customerID, myDs.Tables[1], worker); helper.AddContact(customerID, myDs.Tables[2], worker); helper.AddSupplier(customerID, myDs.Tables[3], worker); e.Result = "操作完成"; } else { e.Result = "操作失敗"; } } catch (Exception ex) { e.Result = ex.Message; LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.ToString()); } } else { e.Result = "請檢查數據記錄是否存在"; } }
三、數據批量導入操作
雖然上面可以一次性導入客戶和其相關數據,但是還是一次性導入一個Excel,如果對於客戶數據比較多的情況下,一次次導入操作也是很繁瑣的事情,因此客戶提出,需要按照目錄把所有相關的Excel數據一次性導入,這種導入有個問題就是我們不能再中途干預導入操作,因此爲了數據的安全性,我提供一個界面讓客戶選擇目錄,然後把目錄裏面的Excel文件列出來,然後在讓客戶確認是否進一步導入。
上面操作的實現代碼我逐一介紹,首先第一步是需要遞歸列出目錄下面的Excel文件,然後顯示出來供用戶確認導入的清單。
private void btnSelectPath_Click(object sender, EventArgs e) { string mydocDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); string selectPath = FileDialogHelper.OpenDir(mydocDir); if (!string.IsNullOrEmpty(selectPath)) { //清空就記錄 this.lstPath.Items.Clear(); string[] fileArray = Directory.GetFiles(selectPath, "*.xls", SearchOption.AllDirectories); if (fileArray != null && fileArray.Length > 0) { foreach (string file in fileArray) { string fileName = Path.GetFileName(file); this.lstPath.Items.Add(new CListItem(fileName, file)); } } } }
當用戶確認操作的時候,提示客戶確認是否進行,確認後將統一批量導入列表裏面的文件,這個地方也是爲了方便,使用後臺線程進行數據的導出操作,並在過程中提供進度條的指示。
private void btnConfirm_Click(object sender, EventArgs e) { if (worker.IsBusy) return; if (this.lstPath.Items.Count > 0) { if (MessageDxUtil.ShowYesNoAndTips("您確認導入列表的Excel文件嗎?") == System.Windows.Forms.DialogResult.Yes) { List<string> fileList = new List<string>(); foreach (object item in this.lstPath.Items) { CListItem fileItem = item as CListItem; if (fileItem != null) { fileList.Add(fileItem.Value); } } this.progressBar1.Visible = true; worker.RunWorkerAsync(fileList); } } }
這個後臺線程的處理邏輯和單個文件導入的操作差不多,只不過這裏需要增加一個文件列表的遍歷處理而已,具體代碼如下所示。
void worker_DoWork(object sender, DoWorkEventArgs e) { List<string> fileList = e.Argument as List<string>; if (fileList == null || fileList.Count == 0) return; bool hasError = false; ImportCustomerDataHelper helper = new ImportCustomerDataHelper(); helper.LoginUserInfo = LoginUserInfo; foreach (string file in fileList) { DataSet myDs = new DataSet(); string error = ""; AsposeExcelTools.ExcelFileToDataSet(file, out myDs, out error); if (myDs != null && myDs.Tables.Count >= 4 && myDs.Tables[0].Rows.Count > 0) { try { //寫入或更新客戶信息 string customerID = helper.UpdateCustomer(myDs.Tables[0]); if (!string.IsNullOrEmpty(customerID)) { helper.AddFollow(customerID, myDs.Tables[1], worker); helper.AddContact(customerID, myDs.Tables[2], worker); helper.AddSupplier(customerID, myDs.Tables[3], worker); } } catch (Exception ex) { hasError = true; LogTextHelper.Error(ex); } } } string msg = "操作完成"; if (hasError) { msg += ",導入出現錯誤。具體可以查看log.txt日誌記錄。"; } e.Result = msg;
和上面的單個文件導入一樣,我們這裏使用了一個封裝類ImportCustomerDataHelper,用來對數據進行轉換實體類,然後保存到數據庫的操作過程,下面我們來簡單看看裏面的處理代碼:
/// <summary> /// 客戶數據的批量導入和普通導入的操作邏輯代碼 /// </summary> public class ImportCustomerDataHelper { /// <summary> /// 登陸用戶信息 /// </summary> public LoginUserInfo LoginUserInfo { get; set; } /// <summary> /// 寫入或更新客戶數據,如果成功更新返回ID值 /// </summary> /// <param name="dataTable">客戶數據表</param> /// <returns></returns> public string UpdateCustomer(DataTable dataTable) { bool success = false; bool converted = false; DateTime dtDefault = Convert.ToDateTime("1900-01-01"); DateTime dt; string result = ""; DataRow dr = dataTable.Rows[0]; if (dr != null) { string customerName = dr["客戶名稱"].ToString(); CustomerInfo info = CallerFactory<ICustomerService>.Instance.FindByName(customerName); bool isNew = false; if (info == null) { info = new CustomerInfo(); isNew = true; } info.Name = customerName; info.HandNo = dr["客戶編號"].ToString(); info.SimpleName = dr["客戶簡稱"].ToString(); .......................... info.IsPublic = dr["公開與否"].ToString().ToBoolean(); info.Satisfaction = dr["客戶滿意度"].ToString().ToInt32(); info.TransactionCount = dr["交易次數"].ToString().ToInt32(); info.TransactionTotal = dr["交易金額"].ToString().ToDecimal(); info.Creator = dr["客戶所屬人員"].ToString(); converted = DateTime.TryParse(dr["創建時間"].ToString(), out dt); if (converted && dt > dtDefault) { info.CreateTime = dt; } info.Editor = LoginUserInfo.ID.ToString(); info.EditTime = DateTime.Now; if (isNew) { info.Dept_ID = LoginUserInfo.DeptId; info.Company_ID = LoginUserInfo.CompanyId; success = CallerFactory<ICustomerService>.Instance.Insert(info); } else { success = CallerFactory<ICustomerService>.Instance.Update(info, info.ID); } if (success) { result = info.ID; } } return result; } ...........................
四、數據的導出操作
導出操作,我們根據用戶的選擇,可以一次性導出多個Excel文件,每個Excel文件包含客戶的基礎信息,也包含相關數據,它們的格式和導入的格式保持一致即可,這樣方便數據的交換處理。
導出操作,我們需要把客戶的選擇信息轉換爲需要導出的對象列表數據,然後綁定到Excel裏面即可,因此我們的Excel裏面,可以通過自定義模板,指定列的數據屬性就可以綁定好數據了。
獲取選擇的客戶信息的代碼如下所示。
List<CustomerInfo> list = new List<CustomerInfo>(); foreach (int iRow in rowSelected) { string ID = this.winGridViewPager1.GridView1.GetRowCellDisplayText(iRow, "ID"); CustomerInfo info = CallerFactory<ICustomerService>.Instance.FindByID(ID); if (info != null) { list.Add(info); } }
前面介紹了,我們將使用自定義模板,在模板文件裏面的對應字段下面,綁定一個參數屬性就可以了,通過Aspose.Cell的操作處理,我們就很方便把數據導出到Excel裏面了,而裏面的字段還可以很方便實現自由的裁剪操作。
自定義模板文件效果如下所示。
導出客戶以及相關信息的主要核心代碼如下所示。
#region 導出操作 //依次每個客戶數據導出一個文件 string ownerUserName = CallerFactory<IUserService>.Instance.GetFullNameByID(customerInfo.Creator.ToInt32()); string filePath = Path.Combine(selectPath, ownerUserName); DirectoryUtil.AssertDirExist(filePath); Dictionary<string, object> dict = new Dictionary<string, object>(); dict.Add("Customer", new List<CustomerInfo>() { customerInfo });//需要構造一個列表綁定 List<FollowInfo> followList = CallerFactory<IFollowService>.Instance.Find(string.Format("Customer_ID ='{0}' ", customerInfo.ID)); dict.Add("Follow", followList); List<ContactInfo> contactList = CallerFactory<IContactService>.Instance.FindByCustomer(customerInfo.ID); dict.Add("Contact", contactList); PagerInfo pagerInfo = null; List<SupplierInfo> supplierList = CallerFactory<ISupplierService>.Instance.FindByCustomer(customerInfo.ID, "", ref pagerInfo); dict.Add("Supplier", supplierList); string templateFile = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "客戶綜合資料-導出模板.xls"); if (!File.Exists(templateFile)) { throw new ArgumentException(templateFile, string.Format("{0} 文件不存在,", Path.GetFileName(templateFile))); } string saveFileName = string.Format("{0}.xls", customerInfo.Name); string saveFilePath = Path.Combine(filePath, saveFileName); WorkbookDesigner designer = new WorkbookDesigner(); designer.Workbook = new Workbook(templateFile); foreach (string key in dict.Keys) { designer.SetDataSource(key, dict[key]); } designer.Process(); designer.Workbook.Save(saveFilePath, SaveFormat.Excel97To2003); #endregion
這樣利用Aspose.Cell的處理操作,通過綁定相關的數據對象,我們就很容易實現數據導出到符合我們預期格式的Excel裏面去了,這樣操作高效、代碼乾淨,Excel格式也非常符合我們的要求。
以上就是在客戶關係管理系統裏面碰到特殊的數據導入導出需求的介紹和實現,希望大家相互交流,共同把軟件開發過程中,數據導入導出操作的使用體驗做到最好,更符合我們客戶使用的習慣和需求。
本文轉載自:博客園 - 伍華聰
DevExpress技術交流羣3:700924826 歡迎一起進羣討論