EntityFramework批量寫入和修改數據


GitHub上項目地址:https://github.com/shendashan/BulkCopy

最近在工作中遇到一些性能問題,在大批量的數據寫入和修改數據庫時太慢了,甚至會出現操作超時。

所以去網上找了下資料,找到了一些解決方案SqlBulkCopy和SqlDataAdapter(SqlDataAdapter實測了下,批量修改數據的時候速度不快,可能是我使用的姿勢不對。哪位大神知道正確使用姿勢,望留言指點)。下面主要介紹下SqlBulkCopy:

SqlBulkCopy

  SqlBulkCopy用於做批量寫入的操作,通過調用WriteToServer方法來實現批量寫入的功能(WriteToServer方法的實現原理沒有深入研究)。從測試結果來看,執行效率完全可以滿足公司的需求。

  壹.先來說下批量寫入的具體實現:

    1.在調用WriteToServer方法之前,需要准備一個DataTable實例,DataTable的實例中存放的是需要批量寫入的數據集。

      如圖,先創建一個生成DataTable的方法:

              

   第一個參數,需要批量寫入的集合,這里使用的泛型集合,以方便共用。

   第二個參數,SqlConnection對象,用於連接數據庫。

   第三個參數,被寫入目標表的名字,因為是一個共用的方法,不然無法知道要向哪張表寫數據。

   方法內部實現:

    創建一條查詢的sql語句,如圖:

             

    上面圖中的sql語句,使用來獲取目標表的字段。通過SqlCommand的ExecuteReader方法來獲取SqlDataReader的對象。然后在通過獲取到的SqlDataReader對象來獲取表的列名和對應的類型。

    拿到列名和類型后,使用DataColumn的有參構造函數創建DataColumn的對象(如:column),將對象(column)添加到DataTable對象的Columns屬性中。如圖:

             

    到這里,要寫入目標表的所以列都已經添加到DataTable中了(別忘記關閉DataReader,不然運行的時候回報錯哦O(∩_∩)O),下面就可以去處理要批量寫入到表的集合數據了。

    在處理集合數據之前,先利用反射技術獲取到實體的所有屬性。首先通過typeof()獲取到Type對象,然后通過對象的GetProperties()來獲取所有屬性的數組,如圖:

             

    獲取到了實體的所有屬性,接下來就可以處理要寫入的數據集合了。

    循環數據集合,對數據進行逐條處理。在循環的內部通過循環實體屬性的數組對象來獲取對應的數據。在根據屬性獲取數據值的時候,需要進一步處理。因為實體屬性的數組中很有可能包含導航屬性(導航屬性:ef中codefirst建表時,建立表之間關聯關系的屬性),如果不處理掉導航屬性的話,執行語句的時候會出問題。將處理之后的屬性和獲取到的值添加到DataRow中。如下圖:

           

    寫到這里,DataTable的對象就已經准備好了,要寫人的數據也已經添加到DataTable的對象中了。

    接下來就使用SqlBulkCopy來批量將數據寫入到數據庫中。這一步很簡單,只要創建一個SqlBulkCopy的對象,告訴SqlBulkCopy被寫入表的名字,然后調用WriteToServer()將准備好的DataTable對象放到方法中就OK了。如下圖:

            

    批量寫入數據的方法到這里就完全結束了。最后別忘了將SqlConnection對象關閉。

    具體代碼如下:

    a)批量插入數據代碼片段

 /// <summary>
        /// 批量插入數據
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <param name="modelList">數據集合</param>
        /// <param name="connectionString">數據庫連接字符串</param>
        /// <param name="tableName">表名</param>
        public static void BulkInert<TModel>(IList<TModel> modelList, string connectionString, string tableName)
        {
            try
            {
                using (SqlConnection sqlConnect = new SqlConnection(connectionString))
                {
                    DataTable dt = ToSqlBulkCopyDataTable(modelList, sqlConnect, tableName);
                    SqlBulkCopy sqlBulk = null;
                    sqlBulk = new SqlBulkCopy(sqlConnect);
                    using (sqlBulk)
                    {
                        sqlBulk.DestinationTableName = tableName;
                        if (sqlConnect.State != ConnectionState.Open)
                        {
                            sqlConnect.Open();
                        }
                        sqlBulk.WriteToServer(dt);
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

      b)生成DataTable對象代碼片段

 /// <summary>
        /// 生成DataTable
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <param name="modelList">數據集合</param>
        /// <param name="conn">SqlConnection對象</param>
        /// <param name="tableName">表名</param>
        /// <returns></returns>
        private static DataTable ToSqlBulkCopyDataTable<TModel>(IList<TModel> modelList, SqlConnection conn, string tableName)
        {
            DataTable dt = new DataTable();
            #region 獲取所有表字段
            string sql = string.Format("select top 0 * from {0}", tableName);
            if (conn.State != ConnectionState.Open)
            {
                conn.Open();
            }
            SqlCommand command = new SqlCommand();
            command.CommandText = sql;
            command.CommandType = CommandType.Text;
            command.Connection = conn;
            SqlDataReader reader = command.ExecuteReader();
            try
            {
                for (int i = 0; i < reader.FieldCount; i++)
                {
                    var re_name = reader.GetName(i);
                    var re_type = reader.GetFieldType(i);
                    DataColumn column = new DataColumn(re_name, re_type);
                    dt.Columns.Add(column);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                }
            }
            #endregion
            //獲取實體
            Type mType = typeof(TModel);
            var mType_Properts = mType.GetProperties();

            foreach (var model in modelList)
            {
                DataRow dr = dt.NewRow();
                foreach (var proper in mType_Properts)
                {
                    string fullName = proper.PropertyType.FullName;
                    bool isValueType = proper.PropertyType.IsValueType;
                    bool isClass = proper.PropertyType.IsClass;
                    bool isEnum = proper.PropertyType.IsEnum;
                    if ((isValueType || isEnum) && !isClass)
                    {
                        object value = proper.GetValue(model);
                        if (proper.PropertyType.IsEnum)
                        {
                            if (value != null)
                            {
                                value = (int)value;
                            }
                        }
                        dr[proper.Name] = value ?? DBNull.Value;
                    }
                    else if (fullName == "System.String")
                    {
                        object value = proper.GetValue(model);
                        dr[proper.Name] = value ?? DBNull.Value;
                    }
                }
                dt.Rows.Add(dr);
            }
            return dt;
        }

到這里批量寫入就結束了O(∩_∩)O

    貳.接下來要說的就是批量修改了

    為了能更快的更新數據,在批量修改的實現中還需要用到SqlBulkCopy。這里實現批量修改的操作很簡單,只要在批量寫入的操作之后再執行批量修改的操作就行了(方法比較笨^_^)。

    下面開始正式介紹批量修改的實現:

    1.首先要准備一個臨時表,用於保存要修改的數據(注意:臨時表的名字不可重復,不然會報錯,這里要做處理,保證表名的唯一性)。

    2.使用SqlBulkCopy來向臨時表 中寫入數據,以確保數據能快速寫入到臨時表中。

    3.同樣,使用反射技術來獲取到修改的實體屬性,然后根據獲取到的屬性來組建update語句,用update語句將臨時表和被修改的目標表關聯起來進線批量修改操作。

    批量修改的操作就這么簡單的幾個步驟。需要注意的地方就是在組建update語句的時候對實體屬性的處理,一定要將不屬於表的導航屬性去除掉。不然在執行修改的時候會報出錯誤“XXX列不存在”的提示。下面之間貼出代碼:

    a)批量修改代碼片段

 /// <summary>
        /// 批量修改數據
        /// </summary>
        /// <param name="modelList"></param>
        /// <param name="connectionString">數據庫連接字符串</param>
        /// <param name="tableName">表名</param>
        /// <param name="primaryKey">主鍵</param>
        /// <returns></returns>
        public static int BulkUpdate<TModel>(IList<TModel> modelList, string connectionString, string tableName, string primaryKey)
        {
            try
            {
                Debug.WriteLine("進入BulkCopy");
                //臨時表名使用日期加隨機數
                Random ran = new Random();
                int ranNumber = ran.Next(1, 10000);
                string dateStr = DateTime.Now.ToString("yyyyMMddHHmmss");
                string tempName = "#" + tableName + dateStr + ranNumber;
                var model = typeof(TModel);
                var propers = model.GetProperties();
                StringBuilder updateStrBuild = new StringBuilder();

                foreach (var item in propers)
                {
                    string fullName = item.PropertyType.FullName;
                    bool isValueType = item.PropertyType.IsValueType;
                    bool isClass = item.PropertyType.IsClass;
                    bool isEnum = item.PropertyType.IsEnum;
                    if ((isValueType || isEnum) && !isClass)
                    {
                        updateStrBuild.Append(" t2." + item.Name + " = t1." + item.Name + ",");
                    }
                    else if (fullName == "System.String")
                    {
                        updateStrBuild.Append(" t2." + item.Name + " = t1." + item.Name + ",");
                    }
                }

                string updaSql = updateStrBuild.ToString();
                updaSql = updaSql.TrimEnd(',');
                Debug.WriteLine("修改語句" + updaSql);
                string updateSql = string.Format("update t2 SET {2}  FROM  {0} AS t1,{1} AS t2 WHERE t1.{3} = t2.{3}", tempName, tableName, updaSql, primaryKey);
                Debug.WriteLine(updateSql);
                StringBuilder strB = new StringBuilder();
                foreach (var item in propers)
                {
                    string fullName = item.PropertyType.FullName;
                    bool isValueType = item.PropertyType.IsValueType;
                    bool isClass = item.PropertyType.IsClass;
                    bool isEnum = item.PropertyType.IsEnum;
                    if ((isValueType || isEnum) && !isClass)
                    {
                        strB.Append(" " + item.Name + ", ");
                    }
                    else if (fullName == "System.String")
                    {
                        strB.Append(" " + item.Name + ", ");
                    }
                }
                strB.Append(" " + primaryKey + " as Ids ");
                string strSql = strB.ToString();
                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    string sql = "SELECT top 0 " + strSql + " into " + tempName + " from  " + tableName;
                    if (conn.State != ConnectionState.Open)
                    {
                        conn.Open();
                    }
                    SqlCommand command = new SqlCommand();
                    command.CommandText = sql;
                    command.CommandType = CommandType.Text;
                    command.Connection = conn;
                    command.ExecuteNonQuery();
                    DataTable dt = ToSqlBulkCopyDataTable2(modelList, conn, tableName, strSql, primaryKey);
                    SqlBulkCopy sqlBulk = null;
                    sqlBulk = new SqlBulkCopy(conn);
                    using (sqlBulk)
                    {
                        sqlBulk.DestinationTableName = tempName;
                        if (conn.State != ConnectionState.Open)
                        {
                            conn.Open();
                        }
                        sqlBulk.WriteToServer(dt);
                    }
                    command.CommandText = updateSql;
                    int count = command.ExecuteNonQuery();
                    return count;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

    b)批量修改生成DataTable對象的代碼片段

/// <summary>
        /// 批量修改准備DataTable
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <param name="modelList"></param>
        /// <param name="conn"></param>
        /// <param name="tableName"></param>
        /// <param name="strSql"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private static DataTable ToSqlBulkCopyDataTable2<TModel>(IList<TModel> modelList, SqlConnection conn, string tableName, string strSql, string key)
        {
            Debug.WriteLine("進入ToSqlBulkCopyDataTable2");
            DataTable dt = new DataTable();
            //獲取實體
            Type mType = typeof(TModel);
            var mType_Properts = mType.GetProperties();

            #region
            string sql = string.Format("select top 1 " + strSql + " from  {0}", tableName);
            Debug.WriteLine("查詢語句:" + sql);
            if (conn.State != ConnectionState.Open)
            {
                conn.Open();
            }
            SqlCommand command = new SqlCommand();
            command.CommandText = sql;
            command.CommandType = CommandType.Text;
            command.Connection = conn;
            var reader = command.ExecuteReader();
            try
            {
                while (reader.Read())
                {
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        var re_name = reader.GetName(i);
                        var re_type = reader.GetFieldType(i);
                        DataColumn column = new DataColumn(re_name, re_type);
                        dt.Columns.Add(column);
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                }
            }
            #endregion
            foreach (var model in modelList)
            {
                DataRow dr = dt.NewRow();
                foreach (var proper in mType_Properts)
                {
                    string fullName = proper.PropertyType.FullName;
                    bool isValueType = proper.PropertyType.IsValueType;
                    bool isClass = proper.PropertyType.IsClass;
                    bool isEnum = proper.PropertyType.IsEnum;
                    if ((isValueType || isEnum) && !isClass)
                    {
                        object value = proper.GetValue(model);
                        if (proper.PropertyType.IsEnum)
                        {
                            if (value != null)
                            {
                                value = (int)value;
                            }
                        }
                        dr[proper.Name] = value ?? DBNull.Value;
                        if (key.Equals(proper.Name))
                        {
                            dr["Ids"] = value;

                        }
                    }
                    else if (fullName == "System.String")
                    {
                        object value = proper.GetValue(model);
                        dr[proper.Name] = value ?? DBNull.Value;
                        if (key.Equals(proper.Name))
                        {
                            dr["Ids"] = value;

                        }
                    }

                }
                dt.Rows.Add(dr);
            }
            return dt;
        }

對數據的批量修改和批量寫入完全結束了O(∩_∩)O


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM