去ORM副作用經驗

什麼是ORM
ORM即對象關係映射(Object Relational Mapping,簡稱ORM)是通過使用描述對象和數據庫之間映射的元數據,將面嚮對象語言程序中的對象自動持久化到關係數據庫中。

爲什麼要用ORM
我們在具體的操作數據庫的時候,就不需要再去和複雜的SQL語句打交道,只要像平時操作對象一樣操作它就可以了 。也不用像ADO.NET那樣去創建連接對象,Command對象,DataReader,DataAdapter,開連接,關閉連接,一行行讀取數據等那樣自己實現細節還得擔心連接釋放不到位問題等。

沒有ORM我們代碼可能是這樣的

string updateSq = "update MachResult set SendFlag='" + DateTime.Now.ToString("yyyyMMddhhmmss") + "'";
                    for (int i = 0; i < 20; i++)
                    {
                        if ((UPChl != "") && (!UPChl.Contains("Chl" + (i + 1))))
                        {
                            continue;
                        }
                        updateSq += ",Chl" + (i + 1) + "='" + randomRes[i] + "'";
                    }
                    updateSq += " where RowID=" + r["RowID"].ToString();
                    try
                    {
                        int ret = DBHelper.ExeSql(ConnectionString, updateSq);
                        if (ret == 1)
                        {
                            WriteLog("更新" + r["EpisNo"].ToString() + "數據庫結果");
                        }
                    }
                    catch (Exception ex)
                    {
                        WriteLog("儀器數據庫結果插入異常" + ex.Message);
                    }
DataTable dt = DBHelper.QryData(ConnectionString, "select * from MachResult where SendFlag is null");
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.OleDb;
using System.Data;

namespace LISVMachine
{
    /// <summary>
    /// 數據庫幫助類
    /// </summary>
    public static class DBHelper
    {
        /// <summary>
        /// 執行插入更新
        /// </summary>
        /// <param name="connStr"></param>
        /// <param name="sql"></param>
        /// <returns></returns>
        public static int ExeSql(string connStr,string sql)
        {
            OleDbConnection conn = null;
            try
            {
                conn = new OleDbConnection(connStr);
                //插入
                //更新表
                OleDbCommand cmd = new OleDbCommand(sql, conn);
                cmd.CommandType = CommandType.Text;
                if (conn.State == ConnectionState.Closed)
                {
                    conn.Open();
                }
                //插入數據
                int ret = cmd.ExecuteNonQuery();
                cmd.Dispose();
                return ret;

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (conn != null)
                {
                    conn.Close();
                    conn.Dispose();
                }
            }
        }

