實戰分層架構


現在可選的框架

    現在我們開發一個.net應用,面臨的選擇比較多。我們可以選擇entity framework, enterprise library, nhibernate, 還有一個mybatis.net, 即java世界mybatis/ibatis的.net版。 IOC的框架可以選擇Unity, Ninject,Spring.net(java的spring對應的.net版本)。Entity framework可以使用linq查詢,有好幾種開發模式,如code first, db first, 可以不用寫sql。Entity framework適合sql server。雖有mysql提供了entity framework的provider,但是不是很好, 經常不得不單獨寫sql來操作mysql. Enterprise library是個不錯的選擇,有log, exception, policy injection的一些東西,操作數據庫也比較好。nhibernate是java版hibernate的.net對應版本。對數據庫包裝得比較多,可以不用寫sql. 但是有些時候對數據庫操作不夠優化,會有一些多余數據庫操作。帶來一些性能的影響。mybatis.net是個介於ado.net與nhibernate之間的框架,它負責數據庫對象與內存對象的映射。程序員必須寫sql語句來操作數據庫。IOC的框架中Unity, Ninject, Spring.net, 都是不錯的框架。有時這些框架能照顧到我們大部分的需求,也有一些情況,不能全部照顧到我們的需求。這時就得用一些老辦法。比如直接就用ado.net

適應多種數據庫的db helper代碼

    這里主要是想說一些基於ado.net來開發asp.net mvc應用的一些思路。為什么選用ado.net呢,ado.net性能可以達到最好,靈活。先看一段db helper的代碼:

using System;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;

namespace DataAccessCommon
{
    /// <summary>
    /// The MyDBHelper class is intended to encapsulate high performance, scalable best practices for 
    /// common uses of SqlClient, OracleClient, OleDb, and others
    /// </summary>
    public static class MyStaticDBHelper
    {
        public struct MyDBParameter
        {
            public string strParameterName;
            public DbType dbType;
            public object value;
            public ParameterDirection parameterDirection;

            public MyDBParameter(string parameterName, DbType type, object theValue, ParameterDirection direction = ParameterDirection.Input)
            {
                strParameterName = parameterName;
                dbType = type;
                value = theValue;
                parameterDirection = direction;
            }
        }
        public static string DatabaseType = "SqlServer";
        private static Dictionary<string, string> providers = new Dictionary<string, string>() {
        { "SqlServer", "System.Data.SqlClient" }
        , { "Oracle", "System.Data.OracleClient" }
        , { "OleDb", "System.Data.OleDb" } 
        };
        private static DbProviderFactory dataFactory = DbProviderFactories.GetFactory(providers[DatabaseType]);
        public static string CONNECTION_STRING = ConfigurationManager.AppSettings["ConnectionString"];

        #region private methods
        private static void AttachParameters(DbCommand command, DbParameter[] parameters)
        {
            if (parameters != null)
            {
                command.Parameters.AddRange(parameters);
            }
        }

        private static DbCommand CreateCommand(object conn)
        {
            DbCommand command = null;
            //If it is just a connection(not a transaction)
            if (conn is DbConnection)
            {
                command = ((DbConnection)conn).CreateCommand();
                if (command.Connection.State != ConnectionState.Open)
                {
                    command.Connection.Open();
                }
            }
            else //It is a transaction, then join the transaction
            {
                command = ((DbTransaction)conn).Connection.CreateCommand();
                command.Transaction = (DbTransaction)conn;
            }
            return command;
        }

        private static DbCommand SetupCommand(object conn, CommandType commandType, string strSQLOrSPName, List<MyDBParameter> myDBParameters)
        {
            DbParameter[] parameters = myDBParameters != null ? CreateDBParameters(myDBParameters).ToArray() : null;
            DbCommand command = CreateCommand(conn);
            command.CommandText = strSQLOrSPName;
            command.CommandType = commandType;
            AttachParameters(command, parameters);
            return command;
        }

        private static DbParameter CreateDBParameter(string strParameterName, DbType dbType, object value, ParameterDirection direction)
        {
            DbParameter parameter = dataFactory.CreateParameter();
            parameter.ParameterName = strParameterName;
            parameter.DbType = dbType;
            parameter.Value = value;
            parameter.Direction = direction;
            return parameter;
        }

