[C#]C#中的自定义属性

mikel阅读(1145)

原著:Sadaf Alvi
翻译:Abbey

 

原文出处 Attributes in C# CodeProject
摘要
  在这篇指南里,我们将会看到如何自己创建属性(Attribute),并将其用到不同的程序实体(Entity)上,在程序运行时获取属性信息。
译注
  MSDN将 Attribute 与 Property 均译作“属性”。我真不知道该怎么译了,就将 Property 译作“数据属性”吧。
介绍
  属性(Attribute)是一种新型的声明信息。我们可以使用属性来定义设计时的信息(比如帮助文件、文档的链接),以及运行时的信息(比如将一个 类的域与一个XML的域相关联)。我们也可以用属性来创建“自描述”的组件(可以提供该组件的描述信息)。在这篇指南里,我们将会看到如何自己创建属性 (Attribute),并将其用到不同的程序实体(Entity)上,并在程序运行时获取属性信息。
属性的概念
  MSDN(ms-help://MS.MSDNQTR.2002APR.1033/csspec/html/vclrfcsharpspec_17_2.htm)里是这样定义的:属性是一个声明的附加声明。
使用预定义的属性
  C#已经预定义了一小组的属性供我们使用。在学习如何创建一个自定义的属性前,我们先通过一段代码来看看怎么使用这些预定义的属性吧。

using System;
public class AnyClass
{
[Obsolete("别用Old这个老方法了,请用New方法", true)]
static void Old( ) { }
static void New( ) { }
public static void Main( )
{
Old( );
}
}      

在这个例子里我们使用了Obsolete(“陈旧的”)属性,它会将其所修饰的程序实体(类、方法、数据成员等)说明为已废弃不用的。第一个参数—一 个字符串说明这个实体为何被废弃、由谁代替。实际上这个字符串的内容你想写什么都可以。第二个参数则告诉编译器将用户对此实体的调用视作一个编译错误。这 个参数的缺省值为false,表示编译器仅将用户对其的调用视作警告。编译上面这段代码时,我们将会得到一个编译错误(译注:注意编译错误后附的提示了 吗?):

AnyClass.Old() is obsolete:“别用Old这个老方法了,请用New方法”

开发自定义的属性
  现在开始开发我们自己的属性吧。这儿有一个小窍门:从C#定义的System.Attribute类派生我们的属性类(从抽象基类 System.Attribute直接或间接地派生一个类,该派生类都是一个属性类。一个属性类的声明就定义了一种新的属性类型),然后得到了这样一个声 明:

using System;
public class HelpAttribute : Attribute
{
}

不管你相不相信,我们已经创建了一个自定义的属性。我们可以象这样用它修饰任何的类:

[Help()]
public class AnyClass
{
}

注意:
  
在 属性类名与后缀Attribute间存在一个自动的编译转换。因此当我们用一个属性去修饰一个程序实体时,不需要给出Attribute这个后缀。编译器 首先会在System.Attribute的所有派生类中进行匹配,如果没有找到匹配属性,它就将属性名加上Attribute后缀名后再进行匹配。   
  目前我们的这个属性还没什么用,让我们加点内容吧。在这个示例里,我们为自定义的属性类添加了一个数据属性Description(Property),我们将在本文的最后演示如何在运行时查询这些信息。

using System;
public class HelpAttribute : Attribute
{
public HelpAttribute(String Descrition_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
}
[Help("这是个什么也不做的类")]
public class AnyClass
{
}

定义/控制自定义属性的使用
  AttributeUsage 类是另一个预定义的属性类,以帮助我们控制自定义属性的使用。亦即我们可以定义自定义属性类的属性。这个类描述了如何使用自定义的属性类。AttributeUsage有三个数据属性可用以修饰我们的自定义的属性:

ValidOn

定义了自定义属性在哪些程序实体上可被使用。这个可使用实体的列表可通过AttributeTargets枚举类型的OR操作进行设置
AllowMultiple 定义了是否可在同一个程序实体上同时使用多个属性进行修饰
Inherited 定义了自定义属性的修饰是否可由被修饰类的派生类继承

  让我们做点具体的吧。我们将会用一个AttributeUsage属性修饰我们的属性类,以控制其作用范围:

using System;
[AttributeUsage(AttributeTargets.Class), AllowMultiple = false, Inherited = false ]
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
}

先看看AttributeTargets.Class,说明了我们的Help属性只能用以修饰类,下面的这段代码将会导致一个编译错误(“属性 Help不能用在这样的声明上,它只能用在类的声明上”),因为我们用Help属性去修饰方法AnyMethod()了:

[Help("this is a do-nothing class")]
public class AnyClass
{
[Help("this is a do-nothing method")]    //error
public void AnyMethod()
{
}
}

编译错误:

AnyClass.cs: Attribute ''Help'' is not valid on this declaration type.
It is valid on ''class'' declarations only.

当然我们可以AttributeTargets.All来允许Help属性修饰任何类型的程序实体。AttributeTargets可能的值包括:

  • Assembly,
  • Module,
  • Class,
  • Struct,
  • Enum,
  • Constructor,
  • Method,
  • Property,
  • Field,
  • Event,
  • Interface,
  • Parameter,
  • Delegate,
  • All = Assembly | Module | Class | Struct | Enum | Constructor | Method | Property | Field | Event | Interface | Parameter | Delegate,
  • ClassMembers = Class | Struct | Enum | Constructor | Method | Property | Field | Event | Delegate | Interface )

接下来,该看看AllowMultiple = false这句了:它确定了不能象下面这样,在同一实体上同时使用多个同种属性进行修饰:

[Help("this is a do-nothing class")]
[Help("it contains a do-nothing method")]
public class AnyClass
{
[Help("this is a do-nothing method")]        //这也是错误的,因为Help属性只能修饰类
public void AnyMethod()
{
}
}

编译错误:

      AnyClass.cs: Duplicate ''Help'' attribute

我们再来谈谈AttributeUsage的最后一个数据属性Inherited:定义了自定义属性的修饰是否可由被修饰类的派生类继承。基于下示代码表示的继承关系,让我们看看会发生什么吧:

[Help("BaseClass")]
public class Base
{
}
public class Derive :  Base
{
}

我们选择了AttributeUsage的四种组合:

  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false ]
  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false ]
  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true ]
  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true ]

对应上述组合的结果:

  1. 如果我们查询(稍后我们会看见如何在运行时查询一个类的属性信息。)这个Derive类的Help属性时,会因其未从基类继承该属性而一无所获。
  2. 因为同样的原因,得到与结果一同样的结果。
  3. 为了解释这后面的两种情况,我们把同样的属性也用在这个Derive派生类上,代码修改如下:
  4. [Help("BaseClass")]
    public class Base
    {
    }
    [Help("DeriveClass")]
    public class Derive :  Base
    {
    }
  5. 我们的查询会同时得到其类Base与派生类Dervie的Help属性信息,因为继承与多重修饰均被允许。

注意:
  
AttributeUsage只能用于System.Attribute的派生类,且该派生类的AllowMultiple与Inherited都为false。
定位参数与命名参数   
  定位参数是属性类构造子(Constructor)的参数。它们是每次使用该属性修饰某个程序实体时都必须提供值的参数。相对的,命名参数则是可选参 数,它也不是属性类构造子的参数。为了详细解释它们的含义,让我们给Help属性类加点内容,然后看看下面的示例:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
