VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > C#教程 > c#编程 >
  • c#用Attribute实现AOP事务

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

  阅前注意

  1.  整篇文章的核心和突破点在于上下文Context的使用,务必注意CallContext在整个程序中起到的作用

  2.  本文中看到的SqlHelper使用的是微软SqlHelper.cs。

  3.  本文重点在于如何实现,并且已经测试通过,只贴关键性代码,所以请认真阅读,部分代码直接拷贝下来运行是会出错的!

  正文

  首先我们来看一段未加事务的代码:

  SqlDAL.cs  

publicabstractclassSqlDAL
  {
    #regionConnectionString
    privateSqlConnectionStringBuilder_ConnectionString=null;
    ///<summary>
    ///字符串连接
    ///</summary>
    publicvirtualSqlConnectionStringBuilderConnectionString
    {
      get
      {
        if(_ConnectionString==null||string.IsNullOrEmpty(_ConnectionString.ConnectionString))
        {
          _ConnectionString=newSqlConnectionStringBuilder(Configurations.SQLSERVER_CONNECTION_STRING);
        }
        return_ConnectionString;
      }
      set{_ConnectionString=value;}
    }
    #endregion
    #regionExecuteNonQuery
    publicintExecuteNonQuery(stringcmdText)
    {
      returnSqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString,CommandType.Text,cmdText);
    }
    publicintExecuteNonQuery(stringcmdText,CommandTypetype)
    {
      returnSqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString,type,cmdText);
    }
    publicintExecuteNonQuery(stringcmdText,CommandTypetype,paramsSqlParameter[]cmdParameters)
    {
      returnSqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString,type,cmdText,cmdParameters);
    }
    #endregion

 

  代码说明:

  1.  本类对SqlHelper.cs 进一步封装。

  2.  Configurations.SQLSERVER_CONNECTION_STRING 替换成自己的连接字符串就行了。

  UserInfoAction.cs
  publicclassUserInfoAction:SqlDAL
  {
    ///<summary>
    ///添加用户
    ///</summary>
    publicvoidAdd(UserInfouser)
    {
      StringBuildersb=newStringBuilder();
      sb.Append("UPDATE[UserInfo]SETPassword='");
      sb.Append(user.Password);
      sb.Append("'WHEREUID=");
      sb.Append(user.UID);
      ExecuteNonQuery(sql);
    }
  }

  如果我们要加入事务,通常的办法就是在方法内try、catch然后Commit、Rollback,缺点就不说了,下面我会边贴代码边讲解,力图大家也能掌握这种方法: )

  先贴前面两个被我修改的类

  SqlDAL.cs

publicabstractclassSqlDAL:ContextBoundObject
  {
    privateSqlTransaction_SqlTrans;
    ///<summary>
    ///仅支持有事务时操作
    ///</summary>
    publicSqlTransactionSqlTrans
    {
      get
      {
        if(_SqlTrans==null)
        {
          //从上下文中试图取得事务
          objectobj=CallContext.GetData(TransactionAop.ContextName);
          if(obj!=null&&objisSqlTransaction)
            _SqlTrans=objasSqlTransaction;
        }
        return_SqlTrans;
      }
      set{_SqlTrans=value;}
    }
    #regionConnectionString
    privateSqlConnectionStringBuilder_ConnectionString=null;
    ///<summary>
    ///字符串连接
    ///</summary>
    publicvirtualSqlConnectionStringBuilderConnectionString
    {
      get
      {
        if(_ConnectionString==null||string.IsNullOrEmpty(_ConnectionString.ConnectionString))
        {
          _ConnectionString=newSqlConnectionStringBuilder(Configurations.SQLSERVER_CONNECTION_STRING);
        }
        return_ConnectionString;
      }
      set{_ConnectionString=value;}
    }
    #endregion
    #regionExecuteNonQuery
    publicintExecuteNonQuery(stringcmdText)
    {
      if(SqlTrans==null)
        returnSqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString,CommandType.Text,cmdText);
      else
        returnSqlHelper.ExecuteNonQuery(SqlTrans,CommandType.Text,cmdText);
    }
    publicintExecuteNonQuery(stringcmdText,CommandTypetype)
    {
      if(SqlTrans==null)
        returnSqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString,type,cmdText);
      else
        returnSqlHelper.ExecuteNonQuery(SqlTrans,type,cmdText);
    }
    publicintExecuteNonQuery(stringcmdText,CommandTypetype,paramsSqlParameter[]cmdParameters)
    {
      if(SqlTrans==null)
        returnSqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString,type,cmdText,cmdParameters);
      else
        returnSqlHelper.ExecuteNonQuery(SqlTrans,type,cmdText,cmdParameters);
    }
    #endregion
  }

 

  代码说明:

  1.  加了一个属性(Property)SqlTrans,并且每个ExecuteNonQuery执行前都加了判断是否以事务方式执行。这样做是为后面从上下文中取事务做准备。

  2.  类继承了ContextBoundObject,注意,是必须的,MSDN是这样描述的:定义所有上下文绑定类的基类。

  3.  TransactionAop将在后面给出。

  UserInfoAction.cs
  [Transaction]
  publicclassUserInfoAction:SqlDAL
  {
    [TransactionMethod]
    publicvoidAdd(UserInfouser)
    {
      StringBuildersb=newStringBuilder();
      sb.Append("UPDATE[UserInfo]SETPassword='");
      sb.Append(user.Password);
      sb.Append("'WHEREUID=");
      sb.Append(user.UID);
      ExecuteNonQuery(sql);
    }
  }

  代码说明:

  1.  很简洁、非侵入式、很少改动、非常方便(想要事务就加2个标记,不想要就去掉)。

  2.  两个Attribute后面将给出。

  ///<summary>
  ///标注类某方法内所有数据库操作加入事务控制
  ///</summary>
  [AttributeUsage(AttributeTargets.Class,AllowMultiple=false)]
  publicsealedclassTransactionAttribute:ContextAttribute,IContributeObjectSink
  {
    ///<summary>
    ///标注类某方法内所有数据库操作加入事务控制,请使用TransactionMethodAttribute同时标注
    ///</summary>
    publicTransactionAttribute()
      :base("Transaction")
    {}
    publicIMessageSinkGetObjectSink(MarshalByRefObjectobj,IMessageSinknext)
    {
      returnnewTransactionAop(next);
    }
  }
  ///<summary>
  ///标示方法内所有数据库操作加入事务控制
  ///</summary>
  [AttributeUsage(AttributeTargets.Method,AllowMultiple=false)]
  publicsealedclassTransactionMethodAttribute:Attribute
  {
    ///<summary>
    ///标示方法内所有数据库操作加入事务控制
    ///</summary>
    publicTransactionMethodAttribute()
    {
    }
  }

 

  代码说明:

  1.  在上面两篇文章中都是把IContextProperty, IContributeObjectSink单独继承并实现的,其实我们发现ContextAttribute已经继承了IContextProperty,所有这里我仅仅只需要再继承一下IContributeObjectSink就行了。关于这两个接口的说明,上面文章中都有详细的说明。

  2.  TransactionAop将在后面给出。

  3.  需要注意的是两个Attribute需要一起用,并且我发现Attribute如果标记在类上他会被显示的实例化,但是放在方法上就不会,打断点可以跟踪到这一过程,要不然我也不会费力气弄两个来标注了。

  TransactionAop.cs