        private static List<DbParameter> CreateDBParameters(List<MyDBParameter> myDBParameters)
        {
            List<DbParameter> parameters = new List<DbParameter>();
            foreach (MyDBParameter myDBParameter in myDBParameters)
            {
                parameters.Add(CreateDBParameter(myDBParameter.strParameterName, myDBParameter.dbType, myDBParameter.value, myDBParameter.parameterDirection));
            }
            return parameters;
        }
        #endregion

        public static DbConnection GetConnection()
        {
            DbConnection connection = dataFactory.CreateConnection();
            connection.ConnectionString = CONNECTION_STRING;
            return connection;
        }

        public static int ExecuteNonQuery(object conn, CommandType commandType, string strSQLOrSPName, List<MyDBParameter> myDBParameters = null)
        {
            DbCommand command = SetupCommand(conn, commandType, strSQLOrSPName, myDBParameters);
            return command.ExecuteNonQuery();
        }

        public static DataSet ExecuteDataset(object conn, CommandType commandType, string strSQLOrSPName, List<MyDBParameter> myDBParameters = null)
        {
            DbCommand command = SetupCommand(conn, commandType, strSQLOrSPName, myDBParameters);
            DbDataAdapter dataAdaptor = dataFactory.CreateDataAdapter();
            DataSet ds = new DataSet();
            dataAdaptor.SelectCommand = command;
            dataAdaptor.Fill(ds);
            return ds;
        }

        public static DbDataReader ExecuteReader(object conn, CommandType commandType, string strSQLOrSPName, List<MyDBParameter> myDBParameters = null)
        {
            DbCommand command = SetupCommand(conn, commandType, strSQLOrSPName, myDBParameters);
            return command.ExecuteReader();
        }

        public static object ExecuteScalar(object conn, CommandType commandType, string strSQLOrSPName, List<MyDBParameter> myDBParameters = null)
        {
            DbCommand command = SetupCommand(conn, commandType, strSQLOrSPName, myDBParameters);
            return command.ExecuteScalar();
        }
    }
}

此代碼能支持訪問Oracle, sql server, OleDB。用的都是DbConnection之類的。只要開始選擇了正確的provider, DbProviderFactories就給創建相應的connection, command等類,就可以順利地處理這個對應的數據庫了。sql的參數是DbType。用來適應數據庫類型。MyDBParameter結構封裝了參數名,類型,參數值,傳入傳出方向。目前的版本只考慮了一個數據庫連接。連接串只有一個。DbProviderFactory只有一個實例。沒有考慮到動態切換連接的情況。如果是要多個連接,得要多個DbProviderFactory的實例。CreateCommand方法里判斷了傳入的的數據庫連接是一個DbConnection還是一個DbTransaction,如果是一個DbTransaction的話,可以加入這個數據庫事務。如果只是一個DbConnection則不加入已有的數據庫事務,使用自動的數據庫事務。

數據實體類

using System;
using System.Collections.Generic;

namespace DataEntity
{
    public class UserMenuItem
    {
        #region Properties
        public int MenuItemID { get; set; }
        public string MenuItemName { get; set; }
        public int MenuID { get; set; }
        public int Ordinal { get; set; }
        public int Indent { get; set; }
        #endregion
    }
}

純數據的類。這里使用了比較老的c#語法。也可以加上DataAnnotation的標簽。可以實現驗證數據,也可以加上Display標簽,引用資源文件。這個數據實體類在MVC頁面里綁定時可以顯示想應的label。label的內容來自於資源文件,便於使用多語言的界面。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Resource.Entity;

namespace DataEntity
{
    public class UserAccount
    {
        #region Properties
        public int ID { get; set; }

        [Required(ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName="Common_Required_ErrorMessage")]
        [StringLength(30, ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "NAME_StringLength_ErrorMessage")]
        [RegularExpression(@"[a-zA-Z].*", ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "NAME_RegularExpression_ErrorMessage")]
        [Display(ResourceType=typeof(Resource.Entity.UserAccount), Name="NAME_DisplayName")]
        public string Name { get; set; }

