.NET 特性Attribute[三]

     刚刚接触Attribute的朋友可能很难想明白Attribute究竟有何用处,以及在应用程序中我们如何使用Attribute。在这节,通过一个例子来演示Attribute的用处!

     针对数据库的操作通常是新手入门时经常接触的例子。在应用程序中,我们经常会遇见以下代码

        public int AddCustomer(SqlConnection connection,string customerName,string country,string province, string city,             string address,string telephone)        
        {
            SqlCommand command
=new SqlCommand("AddCustomer", connection);
            command.CommandType
=CommandType.StoredProcedure;

            command.Parameters.Add(
"@CustomerName",SqlDbType.NVarChar,50).Value=customerName;
            command.Parameters.Add(
"@country",SqlDbType.NVarChar,20).Value=country;
            command.Parameters.Add(
"@Province",SqlDbType.NVarChar,20).Value=province;
            command.Parameters.Add(
"@City",SqlDbType.NVarChar,20).Value=city;
            command.Parameters.Add(
"@Address",SqlDbType.NVarChar,60).Value=address;
            command.Parameters.Add(
"@Telephone",SqlDbType.NvarChar,16).Value=telephone;
            command.Parameters.Add(
"@CustomerId",SqlDbType.Int,4).Direction=ParameterDirection.Output;

            connection.Open();
            command.ExecuteNonQuery();
            connection.Close();

            
int custId=(int)command.Parameters["@CustomerId"].Value;
            
return custId;
        }    

上面的代码,创建一个Command实例,然后添加存储过程的参数,然后调用ExecuteMonQuery方法执行数据的插入操作,最后返回CustomerId。从代码可以看到参数的添加command.Parameters.Add()是一种重复单调的工作。而往往一个项目中,经常有一百多个甚至成千的存储过程,每次编写这样的代码是否会感到枯燥无味?作为开发人员的你,是否会想偷偷懒?

当然,现在喜庆的是出现了非常多的Coder,代码生成器的出现无疑可以解决上面的问题,但在这里,从技术交流的角度,给出一个另类的作法。就是使用Attribute。

在开始之前先理清我们的思路。我们是为了根据方法的参数及方法的名称,自动的给我们“加工”成一个Command对象。而不需要我们自己手工一个个的Add进去。

第1步:创建一个SqlParameterAttribute类

SqlParameterAttribute.cs

这个类很简单,就普通的一个对象类差不多,就拥有一些属性。但不同的是他继承了Attribute。这样也就是说,这个类可以被当作特性来使用!注意类头的一行[ AttributeUsage(AttributeTargets.Parameter) ]表明该特性,只可作用在方法的参数上!

第2步:考虑到方法中并不是每个参数都是存储过程需要的。所以我们可以再定义一个Attribute来标识这样的参数。

    [ AttributeUsage(AttributeTargets.Parameter) ]
    
public sealed class NonCommandParameterAttribute : Attribute
    {
        
//仅作为标识使用 用于方法参数中,并不是执行SQL所需要的参数
    }

 

第3步:到此我们已经完成了SQL的参数Attribute的定义,在创建Command对象生成器之前,让我们考虑这样的一个事实,那就是如果我们数据访问层调用的不是存储过程,也就是说Command的CommandType不是存储过程,而是带有参数的SQL语句,我们想让我们的方法一样可以适合这种情况,同样我们仍然可以使用Attribute,定义一个用于方法的Attribute来表明该方法中的生成的Command的CommandType是存储过程还是SQL文本,并且我们同样希望以相对简单的方式来定义存储过程的名称。下面是新定义的Attribute的代码:

[AttributeUsage(AttributeTargets.Method)]
    
public sealed class SqlCommandMethodAttribute : Attribute
    {
        
private string commandText;            //SQL执行文本
        private CommandType commandType;    //SQL执行类型

        
public SqlCommandMethodAttribute( CommandType commandType, string commandText)
        {
            
this.commandType=commandType;
            
this.commandText=commandText;
        }

        
public SqlCommandMethodAttribute(CommandType commandType) : this(commandType, null){}

        
public string CommandText
        {
            
get
            {
                
return commandText==null ? string.Empty : commandText;
            }
            
set
            {
                commandText
=value;
            }
        }

        
        
public CommandType CommandType
        {
            
get
            {
                
return commandType;
            }
            
set
            {
                commandType
=value;
            }
        }
    }

 