publicsealedclassTransactionAop:IMessageSink
  {
    privateIMessageSinknextSink;//保存下一个接收器
    ///<summary>
    ///构造函数
    ///</summary>
    ///<paramname="next">接收器</param>
    publicTransactionAop(IMessageSinknextSink)
    {
      this.nextSink=nextSink;
    }
    ///<summary>
    /// IMessageSink接口方法,用于异步处理,我们不实现异步处理,所以简单返回null,
    /// 不管是同步还是异步,这个方法都需要定义
    ///</summary>
    ///<paramname="msg"></param>
    ///<paramname="replySink"></param>
    ///<returns></returns>
    publicIMessageCtrlAsyncProcessMessage(IMessagemsg,IMessageSinkreplySink)
    {
      returnnull;
    }
    ///<summary>
    ///下一个接收器
    ///</summary>
    publicIMessageSinkNextSink
    {
      get{returnnextSink;}
    }
    ///<summary>
    ///
    ///</summary>
    ///<paramname="msg"></param>
    ///<returns></returns>
    publicIMessageSyncProcessMessage(IMessagemsg)
    {
      IMessageretMsg=null;
      IMethodCallMessagecall=msgasIMethodCallMessage;
      if(call==null|| (Attribute.GetCustomAttribute(call.MethodBase,typeof(TransactionMethodAttribute)))==null)
        retMsg=nextSink.SyncProcessMessage(msg);
      else
      {
        //此处换成自己的数据库连接
        using(SqlConnectionConnect=newSqlConnection(Configurations.SQLSERVER_CONNECTION_STRING))
        {
          Connect.Open();
          SqlTransactionSqlTrans=Connect.BeginTransaction();
          //讲存储存储在上下文
          CallContext.SetData(TransactionAop.ContextName,SqlTrans);
          //传递消息给下一个接收器->就是指执行你自己的方法
          retMsg=nextSink.SyncProcessMessage(msg);
          if(SqlTrans!=null)
          {
            IMethodReturnMessagemethodReturn=retMsgasIMethodReturnMessage;
            Exceptionexcept=methodReturn.Exception;
            if(except!=null)
            {
              SqlTrans.Rollback();
              //可以做日志及其他处理
            }
            else
            {
              SqlTrans.Commit();
            }
            SqlTrans.Dispose();
            SqlTrans=null;
          }
        }
      }
      returnretMsg;
    }
    ///<summary>
    ///用于提取、存储SqlTransaction
    ///</summary>
    publicstaticstringContextName
    {
      get{return"TransactionAop";}
    }
  }

 

  代码说明:

  1.  IMessageSink  MSDN:定义消息接收器的接口。

  2.  主要关注SyncProcessMessage方法内的代码,在这里创建事务,并存储在上下文中间,还记得上面SqlDAL的SqlTrans属性么,里面就是从上下文中取得的。

  3.  请注意了,这里能捕捉到错误,但是没有办法处理错误,所以错误会继续往外抛,但是事务的完整性我们实现了。你可以在Global.asax可以做全局处理,也可以手动的try一下,但是我们不需要管理事务了,仅仅当普通的错误来处理了。

  结束

  大家可以看到,在被标注的方法里面所有的数据库操作都会被事务管理起来,也算是了了我心愿,貌似我的Attribute做权限又看到了一丝希望了,欢迎大家多提意见:)

  补充(2009-1-8)

  关于在评论中提到的性能的问题,如果要使用AOP的方式来实现事务肯定比直接try catch 然后Commit 和 Rollback效率要低的,但是很明显可维护性、使用方便性要高得多的,所以看个人需求了。这里补充的是关于SqlDAL继承ContextBoundObject的问题,以下是想到的解决办法:

  1.  最简单、修改UserInfoAction最少的办法:把SqlDAL复制一份改下类名,继承一下ContextBoundObject,然后把继承类改一下。很不推荐: (

  2.  从一开始就不使用继承方法来访问数据层的方法,而是将SqlDAL改成一个普通类,通过声明一个SqlDAL方式来访问数据层:

    privateSqlDAL_sqlDao;
    publicSqlDALSqlDao
    {
      get
      {
        if(_sqlDao==null)
        {
          _sqlDao=newSqlDAL();
          objectobj=CallContext.GetData(TransactionAop.ContextName);
          if(obj!=null&&objisSqlTransaction)
            _sqlDao.SqlTrans=objasSqlTransaction;
        }
        return_sqlDao;
      }
    }

 

  这样相对于没有加事务类仅仅多一个取值过程和判断过程,效率应该还是比继承SqlDAL直接继承ContextBoundObject好很多。

  个人感觉还是不是很好,继续探索,已经想到了减少一个Attribute的办法了,感谢欢迎大家提建议 :)

 

 

  代码说明:

  1.  本类对SqlHelper.cs 进一步封装。

  2.  Configurations.SQLSERVER_CONNECTION_STRING 替换成自己的连接字符串就行了。

  UserInfoAction.cs
  publicclassUserInfoAction:SqlDAL
  {
    ///<summary>
    ///添加用户
    ///</summary>
    publicvoidAdd(UserInfouser)
    {
      StringBuildersb=newStringBuilder();
      sb.Append("UPDATE[UserInfo]SETPassword='");
      sb.Append(user.Password);
      sb.Append("'WHEREUID=");
      sb.Append(user.UID);
      ExecuteNonQuery(sql);
    }
  }

  如果我们要加入事务,通常的办法就是在方法内try、catch然后Commit、Rollback,缺点就不说了,下面我会边贴代码边讲解,力图大家也能掌握这种方法: )

  先贴前面两个被我修改的类

  SqlDAL.cs