        [Required(ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")]
        [RegularExpression(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
            , ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName="EMAIL_RegularExpression_ErrorMessage")]
        [Display(ResourceType=typeof(Resource.Entity.UserAccount), Name="EMAIL_DisplayName")]
        public string Email { get; set; }

        [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "PASSWORD_DisplayName")]
        [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")]
        [StringLength(32, ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "PASSWORD_StringLength", MinimumLength = 8)]
        public string Password { get; set; }

        [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "Balance")]
        public decimal Balance { get; set; }

        [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")]
        [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "CONFIRMPASSWORD_DisplayName")]
        [Compare("Password", ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "CONFIRMPASSWORD_CompareErrorMessage")]
        public string ConfirmPassword { get; set; }

        [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")]
        [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "OLDNAME_DisplayName")]
        public string OldName { get; set; }

        [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")]
        [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "OLDEMAIL_DisplayName")]
        public string OldEmail { get; set; }

        [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")]
        [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "OLDPassword_DisplayName")]
        public string OldPassword { get; set; }
        #endregion
    }
}

 

下面是數據訪問的代碼:

using System;
using System.Collections.Generic;
using System.Data;
using DataEntity;
using DataAccessCommon;

namespace DataAccess
{
    public class DALUserMenuItem
    {
        #region data access methods

        public int DeleteUserMenuItem(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuItemID", DbType.Int32, usermenuitem.MENUITEMID)
            };

            string strSQL = "DELETE FROM [UserMenuItem]   WHERE   [MenuItemID] = @MenuItemID";

            int result = 0;
            result = MyStaticDBHelper.ExecuteNonQuery(conn, System.Data.CommandType.Text, strSQL, paras);
            return result;

        }

        public int UpdateUserMenuItem(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuItemName", DbType.String, usermenuitem.MENUITEMNAME),
                new MyStaticDBHelper.MyDBParameter("@MenuID", DbType.Int32, usermenuitem.MENUID),
                new MyStaticDBHelper.MyDBParameter("@Ordinal", DbType.Int32, usermenuitem.ORDINAL),
                new MyStaticDBHelper.MyDBParameter("@MenuItemID", DbType.Int32, usermenuitem.MENUITEMID)
            };

            string strSQL = "UPDATE [UserMenuItem] SET   [MenuItemName] = @MenuItemName,  [MenuID] = @MenuID,  [Ordinal] = @Ordinal  WHERE   [MenuItemID] = @MenuItemID";

            int result = 0;
            result = MyStaticDBHelper.ExecuteNonQuery(conn, System.Data.CommandType.Text, strSQL, paras);
            return result;

        }

        public int AddUserMenuItem(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuItemName", DbType.String, usermenuitem.MENUITEMNAME),
                new MyStaticDBHelper.MyDBParameter("@MenuID", DbType.Int32, usermenuitem.MENUID),
                new MyStaticDBHelper.MyDBParameter("@Ordinal", DbType.Int32, usermenuitem.ORDINAL),
                new MyStaticDBHelper.MyDBParameter("@MenuItemID", DbType.Int32, usermenuitem.MENUITEMID)
            };

            string strSQL = "INSERT INTO [UserMenuItem] (  [MenuItemName] ,  [MenuID] ,  [Ordinal]  ) VALUES( @MenuItemName, @MenuID, @Ordinal );  SELECT SCOPE_IDENTITY() as [MenuItemID]";