第4步:SqlCommandGenerator类的设计

SqlCommandGEnerator类的设计思路就是通过反射得到方法的参数,使用被SqlCommandParameterAttribute标记的参数来装配一个Command实例。

 

using System;
using System.Reflection;
using System.Data;
using System.Data.SqlClient;
using Debug = System.Diagnostics.Debug;
using StackTrace = System.Diagnostics.StackTrace;  


namespace AttributeDemo
{
    
/// <summary>
    
/// SqlCommandGenerator 的摘要说明。
    
/// </summary>
    public class SqlCommandGenerator
    {
        
//私有构造器,不允许使用无参数的构造器构造一个实例
        private SqlCommandGenerator()
        {
            
throw new NotSupportedException();
        }
        
//静态只读字段,定义用于返回值的参数名称
        public static readonly string ReturnValueParameterName = "RETURN_VALUE";
        
//静态只读字段,用于不带参数的存储过程
        public static readonly object[] NoValues = new object[] {};
   
      
        
public static SqlCommand GenerateCommand(SqlConnection connection,
            MethodInfo method, 
object[] values)
        {
            
//如果没有指定方法名称,从堆栈帧得到方法名称
            if (method == null)
                method 
= (MethodInfo) (new StackTrace().GetFrame(1).GetMethod());
 
            
// 获取方法传进来的SqlCommandMethodAttribute
            
// 为了使用该方法来生成一个Command对象,要求有这个Attribute。
            SqlCommandMethodAttribute commandAttribute = 
                (SqlCommandMethodAttribute) Attribute.GetCustomAttribute(method, 
typeof(SqlCommandMethodAttribute));

            
            
//Debug.Assert(commandAttribute != null);

            
//Debug.Assert(commandAttribute.CommandType == CommandType.StoredProcedure ||            commandAttribute.CommandType == CommandType.Text);
            
// 创建一个SqlCommand对象,同时通过指定的attribute对它进行配置。
            SqlCommand command = new SqlCommand();
            command.Connection 
= connection;
            command.CommandType 
= commandAttribute.CommandType;
      
            
// 获取command的文本,如果没有指定,那么使用方法的名称作为存储过程名称 
            if (commandAttribute.CommandText.Length == 0)
            {
                
//Debug.Assert(commandAttribute.CommandType == CommandType.StoredProcedure);
                command.CommandText = method.Name;
            }
            
else
            {
                command.CommandText 
= commandAttribute.CommandText;
            }

            
// 调用GeneratorCommandParameters方法,生成command参数,
            GenerateCommandParameters(command, method, values);
            
            
//同时添加一个返回值参数
            command.Parameters.Add(ReturnValueParameterName, SqlDbType.Int).Direction =ParameterDirection.ReturnValue;

            
return command;
        }


        
private static void GenerateCommandParameters(
            SqlCommand command, MethodInfo method, 
object[] values)
        {

            
// 得到所有的参数,通过循环一一进行处理。
         