this.verion = "No Version is defined for this class";
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
protected String version;
public String Version
{
get
{
return this.version;
}
//即便我们不想我们的属性用户设置Version这个数据属性,我们也不得不提供set方法
set
{
this.verion = value;
}
}
}
[Help("This is Class1")]
public class Class1
{
}
[Help("This is Class2", Version = "1.0")]
public class Class2
{
}
[Help("This is Class3", Version = "2.0",
Description = "This is do-nothing class")]
public class Class3
{
}

我们查询Class1的Help属性信息时,会得到下列结果:

Help.Description : This is Class1
Help.Version :No Version is defined for this class

如果我们不定义Version数据属性的值,那么构造子函数体内所赋的缺省值将会使用。如果也没有,那么该数据属性对应的数据类型的缺省值将被使用(比如int类型的缺省值为0)。查询Class2将会得到这样的结果:

Help.Description : This is Class2
Help.Version :  1.0

请不要为了可选的参数而提供多个构造子的重载版本,而应该将它们定义成命名参数。我们之所以称其为“命名的”,是我们为了能在构造子里给 它们赋值,不得不用一个个的标识符定义和访问它们。比如在第二个类中Help属性的使用[Help("This is Class2", Version = "1.0")] 你瞧,AttributeUsage的ValidOn参数就是一个定位参数,而Inherited与AllowMultiple则是命名参数。   注意:在属性类的构造子中给命名参数赋值,我们必须为它提供一个相应的set方法,否则会导致这样的编译错误(Version不能是一个只读的数据属 性):

''Version'' : Named attribute argument can''t be a read only property

当我们查询Class3的Help属性信息时会发生什么呢?会这样-因上述的原因导致编译错误(Description不能是只读的数据属性):

''Desciption'' : Named attribute argument can''t be a read only property

所以还是给Description添加一个set方法吧。这样会得到正确的输出结果:

This is do-nothing class
Help.Version : 2.0

这是因为构造子利用定位参数构造一个属性时,它会调用所有命名参数的set方法。构造子里的赋值行为实际均由各命名参数对应的数据属性的set方法完成,被其覆写(Override)了。
参数类型
  一个属性类的参数可使用的数据类型限于:

  • bool
  • byte
  • char
  • double
  • float
  • int
  • long
  • short
  • string
  • System.Type
  • object
  • 枚举类型以及上述数据类型的一维数组

属性标识
  让我们想象一下,怎么才能把我们的Help属性用到一个完整的程序集(assembly)上?首先要面对的问题是该把Help属性放在哪儿,以便让编 译器识别出它是属于一个程序集的?再考虑另一种情况:我们想把一个属性用在某个方法的返回类型上时,编译器如何才能确定我们把它用在了返回类型而不是这个 方法本身之上?要解决这么多含糊的问题,我们需要属性标识。借助属性标识的帮助,我们可以明确地告诉程序集我们希望把属性放在哪儿。比如:

[assembly: Help("this a do-nothing assembly")]

这个Help属性前的assembly标识符显式地告诉了编译器,当前这个Help属性用于整个程序集。可用的标识符包括:

  • assembly
  • module
  • type
  • method
  • property
  • event
  • field
  • param
  • return

在运行时查询属性   
  我们已经知道了如何创建属性并如何在程序中使用它们。现在该学习我们所建属性类的用户如何才能在运行时查询该属性类的信息了。要查询一个程序实体的所有属性信息,我们得使用反射(reflection)-在运行时发现类型信息的一种功能。
  我们可以直接使用.NET Framework提供的反射Reflection API来枚举一个完整程序集的所有元数据(metadata),并产生该程序集所有类、类型、方法的列表。还记得之前的Help属性和AnyClass类吗?

using System;
using System.Reflection;
using System.Diagnostics;
//attaching Help attribute to entire assembly
[assembly : Help("This Assembly demonstrates custom attributes creation and their run-time query.")]
//our custom attribute class
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.deescription;
}
}
}
//attaching Help attribute to our AnyClass
[HelpString("This is a do-nothing Class.")]
public class AnyClass
{
//attaching Help attribute to our AnyMethod
[Help("This is a do-nothing Method.")]
public void AnyMethod()
{
}
//attaching Help attribute to our AnyInt Field
[Help("This is any Integer.")]
public int AnyInt;
}
class QueryApp
{
public static void Main()
{
}
}

我们将在接下来的两节里在我们的Main方法里加入属性查询的代码。
查询程序集的属性   
  在接下来的代码片段里,我们获取当前进程的名字,并使用Assembly类的LoadFrom方法装载程序集。然后我们使用 GetCustomAttributes方法获取当前程序集的所有自定义属性。接下来的foreach语句又遍历所有的属性对象,并试着将这些属性转化为 Help属性(使用as关键字进行转换,如果转换失败,将会返回一个空值而不是触发一个异常)。再后的一条语句是指如果转换成功,则显示Help属性的所 有数据属性信息。

class QueryApp
{
public static void Main()
{
HelpAttribute HelpAttr;
//Querying Assembly Attributes
String assemblyName;
Process p = Process.GetCurrentProcess();
assemblyName = p.ProcessName + ".exe";
Assembly a = Assembly.LoadFrom(assemblyName);
foreach (Attribute attr in a.GetCustomAttributes(true))
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of {0}:\n{1}", assemblyName,HelpAttr.Description);
}
}
}
}

程序的输出结果:

Description of QueryAttribute.exe:
This Assembly demonstrates custom attributes creation and
their run-time query.
Press any key to continue

查询类、方法和域的属性
  在下面的代码片段里,和上面的代码不同的只是Main方法的第一条语句变成了:

Type type = typeof(AnyClass);

它使用typeof操作符返回AnyClass对应的Type对象。其余的代码也类似上述的代码,我想不需要再做解释了吧。要查询方法和域的属性,我们首先要获得当前类中所有方法和类,然后再用类似于查询类的属性的方法来查询与之对应的属性。

class QueryApp
{
public static void Main()
{
Type type = typeof(AnyClass);
HelpAttribute HelpAttr;
//Querying Class Attributes
foreach (Attribute attr in type.GetCustomAttributes(true))
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of AnyClass:\n{0}",
HelpAttr.Description);
}
}
//Querying Class-Method Attributes
foreach(MethodInfo method in type.GetMethods())
{
foreach (Attribute attr in method.GetCustomAttributes(true))
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of {0}:\n{1}",
method.Name,
HelpAttr.Description);
}
}
}
//Querying Class-Field (only public) Attributes
foreach(FieldInfo field in type.GetFields())
{
foreach (Attribute attr in field.GetCustomAttributes(true))
{
HelpAttr= attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of {0}:\n{1}",
field.Name,HelpAttr.Description);
}
}
}
}
}

下面是程序输出:

Description of AnyClass:
This is a do-nothing Class.
Description of AnyMethod:
This is a do-nothing Method.
Description of AnyInt:
This is any Integer.
Press any key to continue

关于作者
Sadaf Alvi,卡拉奇大学的自然科学学士,主页http://www24.brinkster.com/salvee

[原创]C#自定义属性应用

mikel阅读(867)

C#的自定义属性就是Java中的Metadata,最近一直在鼓捣自己的C#的持久化架构,用到了对表中的字段标示,于是用了Java中Hibernate的解决办法,加入了自定义属性到持久化类的属性上,用于查询时候获得字段以及表的名称进行对应,代码如下:
1.声明自定义属性:

[AttributeUsage(AttributeTargets.Property,Inherited=true,AllowMultiple=false)]
public class SelectParameterAttribute : Attribute
{
private string parameter;
public SelectParameterAttribute(string param)
{
this.parameter = param;
}
public string GetParameter()
{
return this.parameter;
}
}

2.使用自定义属性定义实体类

