[测试]使用NUnit进行单元测试入门

mikel阅读(722)

使用Nunit进行单元测试入门

前言:

Nunit是一项开源的项目,利用Nunit,你可以方便快捷地对已有的.NET组件进行单元测试。利用Nunit,你可以在不修改原有代码的情况下,编写专门的测试代码对需要测试的函数和组件进行测试。如果大家有兴趣可以将NunitVSTS自带的单元测试进行比较:)

正文:

如果你的机器上没有安装Nunit,请先到http://www.nunit.org/download.html下载,并安装到机器上。

接下来,我们来看看究竟如何来使用Nunit进行单元测试

现在,我有这样一个Account类:

namespace bank

{

    public class Account

    {

        private float balance;

        public void Deposit(float amount)

        {

            balance += amount;

        }

        public void Withdraw(float amount)

        {

            balance -= amount;

        }

        public void TransferFunds(Account destination, float amount)

        {

        }

        public float Balance

        {

            get { return balance; }

        }

    }

}

但是我并不知道这个类是否可以正确地工作,所以我需要对Account类进行单元测试。

在可以新建一个类库项目,在这个类库的引用项目中指定Account.dll和Nunit.framework.dll。注意,Nunit.framework.dll文件在你安装Nunit的bin目录下。

然后我在这个新建的类库项目中添加一个AccountTest的类文件后编译,该文件如下:

namespace bank

{

    using NUnit.Framework;

    [TestFixture]

    public class AccountTest

    {

        [Test]

        public void TransferFunds()

        {

            Account source = new Account();

            source.Deposit(200.00F);

            Account destination = new Account();

            destination.Deposit(150.00F);

            source.TransferFunds(destination, 100.00F);

            Assert.AreEqual(250.00F, destination.Balance);

            Assert.AreEqual(100.00F, source.Balance);

        }

    }

}

请注意,这个测试类一定要是public的,否则Nunit无法正常工作。

接 下来,我们启动Nunit(在你的桌面上有快捷方式的图标),然后在File -> Open Project中打开到你刚刚编译出来的AccountTest.dll即可。这个时候,你会发现右边有一个“Run”的按钮可以使用了,点击它,这个时 候,Account类的单元测试就开始了。测试完毕后,出现红色的标志,这是测试失败的标志。这说明我们的Account类有问题,我们需要将 Account类中的Withdraw方法完成:

public void Withdraw(float amount)

{

    balance -= amount;

}

重新编译Account类以后,我们再次点击“Run”按钮,这个时候,红色变成了绿色,这说明测试通过了。

这样,你便完成了对Account类的测试,接下来,我们来详细说一说AccountTest类中奇怪的Attributes

TestFixture

这个Attribute只能在类上使用,他告诉Nunit这个类中包含有需要测试的方法。

不过注意这个类保证一下4点:

1.   这个类必须是Public的。

2.   这个类不能是Abstract的。

3.   这个类必须有一个默认构造函数。

4.   这个类中的方法被标记的Attribute只能出现一次:SetUp, TearDown, TestFixtureSetUp 和 TestFixtureTearDown。

Test

这个Attribute只能标记了TestFixture的类中的方法上使用,他告诉Nunit这个是一个需要测试的方法。

在下来就是AccountTest类中的TransferFunds方法中的Assert

它类似与.NET Framework中的Assert类,如果Assert中的方法返回False,这测试结果为失败,否则为成功。在Nunit中,就是使用Assert来判断一个测试是否通过。

好了,相信现在大家一定对Nunit的使用有了基本的了解了。那么,恭喜大家了。

后记:

Nunit是一个不断完善的免费工具,但是这并代表他比VSTS自带的单元测试工具差,他对VS的支持也是相当不错的,而且可以针对自己对测试的需要来自定义很多功能,同时对插件的支持也给了我们更大的空间来发挥Nunit的功能。

关于更多更详细的介绍,请大家查看Nunit的帮助文档:)

[MVC]ASP.Net MVC Preview4的AJAX支持

mikel阅读(752)

原文地址:http://www.singingeels.com/Articles/AJAX_Panels_with_ASPNET_MVC.aspx

原著作者:Timothy Khouri

翻译:Anders Liu

ASP.NET MVC Preview 4带来了一些AJAX支持,能够适应MVC设计模式的本质。这篇文章向你展示了在ASP.NET MVC中使用“延迟加载AJAX面板”是如何使其变得不可思议的简单。

首先,“ASP.NET AJAX”的问题

由于“Web Froms”(传统的ASP.NET)是基于同时包含了表现层和后台代码的“页面”的,所以ASP.NET AJAX并没有像它本应该的那样光芒四射。很多步入AJAX领域的ASP.NET开发者只是向页面中随意地放置一些“UpdatePanel”来使其“看 上去”支持AJAX。实际上,这只是防止了页面的“闪烁”,而页面还是进行了完整的回发,并且要经历整个页面的生存周期。

这并不是说这些问题是ASP.NET AJAX的责任,而是由是否需要使用完全的AJAX的不同心态造成的。公平地讲,有比UpdatePanel控件更好的ASP.NET AJAX选择。包括:

  • Page Methods——直接调用位于后台代码中(服务器上)的方法。
  • Web Services——调用位于应用程序的Web Services中的方法。

