CYQ.Data 轻量数据层之路 SQLHelper 回头太难(八)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

提前说明:正如网友反映的一样,为了不至于产生明显的误导,特别加了此首段说明

SQLHelper,几乎是每个过来者必经的阶段,写好一个SQLHelper是非常重要的一环,所以希望年轻的来者,要多加实践,别只看不动手,哪怕照着写一写,也是相当的有益。

对于本框架系列,希望年轻来者在掌握使用的同时,动手照着系列文章写一写,如果照着写出来的,相信成长不是一点半点的;别光看不练,最后只能忽悠却动不了手。

 

 

 

这篇文章很不好写,我在电脑前思索了一天,也不知怎么下手。

关于SQLHelper的文章遍地都是,写的不咋的随时被拍砖,不写吧,本系列又不完整,所以,买了个保险之后,低调点写了。

 

从哪写起呢?直接把整个SQLHelper类复制一下,文章就算写完了?好像其它遍地都是的文章都差不多是这个样子的。

 

在还没写完这篇时,曾经有那么个热心人士反编绎过我的框架,还洒了点代码出来了,提前爆光了一下:

详见:

1:CYQ.Data 轻量数据层之路 华丽升级 V1.3出世(五)

2:CYQ.Data 轻量数据层之路 应用示例二 在线聊天(六)

 

本文停了一天没动了,现在重新执笔动手了,想了想,于是在博客园搜了一下,看了第一页搜出来的10篇SQLHelper相关代码,

简略看了一眼,发现还是鄙人的简洁友好的多,于是,继续写下来了:

 

其实我们要的SQLHelper很简单,只要能执行下sql语句和存储过程,也就这个样了,至于事务,这里先放一边了。

接着一步一脚印:

1:我们新增加一个SQHelper类,由于本类并不对外开放,所以我们不改修饰符为public,默认就好了

复制代码
     ///   <summary>
    
///  SQLHelper by 路过秋天
    
///   </summary>
     class  SQLHelper
    {

    }
复制代码

 

2:由于我们不做成静态方法调用方式,所以我们需要实例化,添加两个构造函数

复制代码
  ///   <summary>
    
///  SQLHelper by 路过秋天
    
///   </summary>
     class  SQLHelper
    {
        
///   <summary>
        
///  默认配置连接字符串名:Conn
        
///   </summary>
         public  SQLHelper()
        {
           
        }
        
///   <summary>
        
///  可以传链接字符串
        
///   </summary>
        
  public  SQLHelper( string  conn)
        {
           
        }
    }
复制代码

 

3:既然要实例化才调用,那我们只需要一个Command和一个链接就可以了,所以,我们把它们拿到外面定义成全局变量

复制代码
     class  SQLHelper
    {
        
private  SqlCommand com  =   new  SqlCommand();
        
private  SqlConnection _con  =   null ;
        
///   <summary>
        
///  默认配置连接字符串名:Conn
        
///   </summary>
         public  SQLHelper()
        {
           
        }
        
///   <summary>
        
///  可以传链接字符串
        
///   </summary>
         public  SQLHelper( string  conn)
        {
           
        }
    }
复制代码

上面没有直接new 出SqlConnection,是因为它和链接字符串相关,留到构造函数里初始化了。

 

4:实现构造函数,初始化SqlConnection