public class Area
{
[SelectParameterAttribute("@Identifier")]
public int Identifier { get; set; }
[SelectParameterAttribute("@AreaName")]
public string AreaName { get;set;}
}

3.获得自定义属性:

Area area = new Area();
PropertyInfo prop = typeof(Area).GetProperty("Identifier");
Object[] objs=prop.GetCustomAttributes(true);
//Console.Write();
//Object[] attrs =typeof(Area).GetCustomAttributes(true);
Console.Write("objs:{0}",attrs.Length);
foreach (Object attr in attrs)
{
if (attr is SelectParameterAttribute)
{
SelectParameterAttribute a = (SelectParameterAttribute)attr;
Console.Write("param:{0}",a.GetParameter());
}
}

[原创]C#执行分页查询且有OUTPUT参数的存储过程

mikel阅读(902)

1.实例表代码

/****** 对象:  Table [dbo].[Test]    脚本日期: 07/25/2008 10:37:05 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
Create TABLE [dbo].[Test](
[Identifier] [int] IDENTITY(1,1) NOT NULL,
[Name] [nchar](10) COLLATE Chinese_PRC_90_CI_AI NULL,
[Alias] [nvarchar](50) COLLATE Chinese_PRC_90_CI_AI NULL,
[Texts]  COLLATE Chinese_PRC_90_CI_AI NULL,
[Price] [decimal](18, 0) NULL,
[CreateDate] [datetime] NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
[Identifier] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

2.存储过程代码

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
-- =============================================
-- Author:		<Author,,Name>
-- Cr&#101;ate date: <Cr&#101;ate Date,,>
-- Description:	<Description,,>
-- =============================================
Alt&#101;r PROCEDURE [dbo].[TestSel&#101;ct]
@PageSize int=0,
@CurrentPage int=1,
@TotalPage int OUTPUT,
@Identifier int=null,
@Name nchar(10)=null,
@Alias nvarchar(50)=null,
@Texts text=null,
@Price decimal(18,0)=null,
@Cr&#101;ateDate datetime=null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with Sel&#101;ct statements.
SET NOCOUNT ON;
--设置字符型的查询Like
SET @Name = &#39;%&#39;+RTRIM(@Name) + &#39;%&#39;
SET @Alias=&#39;%&#39;+RTRIM(@Alias)+&#39;%&#39;
--判断是否进行分页
IF (@PageSize>0)
BEGIN
--取得总页数
Sel&#101;ct @TotalPage=Count(Identifier) FROM Test Wh&#101;re
(@Identifier is null o&#114; Identifier=@Identifier)
and (@Name is null o&#114; Name like @Name)
and (@Alias is null o&#114; Alias like @Alias)
and (@Texts is null o&#114; Texts like @Texts)
and (@Price is null o&#114; Price=@Price)
and (@Cr&#101;ateDate is null o&#114; Cr&#101;ateDate=@Cr&#101;ateDate)
--进行页数加1
IF(@TotalPage>Round(@TotalPage,0))
BEGIN
--大于当前页面+1
SET @TotalPage=Round(@TotalPage,0)+1
END
ELSE
BEGIN
SET @TotalPage=Round(@TotalPage,0)
END
--执行分页查询
Sel&#101;ct TOP (@PageSize) Identifier,Name,Alias,Texts,Price,Cr&#101;ateDate FROM Test
Wh&#101;re Identifier NOT IN (
Sel&#101;ct Top (@PageSize*(@PageSize-1))Identifier
FROM Test
Wh&#101;re
(@Identifier is null o&#114; Identifier=@Identifier)
and (@Name is null o&#114; Name like @Name)
and (@Alias is null o&#114; Alias like @Alias)
and (@Texts is null o&#114; Texts like @Texts)
and (@Price is null o&#114; Price=@Price)
and (@Cr&#101;ateDate is null o&#114; Cr&#101;ateDate=@Cr&#101;ateDate)
)
and
(@Identifier is null o&#114; Identifier=@Identifier)
and (@Name is null o&#114; Name like @Name)
and (@Alias is null o&#114; Alias like @Alias)
and (@Texts is null o&#114; Texts like @Texts)
and (@Price is null o&#114; Price=@Price)
and (@Cr&#101;ateDate is null o&#114; Cr&#101;ateDate=@Cr&#101;ateDate)
END
ELSE
BEGIN
-- Ins&#101;rt statements for procedure here
Sel&#101;ct Identifier,Name,Alias,Texts,Price,Cr&#101;ateDate
FROM Test
Wh&#101;re
(@Identifier is null o&#114; Identifier=@Identifier)
and (@Name is null o&#114; Name like @Name)
and (@Alias is null o&#114; Alias like @Alias)
and (@Texts is null o&#114; Texts like @Texts)
and (@Price is null o&#114; Price=@Price)
and (@Cr&#101;ateDate is null o&#114; Cr&#101;ateDate=@Cr&#101;ateDate)
END
END

3.C#调用存储过程代码

SqlCommand cmd = new SqlCommand();
SqlConnection cn = new SqlConnection(connectionString);
cn.Open();
cmd.Connection = cn;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "TestSel&#101;ct";
cmd.Parameters.Add(new SqlParameter("@PageSize", 1));
cmd.Parameters.Add(new SqlParameter("@CurrentPage",1));
cmd.Parameters.Add(new SqlParameter("@TotalPage",SqlDbType.Int));
cmd.Parameters["@TotalPage"].Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
Console.Write("totalPage:{0}",cmd.Parameters["@TotalPage"].Value.ToString());
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
Console.Write("totolPage:{0}",reader[6]);
}
Console.Read();

[代码]C#获得存储过程输出参数值

mikel阅读(838)

11.获取Return返回值 
 2
 3
 4程序代码 
 5//存储过程 
 6//Create PROCEDURE MYSQL 
 7//    @a int, 
 8//    @b int 
 9//AS 
10//    return @a + @b 
11//GO 
12SQLConnection conn = new SQLConnection(ConfigurationManager.ConnectionStrings["LocalSQLServer"].ToString()); 
13conn.Open(); 
14SQLCommand MyCommand = new SqlCommand("MYSQL", conn); 
15MyCommand.CommandType = CommandType.StoredProcedure; 
16MyCommand.Parameters.Add(new SqlParameter("@a", SqlDbType.Int)); 
17MyCommand.Parameters["@a"].Value = 10
18MyCommand.Parameters.Add(new SqlParameter("@b", SqlDbType.Int)); 
19MyCommand.Parameters["@b"].Value = 20
20MyCommand.Parameters.Add(new SqlParameter("@return", SqlDbType.Int)); 
21MyCommand.Parameters["@return"].Direction = ParameterDirection.ReturnValue; 
22MyCommand.ExecuteNonQuery(); 
23Response.Write(MyCommand.Parameters["@return"].Value.ToString()); 
24
252.获取Output输出参数值 
26
27
28程序代码 
29//存储过程 
30//Create PROCEDURE MYSQL 
31//    @a int, 
32//    @b int, 
33//    @c int output 
34//AS 
35//    Set @c = @a + @b 
36//GO 
37SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["LocalSQLServer"].ToString()); 
38conn.Open(); 
39SqlCommand MyCommand = new SqlCommand("MYSQL", conn); 
40MyCommand.CommandType = CommandType.StoredProcedure; 
41MyCommand.Parameters.Add(new SqlParameter("@a", SqlDbType.Int)); 
42MyCommand.Parameters["@a"].Value = 20
43MyCommand.Parameters.Add(new SqlParameter("@b", SqlDbType.Int)); 
44MyCommand.Parameters["@b"].Value = 20
45MyCommand.Parameters.Add(new SqlParameter("@c", SqlDbType.Int)); 
46MyCommand.Parameters["@c"].Direction = ParameterDirection.Output; 
47MyCommand.ExecuteNonQuery(); 
48Response.Write(MyCommand.Parameters["@c"].Value.ToString());

[SQL]通用查询存储过程

mikel阅读(982)

1,创建数据表:

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Demo]'and OBJECTPROPERTY(id, N'IsUserTable'= 1)
drop table [dbo].[Demo]
GO

Create TABLE [dbo].[Demo] (
 
[DemoId] [varchar] (50) COLLATE Chinese_PRC_CI_AS NOT NULL ,
 
[DemoName] [varchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
 
[ListPrice] [decimal](180NULL ,
 
[Quantity] [int] NULL ,
 
[LastUpdatedDate] [datetime] NULL 
ON [PRIMARY]
GO

Alter TABLE [dbo].[Demo] WITH NOCHECK ADD 
 
CONSTRAINT [PK_ApplePie] PRIMARY KEY  CLUSTERED 
 (
  
[DemoId]
 )  
ON [PRIMARY] 
GO

2,创建存储过程:

Create         procedure usp_selectDemo 
@DemoId varchar(50= null,
@DemoName varchar(50= null,
@ListPrice decimal = null,
@Quantity int = null,
@LastUpdatedDate datetime = null,
@LastUpdatedDateBegin datetime = null,
@LastUpdatedDateEnd datetime = null

as

select * from demo
usp_selectDemo '1'

/* Powered by taeheelive@hotmail.com 
declare @SQL varchar(500)
set @SQL = ' select DemoId, DemoName, ListPrice, Quantity, LastUpdatedDate from Demo where 1=1'

if @DemoId is not null
begin set @SQL = @sql + ' AND DemoId = '''+@DemoId+''''  end

if @DemoName is not null
begin set @sql = @sql + ' AND DemoName = '''+@DemoName+'''' end

if @ListPrice is not null
begin set @sql = @sql + ' AND ListPrice = '+convert(varchar(10),@ListPrice)+'' end

if @Quantity is not null
begin set @sql = @sql + ' AND Quantity = '+convert(varchar(10),@Quantity)+'' end

if @LastUpdatedDate is not null
begin set @sql = @sql + ' AND LastUpdatedDate = '''+convert(varchar(10),@LastUpdatedDate,120)++''' ' end

if @LastUpdatedDateBegin is not null
begin set @sql = @sql + ' AND LastUpdatedDate >= '''+convert(varchar(10),@LastUpdatedDateBegin,120)++''' ' end

if @LastUpdatedDateEnd is not null
begin set @sql = @sql + ' AND LastUpdatedDate < '''+convert(varchar(10),@LastUpdatedDateEnd,120)+''' ' end

–print (@sql)
exec (@sql)
*/