            int result = 0;
            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL, paras);
            if (ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0){
                usermenuitem.MENUITEMID = Convert.ToInt32(ds.Tables[0].Rows[0][0]);
                result = 1;
            }
            return result;

        }

        public List<UserMenuItem> GetAllUserMenuItem(Object conn)
        {

            string strSQL = "SELECT * FROM [UserMenuItem] ";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL);

            return DataMapper.MapDataTableToObjectList<UserMenuItem>(ds.Tables[0]);

        }

        public UserMenuItem FindAUserMenuItem(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuItemID", DbType.Int32, usermenuitem.MENUITEMID)
            };

            string strSQL = "SELECT * FROM [UserMenuItem]   WHERE   [MenuItemID] = @MenuItemID";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL, paras);

            return DataMapper.MapDataTableToSingleRow<UserMenuItem>(ds.Tables[0]);

        }

        public System.Int32 SelectCountUserMenuItem(Object conn)
        {

            string strSQL = "SELECT COUNT(1) AS Count FROM [UserMenuItem] ";

            Object obj = null;
            obj = MyStaticDBHelper.ExecuteScalar(conn, System.Data.CommandType.Text, strSQL);
            return (System.Int32)obj;

        }

        public System.Int32 SelectCountWhereClauseUserMenuItem(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuItemID", DbType.Int32, usermenuitem.MENUITEMID)
            };

            string strSQL = "SELECT COUNT(1) AS Count FROM [UserMenuItem]   WHERE   [MenuItemID] = @MenuItemID";

            Object obj = null;
            obj = MyStaticDBHelper.ExecuteScalar(conn, System.Data.CommandType.Text, strSQL, paras);
            return (System.Int32)obj;

        }

        public List<UserMenuItem> SelectTopUserMenuItem(Object conn)
        {

            string strSQL = "SELECT Top 50 *  FROM [UserMenuItem] ";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL);

            return DataMapper.MapDataTableToObjectList<UserMenuItem>(ds.Tables[0]);

        }

        public List<UserMenuItem> SelectOrderByPrimaryKeyUserMenuItem(Object conn)
        {

            string strSQL = "SELECT *  FROM [UserMenuItem] ORDER BY [MenuItemID] , [MenuItemName] , [MenuID] , [Ordinal]";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL);

            return DataMapper.MapDataTableToObjectList<UserMenuItem>(ds.Tables[0]);

        }

        public List<UserMenuItem> SelectGroupByPrimaryKeyUserMenuItem(Object conn)
        {

            string strSQL = "SELECT * FROM [UserMenuItem] GROUP BY [MenuItemID] , [MenuItemName] , [MenuID] , [Ordinal]";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL);

            return DataMapper.MapDataTableToObjectList<UserMenuItem>(ds.Tables[0]);

        }

        public List<UserMenuItem> SelectUserMenuItemsByMenuID(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuID", DbType.Int32, usermenuitem.MENUID)
            };
            
            string strSQL = "SELECT * FROM [UserMenuItem] WHERE [MenuID] = @MenuID ORDER BY [Ordinal]";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL, paras);

            return DataMapper.MapDataTableToObjectList<UserMenuItem>(ds.Tables[0]);
        }

        public UserMenuItem SelectPreviousMenuItem(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuID", DbType.Int32, usermenuitem.MENUID),
                new MyStaticDBHelper.MyDBParameter("@Ordinal", DbType.Int32, usermenuitem.ORDINAL)
            };

            string strSQL = "SELECT TOP 1 * FROM [UserMenuItem] WHERE [MenuID] = @MenuID AND [Ordinal] < @Ordinal ORDER BY [Ordinal] DESC";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL, paras);

            return DataMapper.MapDataTableToSingleRow<UserMenuItem>(ds.Tables[0]);
        }

        public UserMenuItem SelectNextMenuItem(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuID", DbType.Int32, usermenuitem.MENUID),
                new MyStaticDBHelper.MyDBParameter("@Ordinal", DbType.Int32, usermenuitem.ORDINAL)
            };

            string strSQL = "SELECT TOP 1 * FROM [UserMenuItem] WHERE [MenuID] = @MenuID AND [Ordinal] > @Ordinal ORDER BY [Ordinal] ASC";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL, paras);

            return DataMapper.MapDataTableToSingleRow<UserMenuItem>(ds.Tables[0]);
        }

        public int MoveLeftMenuItem(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuItemID", DbType.Int32, usermenuitem.MENUITEMID)
            };

            string strSQL = "UPDATE [UserMenuItem] SET [Indent] = CASE WHEN [Indent] - 1 >= 0 THEN [Indent] - 1 ELSE 0 END WHERE [MenuItemID] = @MenuItemID";

            int iResult = 0;
            iResult = MyStaticDBHelper.ExecuteNonQuery(conn, System.Data.CommandType.Text, strSQL, paras);

            return iResult;
        }

        public int MoveRightMenuItem(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuItemID", DbType.Int32, usermenuitem.MENUITEMID)
            };

            string strSQL = "UPDATE [UserMenuItem] SET [Indent] = CASE WHEN [Indent] + 1 <= 2 THEN [Indent] + 1 ELSE 2 END WHERE [MenuItemID] = @MenuItemID";

            int iResult = 0;
            iResult = MyStaticDBHelper.ExecuteNonQuery(conn, System.Data.CommandType.Text, strSQL, paras);

            return iResult;
        }

        public UserMenuItem SelectMaxOrdinal(Object conn, UserMenuItem usermenuitem)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@MenuID", DbType.Int32, usermenuitem.MENUID)
            };

            string strSQL = "SELECT IsNull(Max(Ordinal),0) as Ordinal FROM [UserMenuItem] WHERE [MenuID] = @MenuID";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL, paras);

            return DataMapper.MapDataTableToSingleRow<UserMenuItem>(ds.Tables[0]);
        }
        #endregion
    }
}