这两种选择比使用UpdatePanel“好”在无需重新加载整个页面,只需向客户端呈现一部分HTML即可。但它们“坏”在你需要使用JavaScript实现所有的表现逻辑(不用别人说也知道这是很恐怖的)。

MVC AJAX给你转机

如果你能得到和使用UpdatePanel一样的ASP.NET呈现能力,并且所有的代码都能分离开,性能也和访问Web Services一样,你会不吃惊吗?来吧,一起感谢MVC设计模式的本质吧——还要感谢ASP.NET MVC——你能!

我们来看一下现实世界中创建“延迟加载”AJAX面板的问题。假设我们有一个Web应用程序,用于向客户端发布一些巨大 的报表。如果我们不使用AJAX,每个报表都会增加页面的整体加载时间。因此,我们将异步地请求每个报表(使用AJAX),是的页面自身能够立即加载,而 每个报表都会在运行完毕后显示出来。

我们将向页面中添加4个“报表”。每个报表都要运行3到5秒。因此如果我们使用传统的Web Forms,这个页面将要加载12到20秒。但由于有了MVC,我们可以将加载时间降低到5秒,并且页面看上去仍然很漂亮。

注意

有很重要的一点需要注意。上面提到的性能收益会受到一些因素的限制。你必须考虑到服务器要处理所有这些请求,这会使最终的结果有所下降。另外,很多浏览器只允许2个并发的下载,因此对于上面的例子,你节省的时间会降低约50%。

使用Ajax.Form方法

MVC Preview 4在“this.Ajax”字段中为所有MVC页面和MVC用户控件添加了一些方法。“Ajax.Form”方法和“Html.Form”方法类似,但它 会添加一些JavaScript来帮助确保可以异步地发送请求。另外,这里还能为应该返回的结果定义一个HTML元素。

例如,如果你想POST诸如“发送邮件”这样的操作,并希望服务器能将“Your email has been sent”这样的文字放在一个ID是“resultDiv”的<div>内,你需要这样做:

  1. <div id="resultDiv"></div>  
  2. <% using (Ajax.Form("SendMail", new AjaxOptions { UpdateTargetId = "resultDiv" })) { %>  
  3.    <!– Your form elements here… –>  
  4. <% } %>  

上面的代码会生成下面的<form>标签:

  1. <form action="/Home/SendMail" onsubmit="Sys.Mvc.AsyncForm.handleSubmit(this, { insertionMode: 0, updateTargetId: 'resultDiv' }); return false;">  
  2.    <!– Your form elements here… –>  
  3. </form>  

和我们前面提到的一样,这非常像“Html.Form”方法生成的form,但你也能清楚地看到“onsubmit”方法被替换为使用AJAX来发送请求,而且你也能看到“resultDiv”参数被传递到服务器了。

服务器会和平常一样接收这个请求,它也会和平常一样发送请求的数据。这个魔术发生在ASP.NET MVC内部。从服务器传回的响应将会放在我们的<div>中,页面的其他部分不会改变。

这是真的,非常简单的AJAX。然而,还必须向你指出一个问题,该表单只在用户明确地点击了提交按钮 (<input type="submit">)“提交”该表单时,它才会与服务器联系。为了解决这个问题,我们需要添加一行JavaScript使其能够自动提交 表单,异步地请求报表。我还对“Form”方法进行了些微改动,添加了一个HTML ID属性,以便我能在JavaScript中访问它。

现在我们新的代码看起来是下面这样:

  1. <div id="resultDiv"></div>  
  2. <% using (Ajax.Form("ReportOne", null,  
  3.       new AjaxOptions { UpdateTargetId = "resultOne" },  
  4.       new { id="reportFormOne" })) { } %>  
  5. <script type="text/javascript">  
  6.    $get("reportFormOne").onsubmit();  
  7. </script>  

提示

如果我直接调用“Sys.Mvc.AsyncForm.handleSubmit”方法,上面的代码还能更简单些。但我选择让MVC为我创建表单,然后通过JavaScript访问它,因此如果JavaScript方法将来发生了变化,我依然能够使用。

看看结果吧!

使用上面的方法,再加上我从Internet上弄来的“loading gif”,我们就有了这样一个页面,它可以动态地(并且是异步地)加载报表,并在可用的时候立即显示给用户。下面是最终结果的一些截图:

这里是上面的项目的源代码。记住,该项目是在ASP.NET MVC Preview 4下编写和编译的,你下载的时候可能已经过时了(译注:真希望它赶紧过时):

此处下载源代码:SingingEels_MVC_Asyncronous_AJAX_Panels.zip

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

mikel阅读(1148)

原著: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阅读(868)

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

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

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

public class Area
{
[Sel&#101;ctParameterAttribute("@Identifier")]
public int Identifier { get; set; }
[Sel&#101;ctParameterAttribute("@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 Sel&#101;ctParameterAttribute)
{
Sel&#101;ctParameterAttribute a = (Sel&#101;ctParameterAttribute)attr;
Console.Write("param:{0}",a.GetParameter());
}
}

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

mikel阅读(908)

1.实例表代码

/****** 对象:  Table [dbo].[Test]    脚本日期: 07/25/2008 10:37:05 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
Cr&#101;ate 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,
[Cr&#101;ateDate] [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阅读(840)

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阅读(985)

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阅读(848)

轉自: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选项的原因。