/* Powered by 江千帆(cnblogs.com) */
 Set @DemoName='%'+RTIM(@DemoName)+'%'
Select DemoId, DemoName, ListPrice, Quantity, LastUpdatedDate FROM Demo
 
where 1=1 
and (@DemoId is null or DemoId = @DemoId)
and (@DemoName is null or DemoName Like @DemoName)
and (@ListPrice is null or ListPrice = @ListPrice)
and (@Quantity is null or Quantity = @Quantity)
and (@LastUpdatedDate is null or LastUpdatedDate = @LastUpdatedDate)
and (@LastUpdatedDateBegin is null or LastUpdatedDate >= @LastUpdatedDateBegin)
and (@LastUpdatedDateEnd is null or LastUpdatedDate < @LastUpdatedDateEnd)


GO

[ORM]写有效率的SQL查询

mikel阅读(847)

轉自:http://www.netfocus.cn/peoplearticle994.html

先站在应用程序的角度说说它们的不同。

1、 直接拼SQL

就像大家了解的那样,直接拼SQL带来了SQL注入攻击,带来了拼时些许的性能损失,但是拼不用添加SqlParameter,会少写很多代码——很多人喜欢直接拼,也许就因为这点。这种做法会把你拼好的SQL原样直接发送到DB服务器去执行。(注意类似”exec yourproc ‘param1’, 12”的语句不在此范畴,这是调用存储过程的一种方式)

2、 参数化SQL

所谓的“参数化SQL”就是在应用程序侧设置SqlCommand.CommandText的时候使用参数(如:@param1),然后通过SqlCommand.Parameters.Add来设置这些参数的值。这种做法会把你准备好的命令通过sp_executesql系统存储过程来执行。通过参数化SQL,和直接拼SQL相比,最直接的好处就是没有SQL注入攻击了。

3、 调用存储过程

直接调用存储过程其实和参数化SQL非常相似。唯一的本质不同在于你发送到DB服务器的指令不再是sp_executesql,而是直接的存储过程调用而已。

很多人非常非常厌恶在应用程序中使用存储过程,而宁愿使用拼SQL或者参数化SQL,理由是它们提供了更好的灵活性——这个理由其实非常非常的发指(俺现在喜欢上这个词了)。

现 在做设计,一般都是从上到下来,重心都在业务逻辑上。传说中的领域模型设计完,测试用例都通过之后,才会考虑数据持久化方式。数据持久化是系统的一部分, 但绝对不是最重要的部分,设计应该围绕业务逻辑开展,持久化应该仅仅是个附件。至少,高层应用应该尽可能的不关心处于最底层的物理存储结构(如:表)和数 据持久、反持久方式(是拼SQL还是存储过程),所以用不用存储过程根本不重要。很多人害怕存储过程,其实是害怕存储过程中包括业务逻辑——真实情况是,如果存储过程中包含了业务逻辑,那一定最初需求分析不够导致用例提取不足,导致测试用例覆盖不够,导致领域模型设计不充分,要不就是偷懒。

=====

站在DB角度讨论它们的不同,主要从cpu、内存方面来考虑,其他诸如安全性,msdn上都有,google也能拿到一堆资料,不再赘述。

首先是查询计划。

SQL编译完一条SQL之后,会把它缓存起来(可以通过sys.syscacheobjects系统视图查看),以后再有相同的查询过来(注意sys.syscacheobjects视图中的sql字段,和它存储的东西完全一样才能称为“相同的查询”),会直接使用缓存,而不再重新编译。

Ø 存储过程,伊只编译一遍(如果没有指定with recompile选项的话,如果指定了,根本就不会生成计划缓存)。

Ø 参数化SQL,和存储过程基本一样,只要是相同的查询,也都是只编译一次,以后重用(当然,指定了option(recompile)的除外)。这里不得不提.NET SqlClient组件的一个龌龊:如果你的参数中包含varchar或者char类型的参数,你在Parameters.Add的时候又没有指定长度,它都会根据你实际传入的字符串长度(假设是n)给你重新定义成nvarchar(n)。如:select * from mytable where col1 = @p1,你设置@p1’123456’,实际传到sql这边的命令是:exec sp_executesql N'select * from mytable where col1 = @p1',N'@p1 nvarchar(6)',@p1=N'123456'。这样,系统缓存中实际存储的sql是:(@p1 nvarchar(6))select * from mytable where col1 = @p1。看到了吧?如果你的输入参数变动比较多,那么看起来同样的一条语句,会被编译很多次,在缓存中存储很多份。cpu和内存都浪费了。这也是在《写有效率的SQL查询IV》中建议的使用最强类型参数匹配的原因之一。

Ø SQL。到这里不说大家也猜的出来,拼SQL要浪费大量的cpu进行编译,浪费大量缓存空间来存储只用一次的查询计划。