這個數據訪問的代碼都是這種方式,開始准備參數。用的都是MyStaticDBHelper.MyDBParameter結構。給出參數名,參數類型,參數值和參數方向(輸入還是輸出)。然后就是一個sql語句,其中有參數。之后是根據查詢的類型,update/delete/insert的就調用db helper的ExecuteNonQuery方法,如果是select一個表的話,調用db helper的ExecuteDataset方法。再之后,就要將返回的值給轉換成對應的返回類型。如一個實體對象或者實體對象列表。這個類里的sql語句都是預先設計好的sql語句,每個sql語句都有參數,然后每個sql查詢都有一個c#方法與之對應。DataMapper.MapDataTableToSingleRow是將DataTable中第一行轉換成一個實體對象。DataMapper.MapDataTableToObjectList是將返回的DataTable轉換成實體類的列表, 即List<實體類>,這里DataMapper類采用了Reflection的方式來進行轉換實體類。雖然不是最快的。在某些情況下也可以接受。我們做過一個實例程序來對比,將DataTable轉成實體類列表,有直接賦值,Emit, Reflection, delegate, Expression tree等不同的方法,經過性能測試,直接賦值是最快的。Emit稍微比直接賦值慢,但是已經很快了。直接賦值寫代碼比較繁瑣。Emit的方法代碼稍微有點復雜,但是運行效率不錯。又比較靈活。是個相當好的方法,Emit和Expresssion Tree的方法有一些缺點,就是很難調試,萬一出現問題會很難找到問題的根源。這里是比較不同方法將DataTable轉成數據實體類的測試代碼:  http://files.cnblogs.com/mikelij/testGenerateEntity.zip, 大家可以下載了去試試,應該說這幾種方法都還不錯。這里的代碼選用了Reflection方法。因為Reflection也沒有慢得很多。Reflection方法的兼容性好。不會出問題。Emit和Express tree方法經常會遇到不兼容類型的問題。而且很難排查問題。

 數據映射的類

using System;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.Reflection;

namespace DataAccessCommon
{
    public class DataMapper
    {

        public static List<TType> MapDataTableToObjectList<TType>(DataTable dt) where TType : new()
        {
            List<TType> result = new List<TType>();
            foreach (DataRow currentRow in dt.Rows)
            {
                TType ttype = new TType();
                for (int i = 0; i < dt.Columns.Count; i++)
                {
                    for (int j = 0; j < ttype.GetType().GetProperties().Length; j++)
                    {
                        if (dt.Columns[i].ColumnName.ToUpper() == ttype.GetType().GetProperties()[j].Name.ToUpper())
                        {
                            ttype.GetType().GetProperties()[j].SetValue(ttype, currentRow[i], null);
                            break;
                        }
                    }
                }
                result.Add(ttype);
                ttype = new TType();
            }
            return result;
        }

        public static TType MapDataTableToSingleRow<TType>(DataTable dt) where TType : new()
        {
            TType ttype = new TType();
            if (dt.Rows.Count > 0)
            {
                DataRow currentRow = dt.Rows[0];
                for (int i = 0; i < dt.Columns.Count; i++)
                {
                    for (int j = 0; j < ttype.GetType().GetProperties().Length; j++)
                    {
                        if (dt.Columns[i].ColumnName.ToUpper() == ttype.GetType().GetProperties()[j].Name.ToUpper())
                        {
                            ttype.GetType().GetProperties()[j].SetValue(ttype, currentRow[i], null);
                            break;
                        }
                    }
                }
            }
            return ttype;
        }
    }
}

商業類的代碼

