好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

可扩展的单据编号生成器 + 简单的解释器

可扩展的单据编号生成器 + 简单的解释器

可扩展的单据编号生成器 + 简单的解释器

背景

在企业应用中单据编号的自定义是一个很常见的需求,能不能抽象一个通用的框架呢?之前写个一篇 自定义密码强度 的博文,感觉他们两个思路应该很相似。就让我们试试吧。

思路 这里的难点在于实现"解释器",比如将"前缀_<日期:yyyy_MM_dd>"解释为“工号生成器”,而且“解释器”的“规则”允许动态增加。

 

实现 代码下载

类图

核心代码

CodeRuleGenerator.cs

  1   using   System;
   2   using   System.Collections.Generic;
   3   using   System.Linq;
   4   using   System.Text;
   5   using   System.Threading.Tasks;
   6  
  7   using   System.Text.RegularExpressions;
   8  
  9   namespace   EntityCodeRuleDemo
  10   {
  11       public   sealed   class   CodeRuleGenerator : ICodeRuleGenerator
  12       {
  13           private   readonly  IEnumerable<ICodeRuleProvider> _providers =  new  List<ICodeRuleProvider> ();
  14  
 15           internal  CodeRuleGenerator(IEnumerable<ICodeRuleProvider>  providers)
  16           {
  17              _providers =  providers;
  18           }
  19  
 20           public   string  Generate( object   entity)
  21           {
  22               var  sb =  new   StringBuilder();
  23  
 24               foreach  ( var  provider  in   _providers)
  25               {
  26                   sb.Append(provider.Generate(entity));
  27               }
  28  
 29               return   sb.ToString();
  30           }
  31       }
  32  }

CodeRuleInterpreter.cs

  1   using   System;
   2   using   System.Collections.Generic;
   3   using   System.Linq;
   4   using   System.Text;
   5   using   System.Threading.Tasks;
   6  
  7   using   System.Text.RegularExpressions;
   8  
  9   using   EntityCodeRuleDemo.RuleProviders;
  10  
 11   namespace   EntityCodeRuleDemo
  12   {
  13       public   static   class   CodeRuleInterpreter
  14       {
  15           private   static  Dictionary<Regex, Func< string , ICodeRuleProvider>> _providerFactorys =  new  Dictionary<Regex, Func< string , ICodeRuleProvider>> ();
  16  
 17           static   CodeRuleInterpreter()
  18           {
  19              SetProviderFactory( new  Regex( "  ^[^<].*?[^>]?$  "  ), LiteralRuleProvider.LiteralRuleProviderFactory);
  20              SetProviderFactory( new  Regex( "  ^<日期(:(?<格式>.*?))?>$  "  ), DateRuleProvider.DateRuleProviderFactory);
  21              SetProviderFactory( new  Regex( "  ^<属性(:(?<名称>.*?))?>$  "  ), PropertyRuleProvider.PropertyRuleProviderFactory);
  22           }
  23  
 24           public   static   void  SetProviderFactory(Regex regex, Func< string , ICodeRuleProvider>  providerFactory)
  25           {
  26              _providerFactorys[regex] =  providerFactory;
  27           }
  28  
 29  
 30           public   static  ICodeRuleGenerator Interpret( string   codeRule)
  31           {
  32               var  providers =  GetProviders(codeRule);
  33  
 34               return   new   CodeRuleGenerator(providers);
  35           }
  36  
 37           private   static  IEnumerable<ICodeRuleProvider> GetProviders( string   codeRule)
  38           {
  39               var  literals = codeRule.Replace( "  <  " ,  "  $<  " ).Replace( "  >  " ,  "  >$  " ).Split( '  $  '  );
  40  
 41               return   literals
  42                  .Where(x => ! string  .IsNullOrEmpty(x))
  43                   .Select(GetProvider)
  44                   .ToList();
  45           }
  46  
 47           private   static  ICodeRuleProvider GetProvider( string   literal)
  48           {
  49               var  providerFactory =  _providerFactorys
  50                  .FirstOrDefault(x =>  x.Key.IsMatch(literal))
  51                   .Value;
  52  
 53               if  (providerFactory ==  null  )
  54               {
  55                   throw   new  FormatException( "  格式化错误  "  );
  56               }
  57  
 58               return   providerFactory(literal);
  59           }
  60       }
  61  }

Program.cs

  1   using   System;
   2   using   System.Collections.Generic;
   3   using   System.Linq;
   4   using   System.Text;
   5   using   System.Threading.Tasks;
   6  
  7   namespace   EntityCodeRuleDemo
   8   {
   9       class   Program
  10       {
  11           static   void  Main( string  [] args)
  12           {
  13               var  employeeCode =  CodeRuleInterpreter
  14                  .Interpret( "  前缀_<日期:yyyy_MM_dd>_<属性:NamePinYin>  "  )
  15                  .Generate( new  Employee { NamePinYin =  "  DUANGW  "   });
  16  
 17               Console.WriteLine(employeeCode);
  18           }
  19       }
  20  
 21       class   Employee
  22       {
  23           public   string  NamePinYin {  get ;  set  ; }
  24           public   string  EmployeeCode {  get ;  set  ; }
  25       }
  26  }

运行效果

备注

按照这种思路,基本上能满足企业应用的多数编码规则要求。在真实的项目中,这些规则是要持久化到数据库的,这样就可以做到运行时动态的修改规则了。

可扩展的单据编号生成器 之 顺序号(防止重复)