服务器的物理内存有限,SQLServer的缓存空间也有限。有限的空间应该被充分利用。通过性能计数器SQL Server:Buffer Manager"Buffer Cache hit ratio来观察缓存命中率。如果它小于百分之90,你就得研究研究了。关注一把诸如sys.dm_os_memory_cache_counterssys.dm_os_memory_cache_entriessys.dm_os_memory_cache_hash_tablessys.syscacheobjects等视图,基本可以确定问题出在哪儿。

cpu方面需要关注三个性能计数器:SQLServer:SQL Statistics"Batch Requests/SecSQLServer:SQL Statistics"SQLCompilations/secSQLServer:SQL Statistics"SQL Re-Compilations/sec。如果compilations数目超过batch请求数目的百分之10,或者recompilations数目超过compilations数目的百分之10,那基本可以说明cpu消耗了太多在编译查询计划上面。
    最后,我的建议是:
    1、DB中的所有操作都尽可能的使用存储过程,哪怕只是一句简单的select。
    2、鄙视拼SQL。
btw:MSDN中对拼SQL称为"ad hoc",呵呵。
==================
补充一点,说明一下N'@p1 nvarchar(6)'换成N'@p1 nvarchar(30)'会重新编译:)。
程序代码如下:

1//
2SqlCommand cmd = new SqlCommand("select * from myt where data = @d", conn);
3cmd.Parameters.Add(new SqlParameter("@d""1234567890"
));
4
cmd.ExecuteNonQuery();
5

6cmd = new SqlCommand("select * from myt where data = @d"
, conn);
7cmd.Parameters.Add(new SqlParameter("@d""123"
));
8
cmd.ExecuteNonQuery();
9

执行完这段程序,可以观察观察sys.syscacheobjects:

   
上图中的5、6行标记了缓存的查询计划。
=======
另外,再来说个更应该注意的地方:

 1//
 2SqlCommand cmd = new SqlCommand("select * from myt where data = @d", con);
 3cmd.Parameters.Add(new SqlParameter("@d""1234567890"
));
 4
cmd.ExecuteNonQuery();
 5

 6cmd = new SqlCommand("select * from myt where data = @d"
, con);
 7cmd.Parameters.Add(new SqlParameter("@d""123"
));
 8
cmd.ExecuteNonQuery();
 9

10cmd = new SqlCommand("select * from myt where data = @a"
, con);
11cmd.Parameters.Add(new SqlParameter("@a""123"
));
12
cmd.ExecuteNonQuery();
13

注意,上述代码中最后一次操作我把@d参数重命名成了@a,然后再来看看sys.syscacheobjects里面有啥:

注意第六行。
================
稍微提一下“简单参数化”(SQL2k中称为自动参数化)和“强制参数化”。在简单参数化下,SQL会试图参数化你的语句,以减少查询计划编译和重编译, 但是可以被参数化的语句非常有限。这个东东可以通过一条简单的insert语句测试到,偶就不贴图了。简单参数化是SQLServer的默认行为。
强制参数化可以通过设置库的属性PARAMETERIZATION为FORCED实现。强制参数化会在很大程度上参数化你的语句。但是它有很多的限制(见MSDN)。
但是要注意,由于查询计划不会有两种和两种以上的副本,所以SQL可能会选择一个不合适的计划来执行你的查询。这也是偶一再的说,如果你的输入参数引起选择性剧烈变化,最好指定recompile选项的原因。

[优化]ASP.NET中常用的26个优化性能方法

mikel阅读(811)

1. 数据库访问性能优化 
 
数据库的连接和关闭
访问数据库资源需要创建连接、打开连接和关闭连接几个操作。这些过程需要多次与数据库交换信息以通过身份验证,比较耗费服务器资源。ASP.NET中提供了连接池(Connection Pool)改善打开和关闭数据库对性能的影响。系统将用户的数据库连接放在连接池中,需要时取出,关闭时收回连接,等待下一次的连接请求。连接池的大小是有限的,如果在连接池达到最大限度后仍要求创建连接,必然大大影响性能。因此,在建立数据库连接后只有在真正需要操作时才打开连接,使用完毕后马上关闭,从而尽量减少数据库连接打开的时间,避免出现超出连接限制的情况。   
使用存储过程  
 
存储过程是存储在服务器上的一组预编译的SQL语句,类似于DOS系统中的批处理文件。存储过程具有对数据库立即访问的功能,信息处理极为迅速。使用存储过程可以避免对命令的多次编译,在执行一次后其执行规划就驻留在高速缓存中,以后需要时只需直接调用缓存中的二进制代码即可。另外,存储过程在服务器端运行,独立于ASP.NET程序,便于修改,最重要的是它可以减少数据库操作语句在网络中的传输。
优化查询语句
  
ASP.NET中ADO连接消耗的资源相当大,SQL语句运行的时间越长,占用系统资源的时间也越长。因此,尽量使用优化过的SQL语句以减少执行时间。比如,不在查询语句中包含子查询语句,充分利用索引等。   
2. 字符串操作性能优化 
 
使用值类型的ToString方法
  
在连接字符串时,经常使用”+”号直接将数字添加到字符串中。这种方法虽然简单,也可以得到正确结果,但是由于涉及到不同的数据类型,数字需要通过装箱操作转化为引用类型才可以添加到字符串中。但是装箱操作对性能影响较大,因为在进行这类处理时,将在托管堆中分配一个新的对象,原有的值复制到新创建的对象中。使用值类型的ToString方法可以避免装箱操作,从而提高应用程序性能。   
运用StringBuilder类   
String类对象是不可改变的,对于String对象的重新赋值在本质上是重新创建了一个String对象并将新值赋予该对象,其方法ToString 对性能的提高并非很显著。在处理字符串时,最好使用StringBuilder类,其.NET 命名空间是System.Text。该类并非创建新的对象,而是通过Append,Remove,Insert等方法直接对字符串进行操作,通过 ToString方法返回操作结果。   其定义及操作语句如下所示:
int num;   System.Text.StringBuilder str = new System.Text.StringBuilder(); //创建字符串   str.Append(num.ToString()); //添加数值num   Response.Write(str.ToString); //显示操作结果3. 优化 Web 服务器计算机和特定应用程序的配置文件以符合您的特定需要
默认情况下,ASP.NET 配置被设置成启用最广泛的功能并尽量适应最常见的方案。因此,应用程序开发人员可以根据应用程序所使用的功能,优化和更改其中的某些配置,以提高应用程序的性能。下面的列表是您应该考虑的一些选项。
仅对需要的应用程序启用身份验证。
默认情况下,身份验证模式为 Windows,或集成 NTLM。大多数情况下,对于需要身份验证的应用程序,最好在 Machine.config 文件中禁用身份验证,并在 Web.config 文件中启用身份验证。根据适当的请求和响应编码设置来配置应用程序。ASP.NET 默认编码格式为 UTF-8。如果您的应用程序为严格的 ASCII,请配置应用程序使用 ASCII 以获得稍许的性能提高。
  
