最近我在開發一個業務信息統計頁面,由於數據存儲在多個不同服務器的數據庫中,直接跨庫查詢很明顯不合適,實際情況也不允許。遇到這種情況,我的常規思路是將各部分需要的數據先放到內存中然后關聯查詢。這里我想到自己之前碰到的一個坑,當時我是使用內存中List<T>數據和數據表的實體直接使用Linq關聯查詢。本地測試時可以的,所以就這么做了。但是到了生產環境就遇到了問題,系統報錯提示內存溢出。由於生產環境數據量大,內存數據關聯實體查詢實際上是將實體表的數據也拿到內存中然后進行操作,結果導致內存溢出。
回到這次的問題。bulk方法就是這個。在測試環境,完全沒有問題,數據能正常插入,說明程序邏輯是沒問題的。
public static bool ExecuteSQLBulkInsert(DataTable dt, SqlConnection Connection) { try { using (SqlBulkCopy sbc = new SqlBulkCopy(Connection)) { //服務器上目標表的名稱 sbc.DestinationTableName = dt.TableName; sbc.BatchSize = 1; sbc.BulkCopyTimeout = 10; for (int i = 0; i < dt.Columns.Count; i++) { //列映射定義數據源中的列和目標表中的列之間的關系 sbc.ColumnMappings.Add(dt.Columns[i].ColumnName, dt.Columns[i].ColumnName); } sbc.WriteToServer(dt); } return true; } catch (Exception) { return false; } }
調用方法:
public static bool InsertIntoStaffInfo(string cityName) { CityBase citySite = new CityBase(cityName, ""); DataTable StaffLearninginfo = GetStaffLearninginfo(citySite); DataTable UserInfo = GetUserInfo(citySite); var queryList = from x in StaffLearninginfo.AsEnumerable().DefaultIfEmpty() join y in UserInfo.AsEnumerable() on x.Field<int>("UserID") equals y.Field<int>("UserID") select new { UserID = x.Field<int>("UserID"), UserRealName = y.Field<string>("UserRealName"), CityName = x.Field<string>("CityName"), EnrollCourseNum = x.Field<int>("EnrollCourseNum"), FinishCourseNum = x.Field<int>("LearnedCourseNum"), FinishRate = x.Field<decimal>("FinishRate"), LearningTimeSpan = x.Field<int>("TotalLearnTime"), ExamParticipantNum = x.Field<int>("ExamTimes"), ExamPassNum = x.Field<int>("PassExamTimes"), ExamPassRate = x.Field<decimal>("PassRate"), BonusPoint = x.Field<decimal>("BonusPoints"), EmployeeEntryDate = y.Field<DateTime>("EmployeeEntryDate"), CreateTime = x.Field<DateTime>("CreateTime") }; DataTable StaffStatInfo = DataTableExtensions.ToDataTable(queryList); StaffStatInfo.TableName = "EB_ExamCourse_StaffStatInfo"; using (SqlConnection sqlcon = new System.Data.SqlClient.SqlConnection(DataFactory.GetConnectionStr(BaseClass.BusinessType.EBDataStatisticWrite, citySite))) { sqlcon.Open(); // StaffStatInfo.TableName = string.Format("[{0}]..[{1}]", sqlcon.Database, "ExamCourse_StaffStatInfo"); try { SQLHelper.ExecuteCommand(string.Format("DELETE FROM EB_ExamCourse_StaffStatInfo WHERE CityName='{0}'",cityName), sqlcon); // SQLHelper.ExecuteSQLBulkInsert(StaffStatInfo, sqlcon); //正式庫權限不夠,無法查看表結構 SQLHelper.WriteTableDataToDB(StaffStatInfo,sqlcon); return true; } catch { return false; } } }
我根據報錯信息找到了微軟官方給出的問題解決方法 。bulk目標表名中間不能有 . 符號,例如 [test.3]
報錯信息是:無法獲取目標表的列排序信息。如果該表不在當前數據庫中,名稱必須使用數據庫名稱限定(例如 [mydb]..[mytable]);此規則也適用於臨時表(例如 #mytable 將指定為 tempdb..#mytable)。
https://support.microsoft.com/en-us/kb/944389
將bulk的目標表名按要求改為server.database.schema.tablename
然並卵!
當然,后來我google到了一個哥們遇到了和我同樣的問題,但也沒有給出解決方法。
我對比開發環境和生產環境最大的不同就是數據庫賬號權限有差異,生產數據庫沒法查看數據表結構,裝了插件也識別不出來。不知道這算不算一個bug,反正我給微軟提了反饋。
最終只好用效率較低的笨方法完成的,逐條插入。方法是上網找的,不過做了一些修改和優化,其實就是將字符串拼接換了StringBuilder。如下
/// <summary> /// 將DataTable中數據寫入數據庫中 /// </summary> /// <param name="dt"></param> /// <returns></returns> public static bool WriteTableDataToDB(DataTable dt, SqlConnection Connection) { if (dt == null || dt.Rows.Count == 0) { return true; } string tname = dt.TableName; string colNames = ""; for (int i = 0; i < dt.Columns.Count; i++) { colNames += dt.Columns[i].ColumnName + ","; } colNames = colNames.TrimEnd(','); StringBuilder cmd = new StringBuilder(); StringBuilder colValues=new StringBuilder(); string cmdmode = string.Format("insert into {0}({1}) values({{0}});", tname, colNames); for (int i = 0; i < dt.Rows.Count; i++) { colValues.Clear(); for (int j = 0; j < dt.Columns.Count; j++) { if (dt.Rows[i][j].GetType() == typeof(DBNull)) { colValues.AppendLine( "NULL,"); continue; } if (dt.Columns[j].DataType == typeof(string)) colValues.Append(string.Format("'{0}',", dt.Rows[i][j])); else if (dt.Columns[j].DataType == typeof(int) || dt.Columns[j].DataType == typeof(float) || dt.Columns[j].DataType == typeof(double)) { colValues.Append(string.Format("{0},", dt.Rows[i][j])); } else if (dt.Columns[j].DataType == typeof(DateTime)) { colValues.Append(string.Format("cast('{0}' as datetime),", dt.Rows[i][j])); } else if (dt.Columns[j].DataType == typeof(bool)) { colValues.Append(string.Format("{0},", dt.Rows[i][j].ToString())); } else { colValues.Append(string.Format("'{0}',", dt.Rows[i][j])); } } cmd.AppendLine(string.Format(cmdmode, colValues.ToString().TrimEnd(','))); } try { ExecuteCommand(cmd.ToString(), Connection); } catch (Exception e) { return false; } return true; }
如果有園友碰到類似問題並且解決麻煩指點下啦,THX.