        /// <summary>
        /// 執行查詢
        /// </summary>
        /// <param name="connStr"></param>
        /// <param name="sql"></param>
        /// <returns></returns>
        public static DataTable  QryData(string connStr, string sql)
        {
            OleDbConnection conn = null;
            try
            {
                conn = new OleDbConnection(connStr);
                OleDbDataAdapter da = new OleDbDataAdapter(sql, conn);
                DataSet ds = new DataSet();
                da.Fill(ds);
                return ds.Tables[0];
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

有了ORM我們代碼可能是這樣的

/// <summary>
    /// 刪除方法數據
    /// </summary>
    /// <returns></returns>
    public string DeleteMCMethod()
    {
        string rowID = Helper.ValidParam(Request["RowID"],"");
        //得到鑑定數量
        string count = EntityManager.GetCount<MCProcessRecord>(null);
        if (count == "0")
        {
            EntityManager.Remove<BTMCMethod>(rowID, out this.Err);
        }
        else
        {
            BTMCMethod dto = EntityManager.GetById<BTMCMethod>(rowID);
            dto.Active = false;
            EntityManager.Update<BTMCMethod>(dto, out this.Err);
        }
        return Helper.Success();
    }
/// <summary>
    /// 得到ftp地址
    /// </summary>
    /// <returns></returns>
    public string GetFTPAddress()
    {
        Hashtable hs = new Hashtable();
        hs.Add("Code", "LABReportImageFTP");
        hs.Add("ParaType", "HOS");
        //獲得ftp配置參數
        List<SYSParameter> ftps = EntityManager.FindAll<SYSParameter>(hs);
        if (ftps.Count > 0)
        {
            return ftps[0].ParaValue;
        }
        return "";
    }
/// <summary>
    /// 更新報告說明
    /// </summary>
    /// <returns></returns>
    public string UpdateReportRemark()
    {
        string ReportDR = Helper.ValidParam(Request["ReportDR"], "");
        string ReportRemark = Helper.ValidParam(Request["ReportRemark"], "");
        if (ReportDR == "")
        {
            return Helper.Error("請傳入報告主鍵!");
        }
        List<string> upColRep = new List<string>();
        upColRep.Add("ReportRemark");
        RPVisitNumberReport repDto = EntityManager.GetById<RPVisitNumberReport>(ReportDR);
        repDto.ReportRemark = ReportRemark;
        int ret=EntityManager.Update<RPVisitNumberReport>(repDto, out Err, upColRep);
        return Helper.Success();
    }

要查詢只要給出實體類型和條件,要更新只要給出更新實體,要刪除只要給出實體類型和主鍵或實體就行。而實體代碼都是工具連數據庫自動生成的。是不是感覺和sql相隔很遠,是不是感受不到數據庫細節,是不是用VS點實體屬性很方便,也能用ORM開發“通用碼錶”(參照通用碼錶設計)等等這些都是使用ORM帶了的優點。

既然有那麼多ORM優點,那麼他的副作用是什麼呢?
1.性能損耗(優化合適的話差異不大)
2.代碼都依賴實體類而寫,對服務型的軟件給已經用了很久時間的程序擴展增加實體字段將變得困難,加多了表裏沒有的屬性將導致查詢等報錯,很難做到基於最新實體只給項目增加他基礎上要增加的屬性,這就需要比對實體更新了。(陸續擴展字段帶了的實體更新困難)

怎麼解決實體更新困難的問題呢?
1.如果ORM的實現能夠在組裝sql的時候把實體有的屬性,表裏面列沒有的屬性去掉就可以當實體的多餘屬性沒用,也就不會引起sql報錯了。
2.可以通過sql查詢表的列信息。sql格式:

select COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dbo' AND TABLE_NAME='SYS_User'

3.每次解析實體得到sql語句時候都查詢表信息實時比對造成性能低下,所有操作都要加一倍數據庫訪問量。
4.可以在本地特定文件夾下把表的列信息存起來,每次反射實體得到sql時候從本地信息比對屬性是否在表裏有。
6.本地沒存表的列信息就沒有疑問直接查詢生成信息就行了。本地存了表的列信息的話可以通過判斷存信息文件和實體動態庫文件的最後修改時間來決定是否要更新本地表的列信息。如果實體動態庫修改時間晚於本地的表的列信息文件那麼本地列信息文件需要更新,否則本地信息是可靠的。
7.基於以上的過程就可以用代碼實現ORM的實體過量容錯功能了,達到給項目不比對直接換最新的實體的目的了。

示例和效果

//創建表信息實體
            TableInfo tableInfo = new TableInfo();
            //獲得實體類型
            Type type = entity.GetType();
            //存類型
            tableInfo.ModelType = type;
            //獲得針對數據庫處理的表名
            tableInfo.TableName = adoHelper.DbFactory.DealTableName(GetTableName(type));
            
            //獲得所有屬性數組
            PropertyInfo[] properties = ReflectionUtils.GetProperties(type);
            //檢測實體和表;列的信息
            CheakOrmColInfo(tableInfo.TableName);
            //遍歷屬性
            foreach (PropertyInfo property in properties)
            {
                //檢測數據庫列信息是否包含
                if (SysTableMap.TableColInfo.ContainsKey(tableInfo.TableName))
                {
                    string colJson = SysTableMap.TableColInfo[tableInfo.TableName];
                    //數據庫不包含的列信息退出
                    if (!colJson.Contains("\"" + property.Name + "\""))
                    {
                        continue;
                    }
                }
/// <summary>
        /// 檢查orm的列信息
        /// </summary>
        /// <param name="tableName">表名</param>
        private void CheakOrmColInfo(string tableName)
        {
            //ORM運行的臨時文件
            string OrmTmpPath = HttpRuntime.AppDomainAppPath.ToString() + "\\Bin\\OrmTmpFile";
            //有目錄纔開啓檢測
            if (Directory.Exists(OrmTmpPath))
            {
                //表列信息名稱
                string curTableColFile = OrmTmpPath + "\\" + tableName + ".orm";
                //不存在orm列文件就創建
                if (!File.Exists(curTableColFile))
                {
                    //更新orm的列文件
                    UpdateOrmColFile(tableName, curTableColFile);
                    //加載並刷新列信息
                    LoadTableColInfo(tableName, true);
                }
                //或者文件日期比實體dll老也新建文件
                else
                {
                    FileInfo fiModel = new FileInfo(HttpRuntime.AppDomainAppPath.ToString() + "\\Bin\\LIS.Model.dll");
                    FileInfo fiOrm = new FileInfo(curTableColFile);
                    //實體dll更新時候更新列信息文件
                    if (fiModel.LastWriteTime > fiOrm.LastWriteTime)
                    {
                        //更新orm的列文件
                        UpdateOrmColFile(tableName, curTableColFile);
                        //加載並刷新列信息
                        LoadTableColInfo(tableName, true);
                    }
                    else
                    {
                        //沒有就加載列信息
                        LoadTableColInfo(tableName, false);
                    }

                }
            }
        }

        /// <summary>
        /// 更新orm的列文件
        /// </summary>
        /// <param name="tableName">表名稱</param>
        /// <param name="savepath">保存路徑</param>
        private void UpdateOrmColFile(string tableName, string savepath)
        {
            //查詢表列名
            string sql = "select COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dbo' AND TABLE_NAME='" + tableName.Replace("dbo.", "") + "'";
            LIS.DAL.ORM.EntityManager.EntityManagerImpl manager = new LIS.DAL.ORM.EntityManager.EntityManagerImpl();
            string err = "";
            string retJson = manager.ExecSelectSQL(sql, false, -1, -1, out err);
            if (err != "")
            {
                LIS.Core.Util.LogUtils.WriteExceptionLog("查詢表:" + tableName + "列信息異常", new Exception(err));
            }
            else
            {
                WriteTxt(savepath, retJson);
            }
        }

        /// <summary>
        /// 加載表列信息到內存
        /// </summary>
        /// <param name="tableName">表名</param>
        /// <param name="isResfresh">是否刷新</param>
        private void LoadTableColInfo(string tableName, bool isResfresh)
        {
            //刷新緩存
            if (isResfresh == true)
            {
                if (SysTableMap.TableColInfo.ContainsKey(tableName))
                {
                    SysTableMap.TableColInfo.Remove(tableName);
                }
            }
            //有數據之間返回
            if (SysTableMap.TableColInfo.ContainsKey(tableName))
            {
                return;
            }
            //ORM運行的臨時文件
            string OrmTmpPath = HttpRuntime.AppDomainAppPath.ToString() + "\\Bin\\OrmTmpFile";
            //有目錄纔開啓檢測
            if (Directory.Exists(OrmTmpPath))
            {
                //表列信息名稱
                string curTableColFile = OrmTmpPath + "\\" + tableName + ".orm";
                if (File.Exists(curTableColFile))
                {
                    string colJson = ReadTxt(curTableColFile);
                    SysTableMap.TableColInfo.Add(tableName, colJson);
                }
            }

        }

        /// <summary>
        /// 讀取文件數據
        /// </summary>
        /// <param name="path">文件全路徑</param>
        /// <returns></returns>
        public static string ReadTxt(string path)
        {
            //文件不存在
            if (!File.Exists(path))
            {
                return "";
            }
            FileStream fs = null;
            try
            {

                fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                StreamReader sr = new StreamReader(fs, Encoding.Default);
                string str = sr.ReadToEnd();
                return str;
            }
            catch (Exception ex)
            {
                LIS.Core.Util.LogUtils.WriteExceptionLog("讀表列信息異常", ex);
                return "";
            }
            finally
            {
                fs.Close();
            }
        }

        /// <summary>
        /// 寫入數據到指定文件
        /// </summary>
        /// <param name="path">文件全路徑</param>
        /// <param name="str">數據</param>
        /// <param name="isReplace">是否提換,默認爲替換,否則爲添加</param>
        /// <returns></returns>
        public bool WriteTxt(string path, string str, bool isReplace = true, Encoding ecod = null)
        {
            if (ecod == null)
            {
                ecod = Encoding.Default;
            }
            FileStream fs = null;
            StreamWriter sw1 = null;
            try
            {
                //如果文件不存在,先創建一個
                if (!File.Exists(path))
                {
                    //創建寫入文件  
                    fs = new FileStream(path, FileMode.Create, FileAccess.Write);
                    sw1 = new StreamWriter(fs, ecod);
                    //開始寫入值  
                    sw1.WriteLine(str);
                }
                else
                {
                    //如果是替換,先清除之前的內容
                    if (isReplace)
                    {
                        using (StreamWriter sw = new StreamWriter(path, false, ecod))
                        {
                            sw.Write("");
                            sw.Close();
                        }
                    }
                    fs = new FileStream(path, FileMode.Append, FileAccess.Write);
                    sw1 = new StreamWriter(fs, ecod);
                    sw1.WriteLine(str);
                }
                return true;
            }
            catch (Exception ex)
            {
                LIS.Core.Util.LogUtils.WriteExceptionLog("寫表列信息異常", ex);
                return false;
            }
            finally
            {
                if (sw1 != null)
                {
                    sw1.Close();
                }
                if (fs != null)
                {
                    fs.Close();
                }
            }
        }

在這裏插入圖片描述

總結
本地數據比對經驗來自於上一家入職的公司(比較文件的最後修改時間)。對數據量大的基礎數據加載提速有用,比如:幾萬條醫囑醫囑(該數據的特點是量大、每次都要、變化不頻繁、只標記刪除)。對這種特點的數據就可以採用本地緩存策略,在數據庫表裏增加修改時間字段,任何修改都存最後修改時間。本地緩存就可以採用如果沒緩存的數據就查詢所有數據,否則就查詢更新時間大於本地緩存文件時間的數據來與本地緩存數據合併。這樣就能把每次查詢返回幾萬條數據的傳輸量減小到這段時間更新的幾個或幾十個的量,使速度大大的提升。
ORM實現也可以對實體和數據庫表的一致性提供更寬鬆的映射支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章