publicabstractclassSqlDAL:ContextBoundObject
  {
    privateSqlTransaction_SqlTrans;
    ///<summary>
    ///仅支持有事务时操作
    ///</summary>
    publicSqlTransactionSqlTrans
    {
      get
      {
        if(_SqlTrans==null)
        {
          //从上下文中试图取得事务
          objectobj=CallContext.GetData(TransactionAop.ContextName);
          if(obj!=null&&objisSqlTransaction)
            _SqlTrans=objasSqlTransaction;
        }
        return_SqlTrans;
      }
      set{_SqlTrans=value;}
    }
    #regionConnectionString
    privateSqlConnectionStringBuilder_ConnectionString=null;
    ///<summary>
    ///字符串连接
    ///</summary>
    publicvirtualSqlConnectionStringBuilderConnectionString
    {
      get
      {
        if(_ConnectionString==null||string.IsNullOrEmpty(_ConnectionString.ConnectionString))
        {
          _ConnectionString=newSqlConnectionStringBuilder(Configurations.SQLSERVER_CONNECTION_STRING);
        }
        return_ConnectionString;
      }
      set{_ConnectionString=value;}
    }
    #endregion
    #regionExecuteNonQuery
    publicintExecuteNonQuery(stringcmdText)
    {
      if(SqlTrans==null)
        returnSqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString,CommandType.Text,cmdText);
      else
        returnSqlHelper.ExecuteNonQuery(SqlTrans,CommandType.Text,cmdText);
    }
    publicintExecuteNonQuery(stringcmdText,CommandTypetype)
    {
      if(SqlTrans==null)
        returnSqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString,type,cmdText);
      else
        returnSqlHelper.ExecuteNonQuery(SqlTrans,type,cmdText);
    }
    publicintExecuteNonQuery(stringcmdText,CommandTypetype,paramsSqlParameter[]cmdParameters)
    {
      if(SqlTrans==null)
        returnSqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString,type,cmdText,cmdParameters);
      else
        returnSqlHelper.ExecuteNonQuery(SqlTrans,type,cmdText,cmdParameters);
    }
    #endregion
  }



相关教程