知识点 Attribute在.net编程中的应用

Attribute的基本概念

经常有朋友问,Attribute是什么?它有什么用?好像没有这个东东程序也能运行。实际上在.Net中,Attribute是一个非常重要的组成部分,为了帮助大家理解和掌握Attribute,以及它的使用方法,特地收集了几个Attribute使用的例子,提供给大家参考。

在具体的演示之前,我想先大致介绍一下Attribute。我们知道在类的成员中有property成员,二者在中文中都做属性解释,那么它们到底是不是同一个东西呢?从代码上看,明显不同,首先就是它们的在代码中的位置不同,其次就是写法不同(Attribute必须写在一对方括符中)。

什么是Atrribute

首先,我们肯定Attribute是一个类,下面是msdn文档对它的描述:
公共语言运行时允许你添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型、字段、方法和属性等。Attributes和Microsoft .NET Framework文件的元数据保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响应用程序的行为。

在.NET中,Attribute被用来处理多种问题,比如序列化、程序的安全特征、防止即时编译器对程序代码进行优化从而代码容易调试等等。下面,我们先来看几个在.NET中标准的属性的使用,稍后我们再回过头来讨论Attribute这个类本身。(文中的代码使用C#编写,但同样适用所有基于.NET的所有语言)

Attribute作为编译器的指令

在C#中存在着一定数量的编译器指令,如:#define DEBUG, #undefine DEBUG, #if等。这些指令专属于C#,而且在数量上是固定的。而Attribute用作编译器指令则不受数量限制。比如下面的三个Attribute:

  • Conditional:起条件编译的作用,只有满足条件,才允许编译器对它的代码进行编译。一般在程序调试的时候使用。
  • DllImport:用来标记非.NET的函数,表明该方法在一个外部的DLL中定义。
  • Obsolete:这个属性用来标记当前的方法已经被废弃,不再使用了。

下面的代码演示了上述三个属性的使用:

#define DEBUG //这里定义条件      
using System; using System.Runtime.InteropServices; 
using System.Diagnostics;      
namespace AttributeDemo 
{    
class MainProgramClass    
     {
        [DllImport("User32.dll")]       
public static extern int MessageBox(int hParent, string Message, string Caption, int Type);             

static void Main(string[] args)       
{          
DisplayRunningMessage();          
DisplayDebugMessage();                
MessageBox(0,"Hello","Message",0);                
Console.ReadLine();       
}             
[Conditional("DEBUG")]       
private static void DisplayRunningMessage()       
{          
Console.WriteLine("开始运行Main子程序。当前时间是"+DateTime.Now);       
}          
[Conditional("DEBUG")]       [Obsolete]       
private static void DisplayDebugMessage()       
{          
Console.WriteLine("开始Main子程序");       
}    
}   

如果在一个程序元素前面声明一个Attribute,那么就表示这个Attribute被施加到该元素上,前面的代码,[DllImport]施加到MessageBox函数上, [Conditional]施加到DisplayRuntimeMessage方法和DisplayDebugMessage方法,[Obsolete]施加到DisplayDebugMessage方法上。

根据上面涉及到的三个Attribute的说明,我们可以猜到程序运行的时候产生的输出:DllImport Attribute表明了MessageBox是User32.DLL中的函数,这样我们就可以像内部方法一样调用这个函数。

重要的一点就是Attribute就是一个类,所以DllImport也是一个类,Attribute类是在编译的时候被实例化的,而不是像通常的类那样在运行时候才实例化。Attribute实例化的时候根据该Attribute类的设计可以带参数,也可以不带参数,比如DllImport就带有"User32.dll"的参数。Conditional对满足参数的定义条件的代码进行编译,如果没有定义DEBUG,那么该方法将不被编译,读者可以把#define DEBUG一行注释掉看看输出的结果(release版本,在Debug版本中Conditional的debug总是成立的)。Obsolete表明了DispalyDebugMessage方法已经过时了,它有一个更好的方法来代替它,当我们的程序调用一个声明了Obsolete的方法时,那么编译器会给出信息,Obsolete还有其他两个重载的版本。大家可以参考msdn中关于的ObsoleteAttribute 类的描述。

Attribute类

除了.NET提供的那些Attribute派生类之外,我们可以自定义我们自己的Attribute,所有自定义的Attribute必须从Attribute类派生。现在我们来看一下Attribute 类的细节:

protected Attribute(): 保护的构造器,只能被Attribute的派生类调用。

三个静态方法:

static Attribute GetCustomAttribute():这个方法有8种重载的版本,它被用来取出施加在类成员上指定类型的Attribute。

static Attribute[] GetCustomAttributes(): 这个方法有16种重载版本,用来取出施加在类成员上指定类型的Attribute数组。

static bool IsDefined():由八种重载版本,看是否指定类型的定制attribute被施加到类的成员上面。

实例方法:

bool IsDefaultAttribute(): 如果Attribute的值是默认的值,那么返回true。

bool Match():表明这个Attribute实例是否等于一个指定的对象。

公共属性: TypeId: 得到一个唯一的标识,这个标识被用来区分同一个Attribute的不同实例。

我们简单地介绍了Attribute类的方法和属性,还有一些是从object继承来的。这里就不列出来了。

下面介绍如何自定义一个Attribute: 自定义一个Attribute并不需要特别的知识,其实就和编写一个类差不多。自定义的Attribute必须直接或者间接地从Attribute这个类派生,如:

public MyCustomAttribute : Attribute { ... }

这里需要指出的是Attribute的命名规范,也就是你的Attribute的类名+"Attribute",当你的Attribute施加到一个程序的元素上的时候,编译器先查找你的Attribute的定义,如果没有找到,那么它就会查找“Attribute名称"+Attribute的定义。如果都没有找到,那么编译器就报错。

对于一个自定义的Attribute,你可以通过AttributeUsage的Attribute来限定你的Attribute 所施加的元素的类型。代码形式如下: [AttriubteUsage(参数设置)] public 自定义Attribute : Attribute { ... }

非常有意思的是,AttributeUsage本身也是一个Attribute,这是专门施加在Attribute类的Attribute. AttributeUsage自然也是从Attribute派生,它有一个带参数的构造器,这个参数是AttributeTargets的枚举类型。下面是AttributeTargets 的定义:

public enum AttributeTargets 
{    
All=16383,
Assembly=1,    
Module=2,    
Class=4,    
Struct=8,    
Enum=16,    
Constructor=32,    
Method=64,    
Property=128,    
Field=256,    
Event=512,    
Interface=1024,    
Parameter=2048,    
Delegate=4096,    
ReturnValue=8192 
}                     

作为参数的AttributeTarges的值允许通过“或”操作来进行多个值得组合,如果你没有指定参数,那么默认参数就是All 。 AttributeUsage除了继承Attribute 的方法和属性之外,还定义了以下三个属性:

AllowMultiple: 读取或者设置这个属性,表示是否可以对一个程序元素施加多个Attribute 。

Inherited:读取或者设置这个属性,表示是否施加的Attribute 可以被派生类继承或者重载。

ValidOn: 读取或者设置这个属性,指明Attribute 可以被施加的元素的类型。

AttributeUsage 的使用例子:

using System;  
namespace AttTargsCS  
{      
// 该Attribute只对类有效.     
[AttributeUsage(AttributeTargets.Class)]    
public class ClassTargetAttribute : Attribute     
{      }       
// 该Attribute只对方法有效.     
[AttributeUsage(AttributeTargets.Method)]    
public class MethodTargetAttribute : Attribute     
{      }       
// 该Attribute只对构造器有效。    
[AttributeUsage(AttributeTargets.Constructor)]    
public class ConstructorTargetAttribute : Attribute     
{      }       
// 该Attribute只对字段有效.     
[AttributeUsage(AttributeTargets.Field)]    
public class FieldTargetAttribute : Attribute    
{    }         
// 该Attribute对类或者方法有效(组合).    
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]   
public class ClassMethodTargetAttribute : Attribute    
{     }       
// 该Attribute对所有的元素有效.    
[AttributeUsage(AttributeTargets.All)]    
public class AllTargetsAttribute : Attribute    
{     }      
//上面定义的Attribute施加到程序元素上的用法    
[ClassTarget]  //施加到类    
[ClassMethodTarget]//施加到类    
[AllTargets] //施加到类    
public class TestClassAttribute    
{        
[ConstructorTarget] //施加到构造器       
[AllTargets] //施加到构造器       
TestClassAttribute()       
{         }         
[MethodTarget] //施加到方法       
[ClassMethodTarget] //施加到方法       
[AllTargets] //施加到方法       
public void Method1()       
{       }             
[FieldTarget] //施加到字段       
[AllTargets] //施加到字段       
public int myInt;         
static void Main(string[] args)       
{        }     
}
}              

 

至此,我们介绍了有关Attribute类和它们的代码格式。你一定想知道到底如何在你的应用程序中使用Attribute,如果仅仅是前面介绍的内容,还是不足以说明Attribute有什么实用价值的话,那么从后面的章节开始我们将介绍几个Attribute的不同用法,相信你一定会对Attribute有一个新的了解。

应用实例一

.NET Framework中对Attribute的支持是一个全新的功能,这种支持来自它的Attribute类。在你的程序中适当地使用这个类,或者是灵活巧妙地利用这个类,将使你的程序获得某种在以往编程中很难做到的能力。我们来看一个例子:

假如你是一个项目开发小组中的成员,你想要跟踪项目代码检查的信息,通常你可以把代码的检查信息保存在数据库中以便查询;或者把信息写到代码的注释里面,这样可以阅读代码的同时看到代码被检查的信息。我们知道.NET的组件是自描述的,那么是否可以让代码自己来描述它被检查的信息呢?这样我们既可以将信息和代码保存在一起,又可以通过代码的自我描述得到信息。答案就是使用Attribute.
下面的步骤和代码告诉你怎么做:
首先,我们创建一个自定义的Attribute,并且事先设定我们的Attribute将施加在class的元素上面以获取一个类代码的检查信息。

using System; using System.Reflection;   
[AttributeUsage(AttributeTargets.Class)] //还记得上一节的内容吗? 
public class CodeReviewAttribute : System.Attribute 
//定义一个CodeReview的Attribute 
{  
private string reviewer;  //代码检查人  
private string date;      //检查日期  
private string comment;   //检查结果信息   
//参数构造器  
public CodeReviewAttribute(string reviewer, string date)  
{   
this.reviewer=reviewer;   
this.date=date;  
}   
public string Reviewer  
{   get   {    return reviewer;   }  }   
public string Date  {   get   {    return date;   }  }   
public string Comment  {   get   {    return comment;   }   set   {    comment=value;   }  } 
} 

我们的自定义CodeReviewAttribute同普通的类没有区别,它从Attribute派生,同时通过AttributeUsage表示我们的Attribute仅可以施加到类元素上。

第二步就是使用我们的CodeReviewAttribute, 假如我们有一个Jack写的类MyClass,检查人Niwalker,检查日期2003年7月9日,于是我们施加Attribute如下:

[CodeReview("Niwalker","2003-7-9",Comment="Jack的代码")] 
public class MyClass {  //类的成员定义 }  

当这段代码被编译的时候,编译器会调用CodeReviewAttribute的构造器并且把"Niwalker"和"2003-7-9"分别作为构造器的参数。注意到参数表中还有一个Comment属性的赋值,这是Attribute特有的方式,这里你可以设置更多的Attribute的公共属性(如果有的话),需要指出的是.NET Framework1.0允许向private的属性赋值,但在.NET Framework1.1已经不允许这样做,只能向public的属性赋值。

第三步就是取出我们需要的信息,这是通过.NET的反射来实现的,关于反射的知识,限于篇幅我不打算在这里进行说明,也许我会在以后另外写一篇介绍反射的文章。

class test 
{
static void Main(string[] args)    
{       
System.Reflection.MemberInfo info=typeof(MyClass); //通过反射得到MyClass类的信息  //得到施加在MyClass类上的定制Attribute        
CodeReviewAttribute att= (CodeReviewAttribute)Attribute.GetCustomAttribute(info,typeof(CodeReviewAttribute));        
if(att!=null)       
{           
Console.WriteLine("代码检查人:{0}",att.Reviewer);           
Console.WriteLine("检查时间:{0}",att.Date);           
Console.WriteLine("注释:{0}",att.Comment);       
}    
} 
在上面这个例子中,Attribute扮演着向一个类添加额外信息的角色,它并不影响MyClass类的行为。通过这个例子,我们大致可以知道如何写一个自定义的Attribute,以及如何在应用程序使用它。使用Attribute来自动生成ADO.NET的数据访问类的代码。
 用于参数的Attribute

在编写多层应用程序的时候,你是否为每次要写大量类似的数据访问代码而感到枯燥无味?比如我们需要编写调用存储过程的代码,或者编写T_SQL代码,这些代码往往需要传递各种参数,有的参数个数比较多,一不小心还容易写错。有没有一种一劳永逸的方法?当然,你可以使用MS的Data Access Application Block,也可以使用自己编写的Block。这里向你提供一种另类方法,那就是使用Attribute。

下面的代码是一个调用AddCustomer存储过程的常规方法:

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。从代码可以看到参数的添加是一种重复单调的工作。如果一个项目有100多个甚至几百个存储过程,作为开发人员的你会不会要想办法偷懒?(反正我会的:-))。

下面开始我们的代码自动生成工程:

我们的目的是根据方法的参数以及方法的名称,自动生成一个Command对象实例,第一步我们要做的就是创建一个SqlParameterAttribute, 代码如下:

SqlCommandParameterAttribute.cs  

using System; 
using System.Data; 
using Debug=System.Diagnostics.Debug;  
namespace DataAccess 
{    
// SqlParemeterAttribute 施加到存储过程参数    
[ AttributeUsage(AttributeTargets.Parameter) ]    
public class SqlParameterAttribute : Attribute    
{       
private string name;      				//参数名称       
private bool paramTypeDefined;     //是否参数的类型已经定义       
private SqlDbType paramType;       //参数类型       
private int size;                  //参数尺寸大小       
private byte precision;            //参数精度       
private byte scale;                //参数范围       
private bool directionDefined;     //是否定义了参数方向       
private ParameterDirection direction;  //参数方向              
public SqlParameterAttribute()       {       }              
public string Name       {          get { return name == null ? string.Empty : name; }          set { _name = value; }       }              
public int Size       {          get { return size; }          set { size = value; }       }              
public byte Precision       {          get { return precision; }          set { precision = value; }       }              
public byte Scale       {          get { return scale; }          set { scale = value; }       }              
public ParameterDirection Direction       { get{Debug.Assert(directionDefined); return direction; } set { direction = value; directionDefined = true;}  }              
public SqlDbType SqlDbType       { get { Debug.Assert(paramTypeDefined);return paramType; }set{ paramType = value;paramTypeDefined = true; }}              
public bool IsNameDefined       {          get { return name != null && name.Length != 0; }       }              
public bool IsSizeDefined       {          get { return size != 0; }       }              
public bool IsTypeDefined       {          get { return paramTypeDefined; }       }              
public bool IsDirectionDefined       {          get { return directionDefined; }       }              
public bool IsScaleDefined       {          get { return _scale != 0; }       }              
public bool IsPrecisionDefined       {          get { return _precision != 0; }       }              
...     
}
   以上定义了SqlParameterAttribute的字段和相应的属性,为了方便Attribute的使用,我们重载几个构造器,不同的重载构造器用于不用的参数:
        ...        
// 重载构造器,如果方法中对应于存储过程参数名称不同的话,我们用它来设置存储过程的名称      
// 其他构造器的目的类似       
public SqlParameterAttribute(string name)       {          Name=name;       }        
public SqlParameterAttribute(int size)       {          Size=size;       }              
public SqlParameterAttribute(SqlDbType paramType)       {          SqlDbType=paramType;       }              
public SqlParameterAttribute(string name, SqlDbType paramType)       {          Name = name;          SqlDbType = paramType;       }              
public SqlParameterAttribute(SqlDbType paramType, int size)       {          SqlDbType = paramType;          Size = size;       }                     
public SqlParameterAttribute(string name, int size)       {          Name = name;          Size = size;       }              
public SqlParameterAttribute(string name, SqlDbType paramType, int size)       {          Name = name;          SqlDbType = paramType;          Size = size;}
} 

为了区分方法中不是存储过程参数的那些参数,比如SqlConnection,我们也需要定义一个非存储过程参数的Attribute:

//NonCommandParameterAttribute.cs  

using System; namespace DataAccess 
{    
[ AttributeUsage(AttributeTargets.Parameter) ]    
public sealed class NonCommandParameterAttribute : Attribute    
{    } 
} 

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

//SqlCommandMethodAttribute.cs  
using System; using System.Data;  
namespace Emisonline.DataAccess 
{    
[AttributeUsage(AttributeTargets.Method)]    
public sealed class SqlCommandMethodAttribute : Attribute    
{       
private string commandText;       
private CommandType commandType;        
public SqlCommandMethodAttribute( CommandType commandType, string commandText)       
{          
commandType=commandType;          
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;          }       }    
} 		 

我们的Attribute的定义工作已经全部完成,下一步就是要创建一个用来生成Command对象的类。

 SqlCommandGenerator类的设计

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

引用的命名空间:

//SqlCommandGenerator.cs  using System; using System.Reflection; using System.Data; using System.Data.SqlClient; using Debug = System.Diagnostics.Debug; using StackTrace = System.Diagnostics.StackTrace;    
类代码:
namespace DataAccess {    public sealed 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);       }    } } 

必要的工作终于完成了。SqlCommandGenerator中的代码都加上了注释,所以并不难读懂。下面我们进入最后的一步,那就是使用新的方法来实现上一节我们一开始显示个那个AddCustomer的方法。

重构新的AddCustomer代码:

[ SqlCommandMethod(CommandType.StoredProcedure) ] public void AddCustomer( [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, null, new object[] {customerName,country,province,city,address,telephone,customerId } );                              connection.Open();    command.ExecuteNonQuery();    connection.Close();     //必须明确返回输出参数的值    customerId=(int)command.Parameters["@CustomerId"].Value; } 

代码中必须注意的就是out参数,需要事先进行初始化,并在Command执行操作以后,把参数值传回给它。受益于Attribute,使我们摆脱了那种编写大量枯燥代码编程生涯。 我们甚至还可以使用Sql存储过程来编写生成整个方法的代码,如果那样做的话,可就大大节省了你的时间了,上一节和这一节中所示的代码,你可以把它们单独编译成一个组件,这样就可以在你的项目中不断的重用它们了。

 原文:http://www.cnblogs.com/dudu/articles/4449.html 

原文地址:https://www.cnblogs.com/raysbo/p/2028701.html