考虑对应用程序禁用 AutoEventWireup。
在 Machine.config 文件中将 AutoEventWireup 属性设置为 false,意味着页面不将方法名与事件进行匹配和将两者挂钩(例如 Page_Load)。如果页面开发人员要使用这些事件,需要在基类中重写这些方法(例如,需要为页面加载事件重写 Page.OnLoad,而不是使用 Page_Load 方法)。如果禁用 AutoEventWireup,页面将通过将事件连接留给页面作者而不是自动执行它,获得稍许的性能提升。
从请求处理管线中移除不用的模块。
默认情况下,服务器计算机的 Machine.config 文件中节点的所有功能均保留为激活。根据应用程序所使用的功能,您可以从请求管线中移除不用的模块以获得稍许的性能提升。检查每个模块及其功能,并按您的需要自定义它。例如,如果您在应用程序中不使用会话状态和输出缓存,则可以从列表中移除它们,以便请求在不执行其他有意义的处理时,不必执行每个模块的进入和离开代码。
4. 一定要禁用调试模式  
在部署生产应用程序或进行任何性能测量之前,始终记住禁用调试模式。如果启用了调试模式,应用程序的性能可能受到非常大的影响。   
5. 对于广泛依赖外部资源的应用程序,请考虑在多处理器计算机上启用网络园艺  
ASP.NET 进程模型帮助启用多处理器计算机上的可缩放性,将工作分发给多个进程(每个CPU一个),并且每个进程都将处理器关系设置为其 CPU。此技术称为网络园艺。如果应用程序使用较慢的数据库服务器或调用具有外部依赖项的 COM 对象(这里只是提及两种可能性),则为您的应用程序启用网络园艺是有益的。但是,在决定启用网络园艺之前,您应该测试应用程序在网络园中的执行情况。  
6. 只要可能,就缓存数据和页输出  
ASP.NET 提供了一些简单的机制,它们会在不需要为每个页请求动态计算页输出或数据时缓存这些页输出或数据。另外,通过设计要进行缓存的页和数据请求(特别是在站点中预期将有较大通讯量的区域),可以优化这些页的性能。与 .NET Framework 的任何 Web 窗体功能相比,适当地使用缓存可以更好的提高站点的性能,有时这种提高是超数量级的。使用 ASP.NET 缓存机制有两点需要注意。首先,不要缓存太多项。缓存每个项均有开销,特别是在内存使用方面。不要缓存容易重新计算和很少使用的项。其次,给缓存的项分配的有效期不要太短。很快到期的项会导致缓存中不必要的周转,并且经常导致更多的代码清除和垃圾回收工作。若关心此问题,请监视与 ASP.NET Applications 性能对象关联的 Cache Total Turnover Rate 性能计数器。高周转率可能说明存在问题,特别是当项在到期前被移除时。这也称作内存压力。
7. 选择适合页面或应用程序的数据查看机制  
根据您选择在 Web 窗体页显示数据的方式,在便利和性能之间常常存在着重要的权衡。例如,DataGrid Web 服务器控件可能是一种显示数据的方便快捷的方法,但就性能而言它的开销常常是最大的。在某些简单的情况下,您通过生成适当的 HTML 自己呈现数据可能很有效,但是自定义和浏览器定向会很快抵销所获得的额外功效。Repeater Web 服务器控件是便利和性能的折衷。它高效、可自定义且可编程。   
8. 将 SqlDataReader 类用于快速只进数据游标  
SqlDataReader 类提供了一种读取从 SQL Server 数据库检索的只进数据流的方法。如果当创建 ASP.NET 应用程序时出现允许您使用它的情况,则 SqlDataReader 类提供比 DataSet 类更高的性能。情况之所以这样,是因为 SqlDataReader 使用 SQL Server 的本机网络数据传输格式从数据库连接直接读取数据。另外,SqlDataReader 类实现 IEnumerable 接口,该接口也允许您将数据绑定到服务器控件。有关更多信息,请参见 SqlDataReader 类。有关 ASP.NET 如何访问数据的信息,请参见通过 ASP.NET 访问数据。   
9. 将 SQL Server 存储过程用于数据访问  
在 .NET Framework 提供的所有数据访问方法中,基于 SQL Server 的数据访问是生成高性能、可缩放 Web 应用程序的推荐选择。使用托管 SQL Server 提供程序时,可通过使用编译的存储过程而不是特殊查询获得额外的性能提高。   
10. 避免单线程单元 (STA) COM 组件  
默认情况下,ASP.NET 不允许任何 STA COM 组件在页面内运行。若要运行它们,必须在 .aspx 文件内将 ASPCompat=true 属性包含在 @ Page 指令中。这样就将执行用的线程池切换到 STA 线程池,而且使 HttpContext 和其他内置对象可用于 COM 对象。前者也是一种性能优化,因为它避免了将多线程单元 (MTA) 封送到 STA 线程的任何调用。使用 STA COM 组件可能大大损害性能,应尽量避免。若必须使用 STA COM 组件,如在任何 interop 方案中,则应在执行期间进行大量调用并在每次调用期间发送尽可能多的信息。另外,小心不要在构造页面期间创建任何 STA COM 组件。例如下面的代码中,在页面构造时将实例化由某个线程创建的 MySTAComponent,而该线程并不是将运行页面的 STA 线程。这可能对性能有不利影响,因为要构造页面就必须完成 MTA 和 STA 线程之间的封送处理。
<%@ Page Language="VB" ASPCompat="true" %> Dim myComp as new MySTAComponent() Public Sub Page_Load() myComp.Name = “Bob” End Sub <% Response.Write(myComp.SayHello) %>
首选机制是推迟对象的创建,直到以后在 STA 线程下执行上述代码,如下面的例子所示。
<%@ Page Language="VB" ASPCompat="true" %> Dim myComp Public Sub Page_Load() myComp = new MySTAComponent() myComp.Name = “Bob” End Sub <% Response.Write(myComp.SayHello) %>
推荐的做法是在需要时或者在 Page_Load 方法中构造任何 COM 组件和外部资源。永远不要将任何 STA COM 组件存储在可以由构造它的线程以外的其他线程访问的共享资源里。这类资源包括像缓存和会话状态这样的资源。即使 STA 线程调用 STA COM 组件,也只有构造此 STA COM 组件的线程能够实际为该调用服务,而这要求封送处理对创建者线程的调用。此封送处理可能产生重大的性能损失和可伸缩性问题。在这种情况下,请研究一下使 COM 组件成为 MTA COM 组件的可能性,或者更好的办法是迁移代码以使对象成为托管对象。   
11. 将调用密集型的 COM 组件迁移到托管代码  
.NET Framework 提供了一个简单的方法与传统的 COM 组件进行交互。其优点是可以在保留现有投资的同时利用新的平台。但是在某些情况下,保留旧组件的性能开销使得将组件迁移到托管代码是值得的。每一情况都是不一样的,决定是否需要迁移组件的最好方法是对 Web 站点运行性能测量。建议您研究一下如何将需要大量调用以进行交互的任何COM 组件迁移到托管代码。许多情况下不可能将旧式组件迁移到托管代码,特别是在最初迁移 Web 应用程序时。在这种情况下,最大的性能障碍之一是将数据从非托管环境封送到托管环境。因此,在交互操作中,请在任何一端执行尽可能多的任务,然后进行一个大调用而不是一系列小调用。例如,公共语言运行库中的所有字符串都是 Unicode 的,所以应在调用托管代码之前将组件中的所有字符串转换成 Unicode 格式。另外,一处理完任何 COM 对象或本机资源就释放它们。这样,其他请求就能够使用它们,并且最大限度地减少了因稍后请求垃圾回收器释放它们所引起的性能问题。   
12. 在 Visual Basic .NET 或 JScript. 代码中使用早期绑定  
以往,开发人员喜欢使用 Visual Basic、VBScript. 和 JScript. 的原因之一就是它们所谓“无类型”的性质。变量不需要显式类型声明,并能够简单地通过使用来创建它们。当从一个类型到另一个类型进行分配时,转换将自动执行。不过,这种便利会大大损害应用程序的性能。Visual Basic 现在通过使用 Option Strict 编译器指令来支持类型安全编程。为了向后兼容,默认情况下,ASP.NET 不启用该选项。但是,为了得到最佳性能,强烈建议在页中启用该选项。若要启用 Option Strict,请将 Strict 属性包括在 @ Page 指令中,或者,对于用户控件,请将该属性包括在 @ Control 指令中。下面的示例演示了如何设置该属性,并进行了四个变量调用以显示使用该属性是如何导致编译器错误的。
<%@ Page Language="VB" Strict="true" %> <% Dim B Dim C As String ' This will cause a compiler error. A = "Hello" ' This will cause a compiler error. B = "World" ' This will not cause a compiler error. C = "!!!!!!" ' But this will cause a compiler error. C = 0 %> JScript. .NET 也支持无类型编程,但它不提供强制早期绑定的编译器指令。若发生下面任何一种情况,则变量是晚期绑定的:被显式声明为 Object,是无类型声明的类的字段,是无显式类型声明的专用函数或方法成员,并且无法从其使用推断出类型。   最后一个差别比较复杂,因为如果 JScript. .NET 编译器可以根据变量的使用情况推断出类型,它就会进行优化。在下面的示例中,变量 A 是早期绑定的,但变量 B 是晚期绑定的。
var A;   var B;   A = “Hello”;   B = “World”;   B = 0; 为了获得最佳的性能,当声明 JScript. .NET 变量时,请为其分配一个类型。例如,var A : String。
13. 使请求管线内的所有模块尽可能高效  
请求管线内的所有模块在每次请求中都有机会被运行。因此,当请求进入和离开模块时快速地触发代码至关重要,特别是在不使用模块功能的代码路径里。分别在使用及不使用模块和配置文件时执行吞吐量测试,对确定这些方法的执行速度非常有用。
14. 使用 HttpServerUtility.Transfer 方法在同一应用程序的页面间重定向  
采用 Server.Transfer 语法,在页面中使用该方法可避免不必要的客户端重定向。
  