            ParameterInfo[] methodParameters 
= method.GetParameters();
            
int paramIndex = 0;

            
foreach (ParameterInfo paramInfo in methodParameters)
            {
                
// 忽略掉参数被标记为[NonCommandParameter ]的参数
         
                
if (Attribute.IsDefined(paramInfo, typeof(NonCommandParameterAttribute)))
                    
continue;
            
                
// 获取参数的SqlParameter attribute,如果没有指定,那么就创建一个并使用它的缺省设置。
                SqlParameterAttribute paramAttribute = (SqlParameterAttribute) Attribute.GetCustomAttribute(
                    paramInfo, 
typeof(SqlParameterAttribute));
   
                
if (paramAttribute == null)
                    paramAttribute 
= new SqlParameterAttribute();
      
                
//使用attribute的设置来配置一个参数对象。使用那些已经定义的参数值。如果没有定义,那么就从方法 
                
// 的参数来推断它的参数值。
                SqlParameter sqlParameter = new SqlParameter();
                
//参数名称
                if (paramAttribute.IsNameDefined)
                    sqlParameter.ParameterName 
= paramAttribute.Name;
                
else
                    sqlParameter.ParameterName 
= paramInfo.Name;

                
if (!sqlParameter.ParameterName.StartsWith("@"))
                    sqlParameter.ParameterName 
= "@" + sqlParameter.ParameterName;
         
                
//参数类型
                if (paramAttribute.IsTypeDefined)
                    sqlParameter.SqlDbType 
= paramAttribute.SqlDbType;
            
                
//参数长度
                if (paramAttribute.IsSizeDefined)
                    sqlParameter.Size 
= paramAttribute.Size;

                
//参数小数位数
                if (paramAttribute.IsScaleDefined)
                    sqlParameter.Scale 
= paramAttribute.Scale;
            
                
//参数最大位数
                if (paramAttribute.IsPrecisionDefined)
                    sqlParameter.Precision 
= paramAttribute.Precision;
            
                
//参数方向
                if (paramAttribute.IsDirectionDefined)
                {
                    sqlParameter.Direction 
= paramAttribute.Direction;
                }
                
else
                {
                    
if (paramInfo.ParameterType.IsByRef)
                    {
                        
//如果参数是引用类型 则认为是可以输出类型
                        sqlParameter.Direction = paramInfo.IsOut ? 
                            ParameterDirection.Output : 
                            ParameterDirection.InputOutput;
                    }
                    
else
                    {
                        sqlParameter.Direction 
= ParameterDirection.Input;
                    }
                }
         
                
// 检测是否提供的足够的参数对象值
                
//Debug.Assert(paramIndex < values.Length);
           
                
//把相应的对象值赋于参数。
                sqlParameter.Value = values[paramIndex];
                command.Parameters.Add(sqlParameter);
                  
                  
                paramIndex
++;
            }
      
            
//检测是否有多余的参数对象值
            
//Debug.Assert(paramIndex == values.Length);
        }

    }
}

 

好了,一切就是如此。现在我们就可以使用这些特性来修改在文章开始给出的示例!

 

[SqlCommandMethodAttribute(CommandType.StoredProcedure)]
        
public void UP_Customer_ADD( [NonCommandParameter] SqlConnection connection, 
            [SqlParameter(
50)] string customerName, 
            [SqlParameter(
20)] string country, 
            [SqlParameter(
20)] string province, 
            [SqlParameter(
20)] string city, 
            [SqlParameter(
60)] string address, 
            [SqlParameter(
16)] string telephone,
            
out int customerId )
        {
            customerId
=0//需要初始化输出参数
            
//调用Command生成器生成SqlCommand实例
            SqlCommand command = SqlCommandGenerator.GenerateCommand( connection, nullnew object[]{customerName,country,province,city,address,telephone,customerId } );
                         
            connection.Open();
            command.ExecuteNonQuery();
            connection.Close();

            
//必须明确返回输出参数的值
            customerId=(int)command.Parameters["@CustomerId"].Value;
        }

消除了前面所看到的command.Parameters.Add方式。注意方法的头定义的 [SqlCommandMethodAttribute(CommandType.StoredProcedure)]特性,表明该方法执行的是存储过程。那么到这里可能有朋友问,那过程的名称是什么呢,细心看GenerateCommand方法的朋友,可以知道,当没有传入存储过程名称时,会自动以方法的名称作为存储过程的名称,这里是“UP_Customer_ADD” 如果要明确写出存储过程名称,则将方法头改为 [SqlCommandMethodAttribute(CommandType.StoredProcedure,"sp_yourProc")],则sp_yourProc即为将要执行的存储过程名称!

为讲解的完整性下面给出程序的调用及SQL表及存储过程

[TestAttribute("ddm")]
        
private void btnSave_Click(object sender, System.EventArgs e)
        {


            
int addID = 0;

            SqlConnection con 
= new SqlConnection("server=.;database=fyDemo;uid=sa;pwd=sa;");

            UP_Customer_ADD(con,
"姓名","国家","湖北省","武汉","地址","133333333333",out addID);

    
            MessageBox.Show(
"添加成功:ID为"+addID.ToString());
        }

 

sqlscript.sql

 

posted on 2008-09-17 22:25  冯岩  阅读(944)  评论(2编辑  收藏  举报

导航