复制代码
         public  SQLHelper()
        {
            
if  (ConfigurationManager.ConnectionStrings[ " Conn " !=   null )
            {
                _con 
=   new  SqlConnection(ConfigurationManager.ConnectionStrings[ " Conn " ].ConnectionString);
                com.Connection 
=  _con;
            }
        }
        
public  SQLHelper( string  conn)
        {
            
if  (conn.Length  <   25 )
            {
                conn 
=  ConfigurationManager.ConnectionStrings[conn].ConnectionString;
            }
            _con 
=   new  SqlConnection(conn);
            com.Connection 
=  _con;
        }
复制代码

 默认用webconfig配置Conn,同时上面根据传入的长度,来判断是从配置文件传入,还是直接的链接字符串!

 

5:我们增加一个全局的成员属性,一个是否记录异常,如果不记录则会抛出异常

public   bool  WriteLog  =   true ;

 

6:我这里另开一个Log类,来处理异常,先留下一个静态空方法,回头处理

复制代码
class  Log
{
        
public   static   void  WriteLog( string  message)
        {
             
// ...待实现...
        }
}
复制代码

 

7:要执行SQL语句或存储过程,免不了要打开和关闭链接,这里封装成方法,加try

复制代码
        private   void  OpenCon()
        {
            
try
            {
                
if  (_con.State  ==  ConnectionState.Closed)
                {
                    _con.Open();
                }
            }
            
catch  (SqlException err)
            {
                
if  (WriteLog)
                {
                    Log.WriteLog(err.Message);
                }
            }

        }
        
private   void  CloseCon()
        {
            
try
            {
                
if  (_con.State  ==  ConnectionState.Open)
                {
                    _con.Close();
                }
            }
            
catch  (SqlException err)
            {
                
if  (WriteLog)
                {
                    Log.WriteLog(err.Message);
                }
            }
        }
复制代码

我们在打开和关闭异时,调用了日志记录功能。

 

8:我们继承IDisposable接口,完成资源释放

复制代码
class  SQLHelper:IDisposable
{
        
// ...省略N行...

        
#region  IDisposable 成员

         
public   void  Dispose()
        {
            
if  (_con  !=   null )
            {
                CloseCon();
                _con 
=   null ;
            }
            
if  (com  !=   null )
            {
                com 
=   null ;
            }
        }

        
#endregion
}
复制代码

 

 

9:我们封装一下SqlCommand的几个执行返回

复制代码
         internal   int  ExeNonQuery( string  procName,  bool  isProc)
        {
            
// 更新删除操作,返回受影响行数
         }
        
internal   object  ExeScalar( string  procName, bool isProc)
        {
            
// 返回首行首列的单个记录
         }
        
internal  SqlDataReader ExeDataReader( string  procName,  bool  isProc)
        {
            
// 返回读取流
         }
        
internal  DataTable ExeDataTable( string  procName,  bool  isProc)
        {
            
// 返回DataTable,本框架没用到,因为有了MDataTable
        }
复制代码

 

由于存储过程和单独的sql语句混在一起,我加了一个函数来处理这些共同的事情:

复制代码
         private   void  SetCommandText( string  commandText,  bool  isProc)
        {
            com.CommandText 
=  commandText;
            com.CommandType 
=  isProc  ?  CommandType.StoredProcedure : CommandType.Text;
            
if  ( ! com.Parameters.Contains( " ReturnValue " ))
            {
                com.Parameters.Add(
" ReturnValue " , SqlDbType.Int).Direction  =  ParameterDirection.ReturnValue;
            }
        }
复制代码

后面附加了一个常用的返回值参数ReturnValue。

 

10:实现封装的几个方法,异常则记录日志/抛出

复制代码
         internal   int  ExeNonQuery( string  procName,  bool  isProc)
        {
            
// 更新删除操作,返回受影响行数
            SetCommandText(procName, isProc);
            
int  rowCount  =   1 ;
            
try
            {
                OpenCon();
                com.ExecuteNonQuery();
            }
            
catch  (SqlException err)
            {
                rowCount 
=   0 ;
                
if  (WriteLog)
                {
                    Log.WriteLog(err.Message);
                }
            }
            
return  rowCount;
        }
        
internal   object  ExeScalar( string  procName, bool isProc)
        {
            
// 返回首行首列的单个记录
            SetCommandText(procName, isProc);
            
object  returnValue  =   null ;
            
try
            {
                OpenCon();
                returnValue 
=  com.ExecuteScalar();
            }
            
catch  (SqlException err)
            {
                
if  (WriteLog)
                {
                    Log.WriteLog(err.Message);
                }
            }
            
return  returnValue;
        }
        
internal  SqlDataReader ExeDataReader( string  procName,  bool  isProc)
        {
            
// 返回读取流
            SetCommandText(procName, isProc);
            SqlDataReader sdr 
=   null ;
            
try
            {
                OpenCon();
                sdr 
=  com.ExecuteReader(CommandBehavior.CloseConnection);
                
if  (sdr  !=   null   &&   ! sdr.HasRows)
                {
                    sdr.Close();
                    sdr 
=   null ;
                }

            }
            
catch  (SqlException err)
            {
                
if  (WriteLog)
                {
                    Log.WriteLog(err.Message);
                }
            }
            
return  sdr;
        }
        
internal  DataTable ExeDataTable( string  procName,  bool  isProc)
        {
            
// 返回DataTable,本框架没用到,因为有了MDataTable
            SetCommandText(procName, isProc);
            SqlDataAdapter sdr 
=   new  SqlDataAdapter(com);
            DataTable dataTable 
=   new  DataTable();
            
try
            {
                OpenCon();
                sdr.Fill(dataTable);
            }
            
catch  (SqlException err)
            {
                
if  (WriteLog)
                {
                    Log.WriteLog(err.Message);
                }
            }
            
finally
            {
                sdr.Dispose();
            }
            
return  dataTable;
        }
复制代码

 

 

11:参数方法,无论是存储过程,还是传参型的sql语句,都要用到

A:参数增加:

复制代码
         internal   void  AddParameters( string  parameterName,  object  value)
        {
            
if  ( ! com.Parameters.Contains(parameterName))
            {
                com.Parameters.AddWithValue(parameterName, value);
            }
        }
        
internal   void  AddParameters( string  parameterName,  object  value, SqlDbType sqlDbType)
        {
            
if  ( ! com.Parameters.Contains(parameterName))
            {
                com.Parameters.Add(parameterName, sqlDbType).Value 
=  value;
            }
        }
复制代码

 

B:参数清除:

复制代码
         internal   void  ClearParameters()
        {
            
if  (com  !=   null   &&  com.Parameters  !=   null )
            {
                com.Parameters.Clear();
            }
        }
复制代码

 

12:返回值属性,一般用于返回记录总数

复制代码
         private   int  returnValue;
        
public   int  ReturnValue
        {
            
get
            {
                
if  (com  !=   null   &&  com.Parameters  !=   null )
                {
                 
int .TryParse(Convert.ToString(com.Parameters[ " ReturnValue " ].Value), out  returnValue);
                }
                
return  returnValue;
            }
            
set  { returnValue  =  value; }
        }
复制代码

就此,SQLHelper 就算写完了,余下要处理一下日志记录与异常抛出。

 

13:Log类异常日志记录与抛出

复制代码
class  Log
{
        
public   static   void  WriteLog( string  message)
        {
            
if  (IsCanWrite())
            {
                
 InsertLogToData(message);
            }
            
else
            {
                
throw   new  Exception(message);
            }
        }
        
private   static   bool  IsCanWrite()
        {
           
// 从配置文件取
         }
        
private   static   void  InsertLogToData( string  message)
        {
            
// 错误日志入库,可以自定义写文本或入数据库
         }
}
复制代码

判断是一下是否设置为可写日志,如果是则写,否则抛异常。

 

14:实现IsCanWrite方法

复制代码
private   static   bool  IsCanWrite()
{

     
bool  IsCanWriteLog;
     
bool .TryParse(Convert.ToString(ConfigurationManager.AppSettings[ " IsWriteLog " ]),  out  IsCanWriteLog);
     
return  IsCanWriteLog;
}
复制代码

从配置文件里取配置,如果配置为true,就记录日志,否则抛出异常。

 

插曲:写到这里,博客园又挂了,好在有预感之前保存了一下文章:上图:

十几分钟后,正常了!!!

 

15:实现InsertLogToData方法,将错误异常入库

复制代码
         private   static   void  InsertLogToData( string  message)
        {
            
// 错误日志入库,可以自定义写文本或入数据库
             if  (ConfigurationManager.ConnectionStrings[ " LogConn " ==   null )
            {
                
return   ;
            }
            
string  pageUrl  =  System.Web.HttpContext.Current.Request.Url.ToString();
            SQLHelper helper 
=   new  SQLHelper( " LogConn " );
            helper.WriteLog 
=   false ; // 再产生错误就不写日志了,不能产生死循环
             try
            {
                helper.AddParameters(
" @PageUrl " , pageUrl, System.Data.SqlDbType.NVarChar);
                helper.AddParameters(
" @ErrorMessage " , message, System.Data.SqlDbType.NVarChar);
                helper.ExeNonQuery(
" insert into ErrorLogs(PageUrl,ErrorMessage) values(@PageUrl,@ErrorMessage) " false );
                helper.Dispose();
            }
            
catch
            {
               
// 啥也不做了
            }
        }
复制代码

这里只是对ErrorLogs表进行插入数据操作,将当前发生错误的Url地址及错误信息插入表中。

同时也演示了一把SQLHelper的用法。当然你可以修改成自己需要的日志记录方法。

 

至此,想了一天,终于把这个SQLHelper类给写完了,虽然此类在本框架中不对外开放使用,不过有心的读者仍可以独立出去使用。

 

OK,本节也就到此结束了。欢迎读者留言讨论或提出建议!

 

后语:从你学会写或用SQLHelper那时起,你还会去界面写一堆的类似这样的代码吗?

复制代码
        SqlConnection con  =   new  SqlConnection( " server=.;database=CYQ;uid=sa;pwd=123456 " );
        SqlCommand com 
=   new  SqlCommand();
        com.Connection 
=  con;
        com.CommandType 
=  CommandType.Text;
        com.CommandText 
=   " SELECT * FROM CYQTABLE WHERE ID=888 " ;
        con.Open();
        SqlDataReader sdr
= com.ExecuteReader();
        
if  (sdr  !=   null )
        {
            
while  (sdr.HasRows)
            {
                sdr.Read();
                
// ...循环读...

            }
            sdr.Close();
        }
        con.Close();
复制代码

 

难了吧,回头已太难了,不管是基于认知度的提升还是开发效率上,你都基本不回头了吧。

 

如果有一天

你又认知了微软的ADO.NET Entity Framework或是NHibernate或其它框架。

又或者有一天

你原创框架了

又或是使用本框架了

你还会回头用那个曾经过的SQLHelper么

只能说,一切回头太难。

 

备注:完整框架源码会在本系列结束之后另开章节发布,暂时望勿激动,学习思想才是重要的。

版权声明:本文原创发表于博客园,作者为路过秋天,原文链接:

http://www.cnblogs.com/cyq1162/archive/2010/08/25/1807104.html

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章