商業類的代碼是基於我們OOA/OOD設計出的。比如一個銀行ATM的例子,其業務里有若干名詞,比如銀行戶頭,ATM機等名詞,每個名詞下又有若干屬性,比如銀行帳號,帳號所有者名字,開立日期等,ATM機有ATM機號,地理位置,所屬銀行編號,等等。圍繞着這些名詞,有相關的一些動作。比如取錢,存錢,插卡入ATM機,記錄ATM流水。等等等等。這里已經將名詞的數據屬性放到了數據實體類里。這些數據實體類里就只有那些名詞的數據屬性,沒有那些動作,即一個純數據的類。這里要提到的商業類包含了商業方法的類,這些商業方法就對應着那些動作。比如取錢,就有一個Withdraw方法對應。存錢就就一個Deposite方法對應。這兩個方法都放在一個叫BankAccount的的商業類里面。這里用的銀行的例子,說明這里所用到的設計方法。

使用Unity之類的IOC容器進行policy injection

下面就要說說IOC了,就是說我們設計一個商業類,里面有幾個商業方法。如果讓調用者直接依賴於這個商業類,那么將來有一天要改變這些商業方法時,可能就不得不同時改調用者和商業類。為了避免這種情況,我們可以從商業類提取出一個接口。這個接口只有這些動作的名字,沒有具體具體實現。然后由負責具體實現的商業類來實現這些接口。說了這些東西與IOC有什么關系呢?這樣做正是為了實現IOC打下基礎。要知道象Unity這樣的IOC容器,都是負責創建對象。它負責從接口映射到具體的商業類。當調用者需要創建一個接口的實例,接口本身是不能實例化的,容器會為調用者創建一個實現了該接口商業類的實例。

一個商業接口的例子:

using System;
namespace BusinessLogic
{
    [MyDBHandler]
    public interface IBLLUserMenu
    {
        int AddUserMenu(DataEntity.UserMenu usermenu);
        int DeleteUserMenu(DataEntity.UserMenu usermenu);
        DataEntity.UserMenu FindAUserMenu(DataEntity.UserMenu usermenu);
        System.Collections.Generic.List<DataEntity.UserMenu> GetAllUserMenu();
        int SelectCountUserMenu();
        int SelectCountWhereClauseUserMenu(DataEntity.UserMenu usermenu);
        System.Collections.Generic.List<DataEntity.UserMenu> SelectGroupByPrimaryKeyUserMenu();
        System.Collections.Generic.List<DataEntity.UserMenu> SelectMenusByApplicationID(DataEntity.UserMenu usermenu);
        System.Collections.Generic.List<DataEntity.UserMenu> SelectOrderByPrimaryKeyUserMenu();
        System.Collections.Generic.List<DataEntity.UserMenu> SelectTopUserMenu();
        int UpdateUserMenu(DataEntity.UserMenu usermenu);
        object CONNECTION { get; set; }
        DataEntity.UserMenu USERMENU { get; set; }
        System.Collections.Generic.List<DataEntity.UserMenu> USERMENU_LIST { get; set; }
    }
}

此代碼中的MyDBHandler是一個字定義的attribute, 用於Unity來進行攔截判斷。有這個attribute就攔截,沒有就不攔截。
而相應的商業類就是這樣的:

using System;
using System.Collections.Generic;
using System.Data;
using DataEntity;
using DataAccess;
using DataAccessCommon;
using CommonUtil;

namespace BusinessLogic
{
    internal class BLLUserMenu : BusinessLogic.IBLLUserMenu
    {
        private readonly DataAccess.DALUserMenu dal = new DataAccess.DALUserMenu();
        private object conn = null;
        private UserMenu usermenu;
        private List<UserMenu> usermenus;

        public object CONNECTION
        {
            get
            {
                return conn;
            }
            set
            {
                conn = value;
            }
        }
        
        public UserMenu USERMENU
        {
            get
            {
                return usermenu;
            }
            set
            {
                usermenu = value;
            }
        }
        
        public List<UserMenu> USERMENU_LIST
        {
            get
            {
                return usermenus;
            }
            set
            {
                usermenus = value;
            }
        }
        
        #region business logic method

        public int DeleteUserMenu(UserMenu usermenu)
        {
            return dal.DeleteUserMenu(conn,usermenu);
        }

        public int UpdateUserMenu(UserMenu usermenu)
        {
            return dal.UpdateUserMenu(conn,usermenu);
        }

        public int AddUserMenu(UserMenu usermenu)
        {
            return dal.AddUserMenu(conn,usermenu);
        }

