实战分层架构

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介: 现在可选的框架     现在我们开发一个.net应用,面临的选择比较多。我们可以选择entity framework, enterprise library, nhibernate, 还有一个mybatis.net, 即java世界mybatis/ibatis的.net版。

现在可选的框架

    现在我们开发一个.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, 博客园这里上传不了。没有办法。只能选别处了。

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
目录
相关文章
|
29天前
|
人工智能 前端开发 JavaScript
前端架构思考 :专注于多框架的并存可能并不是唯一的方向 — 探讨大模型时代前端的分层式微前端架构
随着前端技术的发展,微前端架构成为应对复杂大型应用的流行方案,允许多个团队使用不同技术栈并将其模块化集成。然而,这种设计在高交互性需求的应用中存在局限,如音视频处理、AI集成等。本文探讨了传统微前端架构的不足,并提出了一种新的分层式微前端架构,通过展示层与业务层的分离及基于功能的横向拆分,以更好地适应现代前端需求。
|
20天前
|
运维 NoSQL Java
后端架构演进:微服务架构的优缺点与实战案例分析
【10月更文挑战第28天】本文探讨了微服务架构与单体架构的优缺点,并通过实战案例分析了微服务架构在实际应用中的表现。微服务架构具有高内聚、低耦合、独立部署等优势,但也面临分布式系统的复杂性和较高的运维成本。通过某电商平台的实际案例,展示了微服务架构在提升系统性能和团队协作效率方面的显著效果,同时也指出了其带来的挑战。
57 4
|
1月前
|
JSON 前端开发 Java
Spring Boot框架中的响应与分层解耦架构
在Spring Boot框架中,响应与分层解耦架构是两个核心概念,它们共同促进了应用程序的高效性、可维护性和可扩展性。
53 3
|
1月前
|
存储 前端开发 API
DDD领域驱动设计实战-分层架构
DDD分层架构通过明确各层职责及交互规则,有效降低了层间依赖。其基本原则是每层仅与下方层耦合,分为严格和松散两种形式。架构演进包括传统四层架构与改良版四层架构,后者采用依赖反转设计原则优化基础设施层位置。各层职责分明:用户接口层处理显示与请求;应用层负责服务编排与组合;领域层实现业务逻辑;基础层提供技术基础服务。通过合理设计聚合与依赖关系,DDD支持微服务架构灵活演进,提升系统适应性和可维护性。
|
2月前
|
运维 持续交付 API
深入理解并实践微服务架构:从理论到实战
深入理解并实践微服务架构:从理论到实战
137 3
|
2月前
|
存储 缓存 负载均衡
亿级流量架构理论+秒杀实战系列(二)
亿级流量架构理论+秒杀实战系列(二)
|
2月前
|
SQL 缓存 运维
亿级流量架构理论+秒杀实战系列(一)
亿级流量架构理论+秒杀实战系列(一)
|
10天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
9天前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
9天前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
23 1
服务架构的演进:从单体到微服务的探索之旅