15. 必要时调整应用程序每个辅助进程的线程数  
ASP.NET 的请求结构试图在执行请求的线程数和可用资源之间达到一种平衡。已知一个使用足够 CPU 功率的应用程序,该结构将根据可用于请求的 CPU 功率,来决定允许同时执行的请求数。这项技术称作线程门控。但是在某些条件下,线程门控算法不是很有效。通过使用与 ASP.NET Applications 性能对象关联的 Pipeline Instance Count 性能计数器,可以在 PerfMon 中监视线程门控。当页面调用外部资源,如数据库访问或 XML Web services 请求时,页面请求通常停止并释放 CPU。如果某个请求正在等待被处理,并且线程池中有一个线程是自由的,那么这个正在等待的请求将开始被处理。遗憾的是,有时这可能导致 Web 服务器上存在大量同时处理的请求和许多正在等待的线程,而它们对服务器性能有不利影响。通常,如果门控因子是外部资源的响应时间,则让过多请求等待资源,对 Web 服务器的吞吐量并无帮助。为缓和这种情况,可以通过更改 Machine.config 配置文件节点的 maxWorkerThreads 和 maxIOThreads 属性,手动设置进程中的线程数限制。   
注意:辅助线程是用来处理 ASP.NET 请求的,而 IO 线程则是用于为来自文件、数据库或 XML Web services 的数据提供服务的。分配给这些属性的值是进程中每个 CPU 每类线程的最大数目。对于双处理器计算机,最大数是设置值的两倍。对于四处理器计算机,最大值是设置值的四倍。无论如何,对于有四个或八个 CPU 的计算机,最好更改默认值。对于有一个或两个处理器的计算机,默认值就可以,但对于有更多处理器的计算机的性能,进程中有一百或两百个线程则弊大于利。注意进程中有太多线程往往会降低服务器的速度,因为额外的上下文交换导致操作系统将 CPU 周期花在维护线程而不是处理请求上。   
16. 适当地使用公共语言运行库的垃圾回收器和自动内存管理  
小心不要给每个请求分配过多内存,因为这样垃圾回收器将必须更频繁地进行更多的工作。另外,不要让不必要的指针指向对象,因为它们将使对象保持活动状态,并且应尽量避免含 Finalize 方法的对象,因为它们在后面会导致更多的工作。特别是在 Finalize 调用中永远不要释放资源,因为资源在被垃圾回收器回收之前可能一直消耗着内存。最后这个问题经常会对 Web 服务器环境的性能造成毁灭性的打击,因为在等待 Finalize 运行时,很容易耗尽某个特定的资源。   
17. 如果有大型 Web 应用程序,可考虑执行预批编译  
每当发生对目录的第一次请求时都会执行批编译。如果目录中的页面没有被分析并编译,此功能会成批分析并编译目录中的所有页面,以便更好地利用磁盘和内存。如果这需要很长时间,则将快速分析并编译单个页面,以便请求能被处理。此功能带给 ASP.NET 性能上的好处,因为它将许多页面编译为单个程序集。从已加载的程序集访问一页比每页加载新的程序集要快。批编译的缺点在于:如果服务器接收到许多对尚未编译的页面的请求,那么当 Web 服务器分析并编译它们时,性能可能较差。为解决这个问题,可以执行预批编译。为此,只需在应用程序激活之前向它请求一个页面,无论哪页均可。然后,当用户首次访问您的站点时,页面及其程序集将已被编译。没有简单的机制可以知道批编译何时发生。需一直等到 CPU 空闲或者没有更多的编译器进程(例如 csc.exe(C# 编译器)或 vbc.exe(Visual Basic 编译器))启动。还应尽量避免更改应用程序的 \bin 目录中的程序集。更改页面会导致重新分析和编译该页,而替换 \bin 目录中的程序集则会导致完全重新批编译该目录。在包含许多页面的大规模站点上,更好的办法可能是根据计划替换页面或程序集的频繁程度来设计不同的目录结构。不常更改的页面可以存储在同一目录中并在特定的时间进行预批编译。经常更改的页面应在它们自己的目录中(每个目录最多几百页)以便快速编译。Web 应用程序可以包含许多子目录。批编译发生在目录级,而不是应用程序级。
18. 不要依赖代码中的异常  
因为异常大大地降低性能,所以您不应该将它们用作控制正常程序流程的方式。如果有可能检测到代码中可能导致异常的状态,请执行这种操作。不要在处理该状态之前捕获异常本身。常见的方案包括:检查 null,分配给将分析为数字值的 String 一个值,或在应用数学运算前检查特定值。下面的示例演示可能导致异常的代码以及测试是否存在某种状态的代码。两者产生相同的结果。
  try   {   result = 100 / num;   }   catch (Exception e)   {   result = 0;   }   // …to this.   if (num != 0)   result = 100 / num;   else   result = 0;
19. 使用 HttpResponse.Write 方法进行字符串串联
该方法提供非常有效的缓冲和连接服务。但是,如果您正在执行广泛的连接,请使用多个 Response.Write 调用。下面示例中显示的技术比用对 Response.Write 方法的单个调用连接字符串更快。
Response.Write(“a”);   Response.Write(myString);   Response.Write(“b”);   Response.Write(myObj.ToString());   Response.Write(“c”);   Response.Write(myString2);   Response.Write(“d”); 20. 除非有特殊的原因要关闭缓冲,否则使其保持打开
禁用 Web 窗体页的缓冲会导致大量的性能开销。   
21. 只在必要时保存服务器控件视图状态  
自动视图状态管理是服务器控件的功能,该功能使服务器控件可以在往返过程上重新填充它们的属性值(您不需要编写任何代码)。但是,因为服务器控件的视图状态在隐藏的窗体字段中往返于服务器,所以该功能确实会对性能产生影响。您应该知道在哪些情况下视图状态会有所帮助,在哪些情况下它影响页的性能。例如,如果您将服务器控件绑定到每个往返过程上的数据,则将用从数据绑定操作获得的新值替换保存的视图状态。在这种情况下,禁用视图状态可以节省处理时间。默认情况下,为所有服务器控件启用视图状态。若要禁用视图状态,请将控件的EnableViewState 属性设置为 false,如下面的 DataGrid 服务器控件示例所示。
您还可以使用 @ Page 指令禁用整个页的视图状态。当您不从页回发到服务器时,这将十分有用:
<%@ Page EnableViewState="false" %>
注意:@ Control 指令中也支持 EnableViewState 属性,该指令允许您控制是否为用户控件启用视图状态。若要分析页上服务器控件使用的视图状态的数量,请(通过将 trace=”true” 属性包括在 @ Page 指令中)启用该页的跟踪并查看 Control Hierarchy 表的 Viewstate 列。有关跟踪和如何启用它的信息,请参见 ASP.NET 跟踪。
22. 避免到服务器的不必要的往返过程  
虽然您很可能希望尽量多地使用 Web 窗体页框架的那些节省时间和代码的功能,但在某些情况下却不宜使用 ASP.NET 服务器控件和回发事件处理。通常,只有在检索或存储数据时,您才需要启动到服务器的往返过程。多数数据操作可在这些往返过程间的客户端上进行。例如,从 HTML 窗体验证用户输入经常可在数据提交到服务器之前在客户端进行。通常,如果不需要将信息传递到服务器以将其存储在数据库中,那么您不应该编写导致往返过程的代码。如果您开发自定义服务器控件,请考虑让它们为支持 ECMAScript. 的浏览器呈现客户端代码。通过以这种方式使用服务器控件,您可以显著地减少信息被不必要的发送到 Web 服务器的次数。
使用 Page.IsPostBack 避免对往返过程执行不必要的处理
如果您编写处理服务器控件回发处理的代码,有时可能需要在首次请求页时执行其他代码,而不是当用户发送包含在该页中的 HTML 窗体时执行的代码。根据该页是否是响应服务器控件事件生成的。
使用 Page.IsPostBack 属性有条件地执行代码
例如,下面的代码演示如何创建数据库连接和命令,该命令在首次请求该页时将数据绑定到 DataGrid 服务器控件。
void Page_Load(Object sender, EventArgs e)   {   // Set up a connection and command here.   if (!Page.IsPostBack)   {   String query = “select * from Authors where FirstName like '%JUSTIN%'”;   myCommand.Fill(ds, “Authors”);   myDataGrid.DataBind();   }   }
由于每次请求时都执行 Page_Load 事件,上述代码检查 IsPostBack 属性是否设置为 false。如果是,则执行代码。如果该属性设置为 true,则不执行代码。注意 如果不运行这种检查,回发页的行为将不更改。Page_Load 事件的代码在执行服务器控件事件之前执行,但只有服务器控件事件的结果才可能在输出页上呈现。如果不运行该检查,仍将为 Page_Load 事件和该页上的任何服务器控件事件执行处理。   
23. 当不使用会话状态时禁用它  
并不是所有的应用程序或页都需要针对于具体用户的会话状态,您应该对任何不需要会话状态的应用程序或页禁用会话状态。   若要禁用页的会话状态,请将 @ Page 指令中的 EnableSessionState 属性设置为 false。例如:
<%@ Page EnableSessi %> 注意:如果页需要访问会话变量,但不打算创建或修改它们,则将@ Page 指令中的 EnableSessionState 属性设置为ReadOnly。还可以禁用 XML Web services 方法的会话状态。有关更多信息,请参见使用 ASP.NET 和 XML Web services 客户端创建的 XML Web services。若要禁用应用程序的会话状态,请在应用程序 Web.config 文件的 sessionstate 配置节中将 mode 属性设置为 off。例如:
24. 仔细选择会话状态提供程序  
ASP.NET 为存储应用程序的会话数据提供了三种不同的方法:进程内会话状态、作为 Windows 服务的进程外会话状态和 SQL Server 数据库中的进程外会话状态。每种方法都有自己的优点,但进程内会话状态是迄今为止速度最快的解决方案。如果只在会话状态中存储少量易失数据,则建议您使用进程内提供程序。进程外解决方案主要用于跨多个处理器或多个计算机缩放应用程序,或者用于服务器或进程重新启动时不能丢失数据的情况。有关更多信息,请参见 ASP.NET 状态管理。   
25. 不使用不必要的Server Control
ASP.net中,大量的服务器端控件方便了程序开发,但也可能带来性能的损失,因为用户每操作一次服务器端控件,就产生一次与服务器端的往返过程。因此,非必要,应当少使用Server Control。   
26. ASP.NET应用程序性能测试  
在对ASP.NET应用程序进行性能测试之前,应确保应用程序没有错误,而且功能正确。具体的性能测试可以采用以下工具进行:Web Application Strees Tool (WAS)是Microsoft发布的一个免费测试工具,可以从http://webtool.rte.microsoft.com/上下载。它可以模拟成百上千个用户同时对web应用程序进行访问请求,在服务器上形成流量负载,从而达到测试的目的,可以生成平均TTFB、平均TTLB等性能汇总报告。 Application Center Test (ACT) 是一个测试工具,附带于Visual Studio.NET的企业版中,是Microsoft正式支持的web应用程序测试工具。它能够直观地生成图表结果,功能比WAS多,但不具备多个客户机同时测试的能力。服务器操作系统”管理工具”中的”性能”计数器,可以对服务器进行监测以了解应用程序性能。   
结论:
对于网站开发人员来说,在编写ASP.NET应用程序时注意性能问题,养成良好的习惯,提高应用程序性能,至少可以推迟必需的硬件升级,降低网站的成本。

[文档]Null值判断

mikel阅读(903)

1) NULL
null 关键字是表示不引用任何对象的空引用的文字值。null 是引用类型变量的默认值。那么也只有引用型的变量可以为NULL,如果 int i=null,的话,是不可以的,因为Int是值类型的。
(2) DBNULL
DBNull在DotNet是单独的一个类型,该类只能存在唯一的实例,DBNULL.,DBNull唯一作用是可以表示数据库中的字符串,数字,或日期,为什么可以表示原因是DotNet储存这些数据的类(DataRow等)都是以 object 的形式来储存数据的。对于 DataRow , 它的 row[column] 返回的值永远不为 null , 要么就是具体的为column 的类型的值 。 要么就是 DBNull 。 所以 row[column].ToString() 这个写法永远不会在ToString那里发生NullReferenceException。DBNull 实现了 IConvertible 。但是,除了 ToString 是正常的外,其他的ToXXX都会抛出不能转换的错误。
(3) “”和String.Empty
这两个都是表示空字符串,其中有一个重点是string str1=”” 和 string str2=null 的区别,这样定义后,str1是一个空字符串,空字符串是一个特殊的字符串,只不过这个字符串的值为空,在内存中是有准确的指向的,string str2=null,这样定义后,只是定义了一个string 类的引用,str2并没有指向任何地方,在使用前如果不实例化的话,都将抱错。
(4) Convert.IsDBNull()
Convert.IsDBNull()返回有关指定对象是否为 DBNull 类型的指示,即是用来判断对象是否为DBNULL的。其返回值是True或Flase。
其实这些广义上的”空值”理解了后,还是有很大的区别的,甚至根本就没有关系。完全是两个概念。
摘自 http://blog.csdn.net/yumanqing/archive/2007/01/29/1497216.aspx