        public List<UserMenu> GetAllUserMenu()
        {
            return dal.GetAllUserMenu(conn);
        }

        public UserMenu FindAUserMenu(UserMenu usermenu)
        {
            return dal.FindAUserMenu(conn,usermenu);
        }

        public System.Int32 SelectCountUserMenu()
        {
            return dal.SelectCountUserMenu(conn);
        }

        public System.Int32 SelectCountWhereClauseUserMenu(UserMenu usermenu)
        {
            return dal.SelectCountWhereClauseUserMenu(conn,usermenu);
        }

        public List<UserMenu> SelectTopUserMenu()
        {
            return dal.SelectTopUserMenu(conn);
        }

        public List<UserMenu> SelectOrderByPrimaryKeyUserMenu()
        {
            return dal.SelectOrderByPrimaryKeyUserMenu(conn);
        }

        public List<UserMenu> SelectGroupByPrimaryKeyUserMenu()
        {
            return dal.SelectGroupByPrimaryKeyUserMenu(conn);
        }

        public List<UserMenu> SelectMenusByApplicationID(UserMenu usermenu)
        {
            return dal.SelectMenusByApplicationID(conn, usermenu);
        }
        #endregion
    }
}

目前這個商業類的方法都比較簡單,如果有比較復雜的,可能一個商業方法里需要調用數據訪問的方法好多個,在做一些邏輯判斷。那么這些商業方法就可以變得復雜多了。如這樣的一個商業方法:

        public bool MoveUpItem(UserMenuItem usermenuitem)
        {
            usermenuitem = dal.FindAUserMenuItem(conn, usermenuitem);
            UserMenuItem previousMenuItem = dal.SelectPreviousMenuItem(conn, usermenuitem);
            int iTempOrdinal = usermenuitem.Ordinal;
            usermenuitem.Ordinal = previousMenuItem.Ordinal;
            previousMenuItem.Ordinal = iTempOrdinal;
            dal.UpdateUserMenuItem(conn, usermenuitem);
            dal.UpdateUserMenuItem(conn, previousMenuItem);
            return true;
        }

Unity配置信息:

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration" />
    <namespace name="BusinessLogic" />
    <container name="myContainer">
      <extension type="Interception" />
      <register type="BusinessLogic.IBLLApplication, BusinessLogic" mapTo="BusinessLogic.BLLApplication, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLDomain, BusinessLogic" mapTo="BusinessLogic.BLLDomain, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLFormElement, BusinessLogic" mapTo="BusinessLogic.BLLFormElement, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLUserAccount, BusinessLogic" mapTo="BusinessLogic.BLLUserAccount, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLUserColumns, BusinessLogic" mapTo="BusinessLogic.BLLUserColumns, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLUserForm, BusinessLogic" mapTo="BusinessLogic.BLLUserForm, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLUserMenu, BusinessLogic" mapTo="BusinessLogic.BLLUserMenu, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLUserMenuItem, BusinessLogic" mapTo="BusinessLogic.BLLUserMenuItem, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLUserSession, BusinessLogic" mapTo="BusinessLogic.BLLUserSession, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLUserTables, BusinessLogic" mapTo="BusinessLogic.BLLUserTables, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <interception>
        <policy name="mypolicy">
          <callHandler name="myHandler1" type="BusinessLogic.MyDBHandler, BusinessLogic"></callHandler>
          <matchingRule name="myrule" type="CustomAttributeMatchingRule">
            <constructor>
              <param name="attributeType" type="System.Type, mscorlib">
                <value value="BusinessLogic.MyDBHandlerAttribute, BusinessLogic" typeConverter="BusinessLogic.GetTypeConverter, BusinessLogic" />
              </param>
              <param name="inherited" type="bool">
                <value value="true" />
              </param>
            </constructor>
          </matchingRule>
        </policy>
      </interception>
    </container>
  </unity>

注意到這些register的節點沒有?這些節點實現了接口到具體商業類的映射。接口表示的是一個抽象。它只有方法的聲明,沒有具體實現。在調用者需要一個具體的實現了這個接口的商業類時,容器幫助我們創建這個商業類的實例,而接口到商業類的映射就是在Unity配置文件里做的。
Unity除了幫助我們實現接口到商業類的映射,還可以幫助我們實現aop. 比如log, db transaction, exception handling.