背景

我在上篇文章“ .NET:可扩展的单据编号生成器 + 简单的解释器 ”中介绍了一个简单的单据编号框架。有朋友留言问如何实现“ 顺序号,且不能重复 ”,本篇文章就针对这个问题用上篇介绍的框架进行实现。

思路 顺序号 = 上次顺序号 + 步长

根据上面的公式,问题可以化解为:如何获取上次顺序号?获取上次顺序号有两种方式:

扫描单据表,找出最新的一条记录。 引入种子表,种子表记录了最新的顺序号。

因为生成的顺序号不能重复,这里就有了并发的要求,为了最大限度的提高并发性,我选择2( 引入种子表 )。

并发处理可以选择:悲观锁或乐观锁,这里为了简单,我选择 悲观锁 。

实现 代码下载: http://yunpan.cn/Q5KMUTA3qGPct 。

种子表设计

 1   CREATE   TABLE   [  dbo  ] . [  CodeSeeds  ]   (
  2       [  Id  ]      UNIQUEIDENTIFIER   NOT   NULL  ,
  3       [  Key  ]     NVARCHAR  ( 500 )    NOT   NULL  ,
  4       [  Value  ]   INT                NOT   NULL  ,
  5       PRIMARY   KEY   CLUSTERED  ( [  Id  ]   ASC  )
  6  );

SeedCodeRuleProvider.cs

  1   using   System;
   2   using   System.Collections.Generic;
   3   using   System.Linq;
   4   using   System.Text;
   5   using   System.Threading.Tasks;
   6  
  7   using   System.Transactions;
   8   using   System.Text.RegularExpressions;
   9   using   System.Data.Entity.Infrastructure;
  10  
 11   namespace   EntityCodeRuleDemo
  12   {
  13       public   class   SeedCodeRuleProvider : ICodeRuleProvider
  14       {
  15           private   readonly   int   _width;
  16  
 17           public  SeedCodeRuleProvider( int   width)
  18           {
  19              _width =  width;
  20           }
  21  
 22           public   string  Generate( object   entity)
  23           {
  24               return  GetSeedValue(entity).ToString().PadLeft(_width,  '  0  '  );
  25           }
  26  
 27           protected   virtual   string  GetKey( object   entity)
  28           {
  29               return   entity.GetType().FullName;
  30           }
  31  
 32           private   int  GetSeedValue( object   entity)
  33           {
  34               try 
 35               {
  36                   var  value =  0  ;
  37                   var  key =  this  .GetKey(entity);
  38  
 39                   using  ( var  ts =  new   TransactionScope(TransactionScopeOption.RequiresNew,
  40                       new  TransactionOptions { IsolationLevel =  IsolationLevel.RepeatableRead }))
  41                   {
  42                       using  ( var  context =  new   TestContext())
  43                       {
  44                           var  seed = context.CodeSeeds.Where(x => x.Key ==  key).FirstOrDefault();
  45                           if  (seed ==  null  )
  46                           {
  47                              seed =  new  CodeSeed { Id = Guid.NewGuid(), Key = key, Value = - 1   };
  48                               context.CodeSeeds.Add(seed);
  49                           }
  50  
 51                          seed.Value++ ;
  52                          value =  seed.Value;
  53                           context.SaveChanges();
  54                       }
  55  
 56                       ts.Complete();
  57                   }
  58  
 59                   return   value;
  60               }
  61               catch   (DbUpdateException)
  62               {
  63                   return   this  .GetSeedValue(entity);
  64               }
  65           }
  66  
 67           public   static  SeedCodeRuleProvider SeedCodeRuleProviderFactory( string   literal)
  68           {
  69               var  match =  new  Regex( "  ^<种子(:(?<宽度>.*?))?>$  "  ).Match(literal);
  70  
 71               var  width = match.Groups[ "  宽度  "  ].Value;
  72  
 73               return   new  SeedCodeRuleProvider( string .IsNullOrEmpty(width) ?  5  :  int  .Parse(width));
  74           }
  75       }
  76  }

Program.cs

  1   using   System;
   2   using   System.Collections.Generic;
   3   using   System.Linq;
   4   using   System.Text;
   5   using   System.Threading.Tasks;
   6  
  7   using   System.Text.RegularExpressions;
   8  
  9   namespace   EntityCodeRuleDemo
  10   {
  11       class   Program
  12       {
  13           static   void  Main( string  [] args)
  14           {
  15              CodeRuleInterpreter.RegistProviderFactory( new  Regex( "  ^<种子(:(?<宽度>.*?))?>$  "  ), SeedCodeRuleProvider.SeedCodeRuleProviderFactory);
  16  
 17               var  employeeCode =  CodeRuleInterpreter
  18                  .Interpret( "  前缀_<日期:yyyy_MM_dd>_<属性:NamePinYin>_<种子:6>  "  )
  19                  .Generate( new  Employee { NamePinYin =  "  DUANGW  "   });
  20  
 21               Console.WriteLine(employeeCode);
  22           }
  23       }
  24  }

运行结果

备注

有写业务要求会强制编号的连续性或编号的随机性,对于这两种需求,还需要单独开发Provider,有机会再写文章介绍了。

现在可选的框架

    现在我们开发一个.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转成数据实体类的测试代码:   https://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 , 博客园这里上传不了。没有办法。只能选别处了。

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于可扩展的单据编号生成器 + 简单的解释器的详细内容...

  阅读:41次