using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using Microsoft.Practices.Unity.InterceptionExtension;
using DataAccessCommon;
using CommonUtil;

namespace BusinessLogic
{
    public class MyDBHandler : ICallHandler
    {
        private int iOrder = 0;

        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            var retvalue = getNext()(input, getNext); // call the intercepting method
            if (retvalue.Exception != null)
            {
                SysLog.GetInstance().LogError(retvalue.Exception);
            }
            return retvalue;
        }

        public int Order
        {
            get
            {
                return iOrder;
            }
            set
            {
                iOrder = value;
            }
        }
    }
}

這個MyDBHandler已經在之前的Unity配置中指定了。即這句:

<callHandler name="myHandler1" type="BusinessLogic.MyDBHandler, BusinessLogic"></callHandler>

這句是去調用被攔截的方法:

retvalue = getNext()(input, getNext);

被攔截方法(即我們的商業方法)返回以后,程序就檢查retvalue.Exception有沒有出錯,有就調用logging的類來寫log。將出錯信息完整地打印出來。
自定義的attribute類:

using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace BusinessLogic
{
    public class MyDBHandlerAttribute : HandlerAttribute
    {
        public override ICallHandler CreateHandler(IUnityContainer container)
        {
            return new MyDBHandler();
        }
    }
}

至於db transaction, 如果數據庫事務比較簡單,可以用TransactionScope,前面的MyDBHandler的invoke方法就替換成這樣。

                using (TransactionScope ts = new TransactionScope())
                {
                    var retvalue = getNext().Invoke(input, getNext);
                    if (retvalue.Exception != null)
                    {
                        SysLog.GetInstance().LogError(retvalue.Exception);
                    }
                    else
                    {
                        ts.Complete();
                    }
                    return retvalue
                }

Unity配置里用到的一個工具類代碼:

using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace BusinessLogic
{
    public class GetTypeConverter : System.ComponentModel.TypeConverter
    {
        public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context,
            System.Globalization.CultureInfo culture,
            object value)
        {
            return Type.GetType(value.ToString());
        }
    }
}

這個類用來做類型轉換的。用來幫助Unity來找自定義的MyDBHandlerAttribute的。

商業類的調用者

為了調用商業類,我們有一個類來幫助創建相應的商業類的實例:

using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace BusinessLogic
{
    public static class BusinessClassCreator
    {
        public static IUnityContainer container = new UnityContainer().LoadConfiguration("myContainer");

        public static T GetInstance<T>()
        {
            return (T)container.Resolve(typeof(T), null);
        }

        public static object GetInstance(Type type)
        {
            return container.Resolve(type, null);
        }
    }
}

在調用的地方代碼這么寫:

        [HttpGet]
        public ActionResult SaveMenuScheme()
        {
            UserMenu userMenu = new UserMenu();
            userMenu.MenuID = GetMenuID(this);
            userMenu = BusinessClassCreator.GetInstance<IBLLUserMenu>().FindAUserMenu(userMenu);
            short bMenuScheme = 0;
            bMenuScheme = (short)DesignTableController.GetID(this);
            userMenu.Scheme = bMenuScheme;
            BusinessClassCreator.GetInstance<IBLLUserMenu>().UpdateUserMenu(userMenu);
            return DisplayMenuList();
        }

這是一個在asp.net mvc中調用上述商業類的樣例代碼,首先通過BusinessClassCreator.GetInstance<IBLLUserMenu>()得到接口IBLLUserMenu對應的商業類對象,然后再調用IBLLUserMenu接口上的方法。比如此例中調用了FindUserMenu方法和UpdateUserMenu方法。每個方法的返回類型已經由接口定義好。我們只要按照這個接口的定義來使用這個接口就可以了。接口在這里的好處就是它定義了商業類的規范,所有實現此接口的商業類都符合此接口的規范。而且具體實現和接口定義是分離的。這樣我們就可以單獨改變實現接口的商業類。商業類的調用者既可以是一個asp.net mvc的程序,也可以是一個asp.net web form的,還可以是一個winform程序。

 demo代碼下載: http://dl.vmall.com/c08haaatpu, 博客園這里上傳不了。沒有辦法。只能選別處了。


免責聲明!

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



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