[转载]浅谈大型网站动态应用系统架构

mikel阅读(908)

[转载]浅谈大型网站动态应用系统架构_IT新闻_博客园.

动态应用,是相对于网站静态内容而言,是指以c/c++、php、Java、perl、.net等服务器端语言开发的网络应用软件,比如论坛、网络相册、交友、BLOG等常见应用。动态应用系统通常与数据库系统、缓存系统、分布式存储系统等密不可分。

大型动态应用系统平台主要是针对于大流量、高并发网站建立的底层系统架构。大型网站的运行需要一个可靠、安全、可扩展、易维护的应用系统平台做为支撑,以保证网站应用的平稳运行。

大型动态应用系统又可分为几个子系统:

1)Web前端系统

2)负载均衡系统

3)数据库集群系统

4)缓存系统

5)分布式存储系统

6)分布式服务器管理系统

7)代码分发系统

Web前端系统

结构图:

为了达到不同应用的服务器共享、避免单点故障、集中管理、统一配置等目的,不以应用划分服务器,而是将所有服务器做统一使用,每台服务器都可以对多个 应用提供服务,当某些应用访问量升高时,通过增加服务器节点达到整个服务器集群的性能提高,同时使他应用也会受益。该Web前端系统基于 Apache/Lighttpd/Eginx等的虚拟主机平台,提供PHP程序运行环境。服务器对开发人员是透明的,不需要开发人员介入服务器管理

负载均衡系统

负载均衡系统分为硬件和软件两种。硬件负载均衡效率高,但是价格贵,比如F5等。软件负载均衡系统价格较低或者免费,效率较硬件负载均衡系统低,不过对于流量一般或稍大些网站来讲也足够使用,比如lvs, nginx。大多数网站都是硬件、软件负载均衡系统并用。

数据库集群系统

结构图:

由于Web前端采用了负载均衡集群结构提高了服务的有效性和扩展性,因此数据库必须也是高可靠的,才能保证整个服务体系的高可靠性,如何构建一个高可靠的、可以提供大规模并发处理的数据库体系?

我们可以采用如上图所示的方案:

1) 使用 MySQL 数据库,考虑到Web应用的数据库读多写少的特点,我们主要对读数据库做了优化,提供专用的读数据库和写数据库,在应用程序中实现读操作和写操作分别访问不同的数据库。

2) 使用 MySQL Replication 机制实现快速将主库(写库)的数据库复制到从库(读库)。一个主库对应多个从库,主库数据实时同步到从库。

3) 写数据库有多台,每台都可以提供多个应用共同使用,这样可以解决写库的性能瓶颈问题和单点故障问题。

4) 读数据库有多台,通过负载均衡设备实现负载均衡,从而达到读数据库的高性能、高可靠和高可扩展性。

5) 数据库服务器和应用服务器分离。

6) 从数据库使用BigIP做负载均衡。

缓存系统

缓存分为文件缓存、内存缓存、数据库缓存。在大型Web应用中使用最多且效率最高的是内存缓存。最常用的内存缓存工具是Memcached。使用正确的缓存系统可以达到实现以下目标:

1、使用缓存系统可以提高访问效率,提高服务器吞吐能力,改善用户体验。

2、减轻对数据库及存储集服务器的访问压力。

3、Memcached服务器有多台,避免单点故障,提供高可靠性和可扩展性,提高性能。

分布式存储系统

结构图:

Web系统平台中的存储需求有下面两个特点:

1) 存储量很大,经常会达到单台服务器无法提供的规模,比如相册、视频等应用。因此需要专业的大规模存储系统。

2) 负载均衡cluster中的每个节点都有可能访问任何一个数据对象,每个节点对数据的处理也能被其他节点共享,因此这些节点要操作的数据从逻辑上看只能是一个整体,不是各自独立的数据资源。

因此高性能的分布式存储系统对于大型网站应用来说是非常重要的一环。(这个地方需要加入对某个分布式存储系统的简单介绍。)

分布式服务器管理系统

结构图:

随着网站访问流量的不断增加,大多的网络服务都是以负载均衡集群的方式对外提供服务,随之集群规模的扩大,原来基于单机的服务器管理模式已经不能够满足我们的需求,新的需求必须能够集中式的、分组的、批量的、自动化的对服务器进行管理,能够批量化的执行计划任务。

在分布式服务器管理系统软件中有一些比较优秀的软件,其中比较理想的一个是Cfengine。它可以对服务器进行分组,不同的分组可以分别定制 系统配置文件、计划任务等配置。它是基于C/S 结构的,所有的服务器配置和管理脚本程序都保存在Cfengine Server上,而被管理的服务器运行着 Cfengine Client 程序,Cfengine Client通过SSL加密的连接定期的向服务器端发送请求以获取最新的配置文件和管理命令、脚本程序、补丁安装等任务。

有了Cfengine这种集中式的服务器管理工具,我们就可以高效的实现大规模的服务器集群管理,被管理服务器和 Cfengine Server 可以分布在任何位置,只要网络可以连通就能实现快速自动化的管理。

代码发布系统

结构图:

随着网站访问流量的不断增加,大多的网络服务都是以负载均衡集群的方式对外提供服务,随之集群规模的扩大,为了满足集群环境下程序代码的批量分发和更新,我们还需要一个程序代码发布系统。

这个发布系统可以帮我们实现下面的目标:

1) 生产环境的服务器以虚拟主机方式提供服务,不需要开发人员介入维护和直接操作,提供发布系统可以实现不需要登陆服务器就能把程序分发到目标服务器。

2) 我们要实现内部开发、内部测试、生产环境测试、生产环境发布的4个开发阶段的管理,发布系统可以介入各个阶段的代码发布。

3) 我们需要实现源代码管理和版本控制,SVN可以实现该需求。

这里面可以使用常用的工具Rsync,通过开发相应的脚本工具实现服务器集群间代码同步分发。

[转载]浅析Web数据存储-Cookie、UserData、SessionStorage、WebSqlDatabase

mikel阅读(1208)

[转载]浅析Web数据存储-Cookie、UserData、SessionStorage、WebSqlDatabase – 葡萄城控件技术团队博客 – 博客园.

Cookie

它是标准的客户端浏览器状态保存方式,可能在浏览器诞生不久就有Cookie了,为什么需要Cookie 这个东东?由于HTTP协议没有状态,所以需要一个标志/存储来记录客户浏览器当前的状态,保证客户浏览器和服务器通讯时可以知道客户浏览器当前的状态。 Cookie就是记录这个状态的容器,Cookie在每次请求的时候都被带回到服务器,从而保证了Server可以知道浏览器当前的状态,由于 Cookie会被带回到Server,所以Cookie的内容不能存太多,最多不能超过4K,4K 限制的介绍 http://ec.europa.eu/ipg/standards/cookies/index_en.htm
其中一段内容为:

A browser is only required to store up to 300 cookies overall and maintain only the last 20 from each domain. The maximum size of a cookie is 4K of disk space.

但是在一些场景下可能需要存储超过4K或者更多的数据,但是这些数据不用在每次请求的时候被带回到服务器,只要能在客户的浏览器上保存住,并且可以 方便的被JavaScript读写就可以了,这种需求尤为在中大型RIA的应用场景下更加的迫切,部分数据放在客户浏览器,节约带宽,提高浏览速度。 HTML5标准已经替我们想到了满足这种需求的方案:sessionStorage , webSQLDatabase, 微软的IE 有 userData 方案。
userData
微软对USERDATA的介绍: http://msdn2.microsoft.com/en-us/library/ms531424(VS.85).aspx
其中一段内容为:

Security Alert:For security reasons, a UserData store is available only in the same directory and with the same protocol used to persist the store.
Security Alert:Using this behavior incorrectly can compromise the security of your application. Data in a UserData store is not encrypted and therefore not secure. Any application that has access to the drive where UserData is saved has access to the data. Therefore, it is recommended that you not persist sensitive data like credit card numbers. For more information, see Security Considerations: DHTML and Default Behaviors.
……
The userData behavior persists data across sessions, using one UserData store for each object. The UserData store is persisted in the cache using the save and load methods. Once the UserData store has been saved, it can be reloaded even if Microsoft Internet Explorer has been closed and reopened.
Setting the userData behavior class on the html, head, title, or style object causes an error when the save or load method is called.

userData可以在同目录同协议下相互访问,长期存储在客户机器上。最大存储空间也增大了很多。userData需要绑定到一个Dom元素上使 用。在userData的method中有removeAttribute方法。经过测试代码发现removeAttribute方法好像不是很管用,需 要使用像cookie过期的方式,才可以彻底的删除一个userData Attribute。
http://www.itwen.com/04web/11skill/skill20060918/60588.html 中介绍说userData存储在X:\Documents and Settings\当前用户\UserData\ 目录下。具体细节MS在userData说明文档中没有具体说明。
sessionStorage
HTML5 标准对 sessionStorage的介绍: http://www.whatwg.org/specs/web-apps/current-work/
其中对 sessionStorage 的介绍:

This specification introduces two related mechanisms, similar to HTTP session cookies [RFC2965], for storing structured data on the client side.
The first is designed for scenarios where the user is carrying out a single transaction, but could be carrying out multiple transactions in different windows at the same time.
Cookies dont really handle this case well. For example, a user could be buying plane tickets in two different windows, using the same site. If the site used cookies to keep track of which ticket the user was buying, then as the user clicked from page to page in both windows, the ticket currently being purchased would “leak” from one window to the other, potentially causing the user to buy two tickets for the same flight without really noticing.
To address this, this specification introduces the sessionStorage DOM attribute. Sites can add data to the session storage, and it will be accessible to any page from that origin opened in that window.

Html5 sessionStorage Demo: http://html5demos.com/storage
下面是根据 http://www.blogjava.net/emu/archive/2006/10/04/73385.html 中提到的IE FF 兼容userData的测试代码:

01 function isIE() {
02 return !!document.all;
03 }
04
05 function initUserData() {
06 if (isIE()) document.documentElement.addBehavior("#default#userdata");
07 }
08
09 function saveUserData(key, value) {
10 var ex;
11 if (isIE()) {
12 //IE
13 with (document.documentElement) try {
14 load(key);
15 setAttribute("value", value);
16 save(key);
17 return getAttribute("value");
18 } catch (ex) {
19 alert(ex.message)
20 }
21 } else if (window.sessionStorage) {
22 //FF 2.0+
23 try {
24 sessionStorage.setItem(key, value)
25 } catch (ex) {
26 alert(ex);
27 }
28 } else {
29 alert("Error occured in user data saving. your browser do not support user data.");
30 }
31 }
32
33 function loadUserData(key) {
34 var ex;
35 if (isIE()) {
36 //IE
37 with (document.documentElement) try {
38 load(key);
39 return getAttribute("value");
40 } catch (ex) {
41 alert(ex.message); return null;
42 }
43 } else if (window.sessionStorage) {
44 //FF 2.0+
45 try {
46 return sessionStorage.getItem(key)
47 } catch (ex) {
48 alert(ex)
49 }
50 } else {
51 alert("Error occured in user data loading. your browser do not support user data.")
52 }
53 }
54 function deleteUserData(key) {
55 var ex;
56 if (isIE()) {
57 //IE
58 with (document.documentElement) try {
59 load(key);
60 expires = new Date(315532799000).toUTCString();
61 save(key);
62 }
63 catch (ex) {
64 alert(ex.message);
65 }
66 } else if (window.sessionStorage) {
67 //FF 2.0+
68 try {
69 sessionStorage.removeItem(key)
70 } catch (ex) {
71 alert(ex)
72 }
73 } else {
74 alert("Error occured in user data deleting. your browser do not support user data.")
75 }
76 }

userData和sessionStorage共同的特点就是:这两个对象都可以存储比cookie大的多的多内容。并且不会随每次请求带回到服务器端。但是根据Html5标准和测试发现userData和sessionStorage有很多地方是不同的。

下面是一个测试页面:

31_110118_jg9ncookieVsStoragegif

其中的 SetInsurance link 会操作JavaScript 在IE下用userData写数据, 在FF下用sessionStore写数据。在IE下的情况是:关闭IE或者重启机器写入的值都不会丢失。在FF下的情况很有意思:在本页面写入的值在本 页面可以访问,在由本页面所打开的其它页面可以访问。但是就算本页面开着,在导航栏里输入地址,打开本页面,存入的值就不能访问了。在本页面存入的值,在 它的父页面(打开这个页面的页面)是访问不到的。又看了看Html5标准。sessionStorage 的全名是:Client-side session and persistent storage of name/value pairs 意思估计是存储在Client的内容是有session 会话的,存储的值由session会话所维系,一旦session会话中断或者丢失,存入的值也就随之消失了。所以当页面没有session(父页面,由 地址栏打开的页面),是取不到值的。当FF关闭或者重启机器必然也就取不到值了。
webSQLDatabase
webSqlDatabase在HTML5 标准中是非常Cool的一个东东, 用JavaScript写SQL查询,数据库就在浏览器里,这在以前几乎不敢想象。不过今天Safari, Chrome, Opera 都已经支持了,两个webSqlDatabase 的 Demo 页面: http://html5demos.com/database http://html5demos.com/database-rollback
W3C 对WEBSQLDATABASE 的介绍页面: http://dev.w3.org/html5/webdatabase/
WiKi上一个简明的说明: http://en.wikipedia.org/wiki/Web_SQL_Database

From W3C: “…an API for storing data in databases that can be queried using a variant of SQL”
Web SQL Database is supported by Google Chrome[1], Opera and Safari but will not be implemented by Mozilla(Firefox)[2] who instead propone Indexed Database API access.

不知道 HTML 5 的 SQLDB 会被浏览器支持的怎么样, 不过sessionStorage看上去已经可以基本满足需求了。

[转载]数据库操作类库整理

mikel阅读(1076)

[转载]【原创】数据库操作类库整理 – 悟道人生 – 博客园.

一直以来对设计模式,怎么写出高质量、高效、可移植性强的理解不深。也许经验不足是一个很大的原因吧。今天花了很久的时间整理了一个数据库操作类库(简化版),希望能得到各位前辈的指点。

首先,我定义了一个数据库操作接口IDbOperator,该接口实现IDisposable接口,用于操作结束后释放各种资源。类图如下:

属性:

ConnectString用于获取数据库连接字符串

DbConn用于获取当前数据库连接(IDbConnection接口)

方法:

Open():打开数据库连接

Close():关闭数据库连接

GetCommand():获取当前数据源,返回IDbCommand

GetDataSet():获取数据集,返回DataSet数据集

GetSingle<T>():泛型方法,获取单个数据信息

Query():执行SQL语句

QueryTran<T>():执行事务操作

RunProcedure():执行存储过程

由于我想有不少操作,与数据库类型无关。操作是通用的,因此我新建了一个数据库操作抽象类AbstractDbOperator,它实现了接口 IDbOperator,实现了一下方法Open()、Close()、QueryTran<T>()、Query()、 GetSingle<T>()。

并将这些方法定义为虚方法,这样在其继承子类中可以进行重写。

而由于GetCommand()、GetDataSet()、RunProcedure()等方法与数据库类型有关,不同的数据库实现方式不同,因此在不同的数据库操作类中实现。类图如下:

具体的数据库操作类被定义为sealed,同时被定义为internal,因此它们都不允许在程序集以外访问。可能这时有人会问,不让在程序集以外访问,这样的类库有何意义呢?别急,下面还有一个类起决定作用。

新建了一个DBOperator类,用于对外公布数据库操作,它只有一个方法,用于获得数据库操作接口实例。类图如下:

方法:

GetInstance():采用反射机制,分析当前数据库类型,并实例化相应数据库操作类,必须在config文件中appSettings节点下添加如下设置:

1 <add key="assemblyName" value="Higame.Database"/>   <!--程序集名称-->
2 <add key="typeName" value="Higame.Database.OledbOperator"/>  <!--数据库操作类名-->
3 <add key="ConnectionString" value="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\\CSharp\\MonsterEdit\\MonsterEdit\\bin\\Debug\\itemDB.accdb;"/>  <!--连接字符串-->

GetInstance(DatabaseType databaseType):传递数据库类型参数,并在Config文件中appSettings节点下添加连接字符串设置:

1 <add key="ConnectionString" value="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\\CSharp\\MonsterEdit\\MonsterEdit\\bin\\Debug\\itemDB.accdb;"/>     <!--连接字符串-->

GetInstance(DatabaseType databaseType,string connectString):传递数据库类型和连接字符串参数

GetInstance(string connectString):传递连接字符串参数,并在Config文件中appSettings节点下添加如下设置:

1 <add key="assemblyName" value="Higame.Database"/>  <!--程序集名称-->
2 <add key="typeName" value="Higame.Database.OledbOperator"/>  <!--数据库操作类名-->

调用操作如下:

var db = DbOperator.GetInstance(DatabaseType.Access);

感觉整个类库中还有很多地方实现的不是很好。耦合度还是很高,希望各位前辈们不吝赐教。

另附实现源代码:数据库操作类库

作者:zwffff悟道人生 – 博客园
出处:http://zwffff.cnblogs.com/
文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]ASP.NET用户个性化设置Profile——配置1

mikel阅读(996)

[转载]ASP.NET用户个性化设置Profile——配置1 – 彬 – 博客园.

ASP.NET 2.0除了提供了Membership身份验证机制之外,还提供了一种用户个性化设置的机制,称为Profile。Profile可以和 Membership结合起来,存储用户自定义数据。在Profile中可以存储各种类型的数据,包括最基本的int、double类型,也可以是复杂的 用户自定义类型,比如购物车、用户信息等。

Profile和Session类似,都是用来存储用户自定义数据,但是二者也有本质的区别:

1.Session只能够短暂的保留用户数据(默认为20分钟),但是Profile可以持久的保存,因为二者的存储介质不同,Session 默认是存放在内存当中的,而Profile是存放在数据库中。虽然说Session可以通过配置使之保存在状态服务器或者数据库当中,但还是有很大的区 别。

2.Profile是强类型的,而Session仅仅是一个项集合而已。并且VS对Profile提供了智能感知的支持,使用方便。

下面来看看如何使用Profile,首先第一步和Membership一样,也是添加数据库支持,还是和Membership一样,如果之前按照前面说的添加过了,在这里直接开始第二步,就是配置。若没有,则参考前面的文章添加数据库支持。将下面代码复制入web.config的system.web节点之下:

<profile enabled=”true” defaultProvider=”SQLProvider”>
<providers>
<add name=”SQLProvider” type=”System.Web.Profile.SqlProfileProvider” connectionStringName=”ConnectionString” applicationName=”TestMembership”/>
</providers>
<properties>
<add name=”test” type=”String” allowAnonymous=”true”/>
</properties>
</profile>

这个配置大多数的属性和Membership中的含义是一样的,我们只是说明一下特有的属性。在这个配置当中有Providers和 Properties节点,其中Providers节点之前在配置Membership中了解过了,现在介绍一下properties节点:

在这个节点下,add标签的作用就是添加一个“属性”,name属性就是“属性”的名字,type就是“属性”的类型,这个类型可以是int、 double等简单数据类型,也可以是购物车类等用户自定义数据类型,不过需要注意的是,如果为“类”类型,则该类必须可以序列化,就是在类的上面添加 [Serializable]特性。allowAnonymous这个属性指定用户在匿名状态下,是否可以设置该属性。

好了,配置完毕,我们就可以在页面上使用Profile了,建立一个页面,在页面上添加一个button双击在button的click事件当中写下如下代码:

Profile.test = 123123;

这个Profile.test就是我们在前面properties节点下添加的“属性”,类型为string类型。

浏览这个页面,点击button,看下效果,哈!出错了!错误提示如下:

配置文件属性“test”允许匿名用户存储数据。这要求启用 AnonymousIdentification 功能。

这是因为ASP.NET默认情况下不允许匿名用户,很简单,我们简单的设置一下,允许使用匿名用户即可,还是在web.config中system.web节点下添加一段配置:

<anonymousIdentification enabled=”true”/>

ok,现在再来浏览下页面,点击button试试?没有任何反应?这就对了!

我们再次新建一个页面,在这个页面的page_load事件当中添加一句Response.write(Profile.test);

完事,我们先浏览第一个页面,点击button,执行了Profile.test=“123123”;这段代码,然后关闭浏览器,再打开第二个页面,可以看到,我们在第一个页面中存入的“123123”这个字符串,在第二个页面显示出来了。

原理很简单:当我们打开了anonymousIdentification功能后,ASP.NET会给每个用户分配一个AnonymousID,保存在Cookie中。如图:

这是Cookie中存储的内容,.ASPXANONYMOUS这是默认的Cookie名称,可以在web.config中的anonymousIdentification节中CookieName属性中设置自己的Cookie名字。

这是在调试状态下,得到的AnonymousID

这是数据库aspnet_Users表中存储的内容,注意UserName和IsAnonymous字段。

这是数据库中aspnet_Profile中的内容,可以看到,我们test属性中的内容为:123123,PropertyNames字段中 test:S:0:6的含义大家应该猜出来了吧!test是属性名,S代表的String类型,0:6代表PropertyValuesString字段 中的从第0位到第6位的内容。

这样,当该用户使用这台电脑再次访问网站网站便可以根据这个Cookie中的AnonymousID来识别出用户的身份,再通过AnonymousID从aspnet_Profile表中,便可以取出用户上一次保存的数据。

ok,以上便是匿名状态下的Profile。当然,如果我们不需要匿名,而要求用户登录后才能保存数据的话,那么就没上面这么麻烦了,直接设置allowAnonymous=”false”即可,不需要去添加anonymousIdentification 配置。这样,该属性只能在用户登录状态下访问,未登录去访问该属性就会报错。

本篇文章简单的介绍了下Profile在实际项目中的应用,大家可以照着试一下,先有一个感性认识,下篇继续,会有一个小的项目,来完整的演示一下Profile的应用。

[转载]Attribute(特性),怎么用才更好?

mikel阅读(1209)

[转载]Attribute(特性),怎么用才更好? – 金色海洋工作室 – 博客园.

前几年:
2008年的某一天,我坐火车去北京。硬卧上铺,一晚上就到北京了。爬到上铺之后发现,旁边上铺有一老兄抱着一个笔记本,一开始还以为是看电影呢,仔细一看才发现——老天呀,居然在写代码!

这老兄也太工作狂了,当时可是晚上九点多了呀。屏幕里的IDE和vs有一点像,但又不是。问过了之后才知道,原来是大名鼎鼎的java(具体叫 啥记不清楚了,好像是j2ee,对java相当的不熟,就是那个意思了)。遇到java高手了,不能错失良机,要问问心中的疑问。

于是我就问他“听说java都在用Hibernate,需要把一些信息记录在XML里?”。
老兄说“是呀,以前都这么用。”

“以前?怎么是以前?”

“因为用XML记录信息不方便。”

“那么现在呢?”
“现在用特性了,把需要的信息放在特性里面。”

……

后面又闲聊了一些。

2008年的事情,对话是记不准确了,大体的意思就是这样,一开始用XML,后来用特性。当时我就觉得,俺们.net程序员怎么总是拾人家的牙恵?

现在:

这几天看到了一些关于特性和实体类的文章,中心思想就是用特性记录一些想要记录的信息,用特性把实体类和XX联系起来。

我们先来看看这一篇,C#基础系列:实现自己的ORM(反射以及Attribute在ORM中的应用)
http://blog.csdn.net/RonoTian/archive/2008/09/08/2900714.aspx

给出的示例如下:

public class Person
{
private string _Name;
private int _Age;
private string _Sex;

[DataFieldAttribute(

name, nvarchar)]
public string Name
{
get { return this._Name; }
set { this._Name = value; }
}

[DataFieldAttribute(

age, int)]
public int Age
{
get { return this._Age; }
set { this._Age = value; }
}

[DataFieldAttribute(

sex, nvarchar)]
public string Sex
{
get { return this._Sex; }
set { this._Sex = value; }
}
}

这里用特性([DataFieldAttribute(“name”, “nvarchar”)])来保存字段名和字段类型,把实体类的属性和字段信息连系了起来。
再来看看这个,手把手教你写ORM(六)
http://www.cnblogs.com/alexander-lee/archive/2007/01/24/hbh-orm-06.html

class tb
{
private string _aaa;

[Param(

NChar,10)]
public string aaa
{
get { return _aaa; }
set { _aaa = value; }
}
private string _bbb;

[Param(

NChar, 10)]
public string bbb
{
get { return _bbb; }
set { _bbb = value; }
}
}

这里用特性([Param(“NChar”, 10)])来保存字段类型和字段大小,把实体类的属性和字段信息连系了起来。不同的人有不同的习惯,有不同的需求,所以对以特性的定义也就不一样了。

再来看一篇,ASP.NET MVC杂谈之:—步步打造表单验证框架[重排版](1)
http://www.cnblogs.com/leven/archive/2009/03/26/aspnetmvc_validate_01.html

public class Student{
[Range(
0, 100, “分数的范围必须在{0}和{1}之间.”)]
public int Source{ get; set; }
}

这里用特性([Range(0, 100, “分数的范围必须在{0}和{1}之间.”)])把属性和验证方式联系了起来。

再看一篇(最后一个了),利用Attribute实现的 MVC动态表单
http://www.cnblogs.com/dozer/archive/2010/08/05/DynamicForm.html

[MetadataType(typeof(MusicMetaData))]
public partial class Music
{ }

public class MusicMetaData
{
[DynamicForm(
Create, true, Edit, false, Order = 3)]
public bool IsDeleted { get; set; }

[DynamicForm(

Create, true, Edit, false, Order = 1)]
public bool IsExist { get; set; }

[DynamicForm(

Create, true, Edit, false, Order = 2, Type = 2)]
public string Content { get; set; }
}

这个把属性和添加表单里是否需要,表单里的排序,对应的控件连系了起来。

在18楼的回复里,为了能够应对权限的需求,又增加了“admin = true ,user = false ”来应对。

==============================

好了举了四篇文章里的例子,我们来综合分析一下。如果我觉得他们的做法都挺好,要把他们都吸收进来,那么实体类的定义会变成什么样子呢?

public class Person
{
private string _Name;
private int _Age;
private string _Sex;

[DataFieldAttribute(

name, nvarchar)]
[Param(
nvarchar, 10)]
[Range(
??, “必须填写姓名!”)]
[DynamicForm(
Create, true, Edit, true, Order = 1)]
public string Name
{
get { return this._Name; }
set { this._Name = value; }
}

[DataFieldAttribute(

age, int)]
[Param(
age, 4)]
[Range(
0, 130, “年龄的范围必须在{0}和{1}之间.”)]
[DynamicForm(
Create, true, Edit, true, Order = 2)]
public int Age
{
get { return this._Age; }
set { this._Age = value; }
}

[DataFieldAttribute(

sex, nvarchar)]
[Param(
sex, 1)]
[Range(??, “男或者女”)]
[DynamicForm(
Create, true, Edit, true, Order = 3)]
public string Sex
{
get { return this._Sex; }
set { this._Sex = value; }
}
}

不知道大家对于这样的实体类是不是能够接受?我是接受不了,呵呵。这是有原因的,不是说我不喜欢就不愿意接受,也不是说我是老顽固。

我觉得这么做设计违反了三个原则:最少获知、依赖倒置、单一职责。(在13楼也回复了)

原则不是挂在嘴边上的,是需要在实际里应用的!

1、最少获知:

实体类和字段信息、控件、表单、验证作对应,那么应该知道什么呢?

需要知道字段名、字段大小、字段类型,或者是添加表单是否需要,序号是多少;或者用什么规则来验证?

要知道的也太多了吧。需要什么信息,就增加一个Attribute来保存,是挺简单,但是越积越多,怎么维护呀?

这样就造成了一个问题,Attribute会越来越多,多了就不好维护。Attribute的增加或者改动,就意味着,实体类结构的变化,就是说你要修改你的代码了。

就其原因:违反了最少获知原则。知道的太多了,会累坏的。

2、依赖倒置:

就是要依赖抽象,而不是依赖具体实现。
把具体的信息放在Attribute里面保存,存放的就是一个具体的实现,而不是抽象。Attribute的种类是依据字段名、字段类型,或者是添加表单、修改表单这类的具体的东东,也不是针对抽象来做的。
记录的又是“name”,“nvarchar”,“10”这样的具体的信息,那么就更是具体,离抽象又远了一步。

3、单一职责:

字段名称有变化了,要修改实体类的定义(Attribute也算吧),Create里面要不要显示,需要改的实体类;序号变了,还要改;验证方式变了,还要改。

有一点风吹草动就要改实体类的定义,累不累呢?

============================

如何解决?

说了这么多,都是发现问题,提出问题。估计好多人也都能发现这些,这说这些没有什么意义,我是最反感只提出问题而不解决问题的人,所以我这里要提出我的解决问题的方法。

我不敢保证我的方法就是好方法,但是至少有一个方法,至少是我觉得还可以的方法。

我的做法是,定义一个“字段编号”,比如1000010,前四位是表编号,后三位是字段序号。

这个编号是不能修改的,确定下来就不能再变了。

那么他有什么用处呢?

字段编号没有含义,但是却可以代表很多,比如可以代表字段名、字段大小、字段类型(图1),可以表示表单需要哪些字段(图2,建立视图),可以代表验证方式(图3,建立视图),也可以代表权限(图4,建立视图)……

字段编号什么都可以代表,因为他就是一个编号。他是一个抽象的,本身并没有什么意义,但是却可以代表很多。

有了这个字段编号,实体类就好办了,只需要一个Attribute来保存这个字段编号就可以了,以后有任何的扩展需求,也不需要增加或者改动Attribute的数量和定义。

这样,实体类的定义就变成了

public class Person
{
private string _Name;
private int _Age;
private string _Sex;

[ColumnID(

2000020)]
public string Name
{
get { return this._Name; }
set { this._Name = value; }
}

[ColumnID(

2000040)]
public int Age
{
get { return this._Age; }
set { this._Age = value; }
}

[ColumnID(

2000030)]
public string Sex
{
get { return this._Sex; }
set { this._Sex = value; }
}
}

简洁多了吧。什么?你问,这个特性不易读看不出来是什么不好。这个嘛,属性名称是给程序员看的,而特性是给程序看的,只要不写错就可以。

符合三个规则:

最小获知,只需要知道字段编号。
依赖倒置,依赖字段编号,而不是具体的信息。
单一职责,字段名可以变化,字段大小也可以变,验证方式也可以变,只要字段编号不变,那么就不需要改实体类。

===========================================

图1

图2,这是一个视图

图3,这个也是视图,其实和上面的是一个视图,只是显示的字段不同

图4,这是一个表里的记录,角色、功能节点与字段编号的关系。

[转载]调试JavaScript 错误的解决方案

mikel阅读(1225)

[转载]调试JavaScript 错误的解决方案 – xugang – 博客园.

A  使用alert() 和document.write() 方法监视变量值

如果要中断代码的运行,监视变量的值,则使用alert() 方法;

如果需要查看的值很多,则使用document.write() 方法,避免反复单击“确定”按钮;

B  使用window.onerror 事件

当页面出现异常时,onerror 事件会在window 对象上触发。它能在一定程度上告诉开发者相关的错误信息。

示例:

<script type=text/JavaScript>
function myerror(_message,_url,_line)
{
alert(错误信息: + _message
+\n错误的URI: + _url
+\n错误的行数: + _line
);
return true; //屏蔽系统的事件
}
//绑定错误事件
window.onerror = myerror;
//触发错误示例:
window,onload = test;
</script>

注意:在IE 中,触发error 事件后,正常的代码会继续运行,所有的变量和数据都会保存下来,在其onerror 事件处理方法中可以正常访问到;而在Firefox 中,触发error 事件后,一切都结束,所有的变量和数据都将被销毁。

C  使用 try…catch 语句找错误

示例:

<script type=text/JavaScript>
try
{
alert(触发异常);
}
catch (_ex) //可以省略“_ex”参数
{
var err = 错误信息;
for (var i in _ex)
{
err += \n参数名: + i
+ \t参数值: + _ex[i];
}
alert(err); //打印错误
}
finally //finally 可以被省略…
{
alert(finally 总是会运行);
}
</script>

注意:try…catch 并不能很好的处理JavaScript 的语法错误

示例:

<script type=text/javascript>
try
{
alert(触发语法错误)); //多了半边“)”
}
catch (_ex) //可以省略_ex参数
{
var err = 错误信息;
for (var i in _ex)
{
err += \n参数名: + i
+ \t参数值: + _ex[i];
}
alert(err); //打印错误
}
</script>

该示例并没有进入catch 块中。

D  使用相关调试器

在IE 和Firefox 浏览器中,可以使用相关的调试器或插件对JavaScript 进行调试。

●  在Firefox 浏览器中,可以使用其自带的“错误控制台”。操作步骤如下:

打开Firefox 浏览器 → 在菜单条“工具”中 → 选择“错误控制台”即可。

在没有其他插件的情况下,其自带的“错误控制台”是一个非常不错的选择。

另外,在Firefox 浏览器中,还有一些很不错的调试器,如:Venkman、Firebug 等。

Venkman 调试器安装后,可以在Firefox 浏览器 → 在菜单条“工具”中 → 选择“JavaScript  Debugger ”命令启用;

Firebug 调试器安装后,可以在Firefox 浏览器 → 在菜单条“工具”中 → 选择“Firebug”→ 选择“打开 Firebug”即可;

●  在IE 浏览器中,可以使用 Microsoft  Script  Debugger 调试器

Microsoft  Script  Debugger 是微软随IE 4 一同发布的一个IE插件,可以从微软的官方网站上免费下载。

下载安装以后,必须将IE 浏览器的调试选项打开才能使用。操作步骤如下:

1>   打开IE 浏览器 → 选择菜单栏的“工具”→ “Internet 选项”命令 → “高级”选项卡 → 将“禁用脚本调试(Internet Explorer )”复选框中的勾去掉即可。

2>   当IE 浏览器正在浏览页面时,运行Microsoft  Script  Debugger 调试器工具即可进行调试。

在Microsoft  Script  Debugger 调试器的 Running  Document 面板中选择开启的页面文件(只读),然后按F9 可以设置断点调试。另外,其Command  Window 面板也是一个很有用的功能,它能在代码断点停止时,在其中输入变量名并回车,便可看到此时变量的值;Command  Window 面板甚至可以接受简单的JavaScript 命令。但Microsoft  Script  Debugger 调试器自身还存在一个bug 问题。

[转载]ASP.NET MVC2.0在Tab页中实现异步无刷新分页

mikel阅读(1125)

[转载]ASP.NET MVC2.0在Tab页中实现异步无刷新分页 – 海纳百川 – 博客园.

概述

很多地方都存在以Tab页来呈现数据的方式,比如网易、新浪、搜狐、QQ等知名的门户网站的首页,还有大家熟知的博客园首页,都是用了tab页来显示数 据。大家之所以喜欢用Tab,因为它能大大的增加显示数据的空间,能在固定的空间中显示更多的数据。分页也是为了方便数据的显示,在应用系统中必不可少。 这篇文章使用JQueryASP.NET MVC中使用Tab页,以及在Tab页中实现异步无刷新的分页功能。估计这个大家都会用得到的。

ASP.NET MVC中实现分页,在之前的一篇博文:ASP.NET MVC2右键菜单和简单分页中已经实现了。实现的方式很简单,在table下面加上一段<a/><a/><a/>…的html就行了。

先介绍一个JQuery插件:Tab,可以到http://jqueryui.com/download上下载。看下它自带一个例子的截图:

ddd

看下本文的成果:

效果图一

gggg

实现:

按照它的Demo,在ASP.NET MVC项目中引入js和css,以及JQuery。我在ASP.NET MVC的母板页中引入这些文件:

    <link href="http://www.cnblogs.com/Content/base/ui.all.css" rel="stylesheet" type="text/css" />
    <script src="http://www.cnblogs.com/Scripts/jquery-1.4.1.js" type="text/javascript"></script>
    <script src="http://www.cnblogs.com/Scripts/ui.core.js" type="text/javascript"></script>
    <script src="http://www.cnblogs.com/Scripts/ui.tabs.js" type="text/javascript"></script>

引入之后,参考它的Demo在MVC项目中View中使用Tab。 可以看到比tab自带的demo多来了一个getContentTab函数。他有两个参数,用于表示你要显示

哪个tab页的第几页数据。这里默认加载的时候,显示tabs-Shoes的第一页数据。

   <script type="text/javascript">
       $(document).ready(function () {
           $("#tabs").tabs();
           getContentTab('Shoes', 1);
       }); 
    </script>


     <div id="tabs">
        <ul>
      
            <li><a href="#tabs-Shoes" onclick="getContentTab('Shoes',1);">Shoes</a></li>
            <li><a href="#tabs-Electronics" onclick="getContentTab('Electronics',1);">Electronics</a></li>
            <li><a href="#tabs-Food" onclick="getContentTab('Food',1);">Food</a></li>
        </ul>
        <div id="tabs-Shoes">
            
        </div>
        <div id="tabs-Electronics">
            
        </div>
        <div id="tabs-Food">
            
        </div>
    </div> 

当然在定义View之前要先写好控制器的代码,很简单,基本上没有代码:

public ActionResult ViewCategory()
{
    return View();
}

好了,下面开始我们重要的几步。显示table以及实现table的分页。这个demo的tab定义了三个tab页,每一页的table结构是一样的,所以我定义一个用户控件来实现table和分页。代码如下:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcAjaxPaging.Models.ProductViewModel>" %>
<%@ Import Namespace="MvcAjaxPaging.HtmlHelpers"%>
     <table class="grid">
        <thead>
            <tr>
                <th>Product name</th>
                <th>Category</th>
            </tr>
        </thead>
        <tbody>
            <% foreach (var product in ViewData.Model.Products) { %>
                <tr>
                    <td><%= product.Name %></td>
                    <td><%= product.Category %></td>
                </tr>
            <% } %>
        </tbody>
    </table>
        <div class="pager">
        <%= Html.Pager(ViewData.Model.PagingInfo.CurrentPage, ViewData.Model.PagingInfo.ItemsPerPage, ViewData.Model.PagingInfo.TotalItems, "", ViewData["CategoryDisplayName"] as string)%>
    </div>

我们再通过一个ajax调用来将这个控件显示在ViewCategory对应的View上,定义一个js函数:

function getContentTab(categoryName, page) {

    var url = '<%= Url.Content("~/MyPaging/ViewByCategory/") %>' + categoryName + "/"  + page;
    var targetDiv = "#tabs-" + categoryName;
    $.get(url, null, function (result) {
        $(targetDiv).html(result);
    });
}

我们看上面代码,我们去请求服务端的ViewByCategory方法,获取table中的数据。看ViewByCategory的代码:

public ActionResult ViewByCategory(string categoryName, int? page)
{
    categoryName = categoryName ?? this.categories[0];
    int currentPageIndex = page.HasValue ? page.Value : 0;
    ProductViewModel productViewModel = new ProductViewModel();
    
    IList<Product> productsByCategory = this.allProducts.Where(p => p.Category.Equals(categoryName)).ToList();
    productViewModel.Products = productsByCategory.Skip((currentPageIndex - 1) * 10).Take(10).ToList();
    productViewModel.PagingInfo.CurrentPage = currentPageIndex;
    productViewModel.PagingInfo.ItemsPerPage = 10;
    productViewModel.PagingInfo.TotalItems = productsByCategory.Count;
    return View("ViewByCategoryTable", productViewModel);
}

为了简单起见数据来来自内存,使用list的take来实现分页。你可以很方便的改成从DB获取数据。在看下如何生成分页的html,其实很简单,我们只要在生成的分页的HTML中使用getContentTab函数就行了。

public static string Pager(this HtmlHelper helper, int currentPage, int currentPageSize, int totalRecords,string urlPrefix,string status)
{
    StringBuilder sb1 = new StringBuilder();

    int seed = currentPage % currentPageSize == 0 ? currentPage : currentPage - (currentPage % currentPageSize);

    if (currentPage > 0)
        sb1.AppendLine(String.Format("<a href='#' onclick=getContentTab(\"{0}\",\"{1}\") >Previous</a>", status, currentPage));

    if (currentPage - currentPageSize >= 0)
        sb1.AppendLine(String.Format("<a href='#' onclick=getContentTab(\"{0}\",\"{1}\") >...</a>", status, (currentPage - currentPageSize) + 1));

    for (int i = seed; i < Math.Round((totalRecords / currentPageSize) + 0.5) && i < seed + currentPageSize; i++)
    {
        sb1.AppendLine(String.Format("<a href='#' onclick=getContentTab(\"{0}\",\"{1}\") >{1}</a>", status, i + 1));
    }

    if (currentPage + currentPageSize <= (Math.Round((totalRecords / currentPageSize) + 0.5) - 1))
        sb1.AppendLine(String.Format("<a href='#' onclick=getContentTab(\"{0}\",\"{1}\") >...</a>", status, (currentPage + currentPageSize) + 1));

    if (currentPage < (Math.Round((totalRecords / currentPageSize) + 0.5) - 1))
        sb1.AppendLine(String.Format("<a href='#'  onclick=getContentTab(\"{0}\",\"{1}\") >Next</a>", status, currentPage + 2));

    return sb1.ToString();
}

效果:

效果图二:

jjjj

图三:

kkkk

总结:ASP.NET MVC中实现了在tab页中的异步无刷新分页。这东西太常用了,放在这里,希望对你有所帮助。

[转载]ASP.NET MVC2.0 基础教程 实现CRUD操作

mikel阅读(991)

[转载]ASP.NET MVC2.0 基础教程 实现CRUD操作 – 将夏♂未泯‰ – 博客园.

这个演示项目实现了CRUD增删查改操作。

开发环境:Visual Studio 2010 SP1 + Linq To SQLSQL Server 2005

一、Demo演示

1、下载本演示Demo源码:http://files.cnblogs.com/rubyloveromantic/MyMikeDemo.rar

2、Visual Studio 2010打开本演示项目,运行即可。

二、从头开始建立这个演示项目

1、附加下载的Demo源码中的数据库:mike108mvp.mdf 【ok】
2、新建一个MVC Preview应用程序,在Models目录下,添加一个Linq To SQL文件:Mike108mvp.dbml,将数据库中的User表拖入LinqToSql设计窗口中。【ok】
3、web.config中添加命名空间:【ok】
4、在相关目录创建视图文件【ok】


5、Controllers/Users/目录下,新建一个UsersController.cs文件,输入增删查改代码。【ok】

usersControl

public class UsersController : Controller { Mike108mvpDataContext db = new Mike108mvpDataContext(); #region CRUD操作 public ActionResult List() { List<User> model = db.User.ToList(); return View(model); } public ActionResult Create() { return View(); } [HttpPost] public ActionResult Add() { User model = new User(); model.UserId = 0; UpdateModel(model, Request.Form.AllKeys); db.User.InsertOnSubmit(model); db.SubmitChanges(); return RedirectToAction("List"); } public ActionResult Details(int userID) { User model = db.User.FirstOrDefault(e => e.UserId == userID); return View(model); } public ActionResult Edit(int userID) { User model = db.User.FirstOrDefault(e => e.UserId == userID); return View(model); } [HttpPost] public ActionResult Edit(int userID, string parm) { if (parm == "更新") { User model = db.User.FirstOrDefault(e => e.UserId == userID); UpdateModel(model, Request.Form.AllKeys); db.SubmitChanges(); } return RedirectToAction("List"); } public ActionResult Delete(int userID) { User model = db.User.FirstOrDefault(e => e.UserId == userID); db.User.DeleteOnSubmit(model); db.SubmitChanges(); return RedirectToAction("List"); } #endregion #region 数据重置 public ActionResult DataReset() { //批量删除 List<User> oldList = db.User.ToList(); db.User.DeleteAllOnSubmit(oldList); //批量添加 List<User> newlist = new List<User> { new User { UserName = "孙中山", Age = 53, Career = "中国总统" }, new User { UserName = "蒋中正", Age = 36, Career = "中国总统" }, new User { UserName = "小布什", Age = 61, Career = "美国总统" }, new User { UserName = "列宁", Age = 49, Career = "邪恶苏联" }, new User { UserName = "斯大林", Age = 49, Career = "邪恶苏联" }, new User { UserName = "比尔盖茨", Age = 62, Career = "微软老大" }, new User { UserName = "Scott Guthrie", Age = 33, Career = "ASP.NET Leader" } }; db.User.InsertAllOnSubmit(newlist); db.SubmitChanges(); return RedirectToAction("List"); } #endregion }

注:该教程是在学习Mike In Jesus课程是对他的代码作的升级(to MVC2.0)。 无侵权之意。

[转载]介绍一款Memcached服务器软件:NorthScale Memcached Server

mikel阅读(937)

[转载]介绍一款Memcached服务器软件:NorthScale Memcached Server – dudu – 博客园.

今天在找Windows上的64位Memcached软件时,发现了NorthScale Memcached Server

这是由NorthScale公司开发的一个Memcached发行版。

NorthScale公司是由Memcached开源项目的负责人在2009年创建的,总部在加利福尼亚州山景城(Google总部也在这里),创建时获得了500万美元的风险投资。

NorthScale Memcached Server的第一个客户是美国当前最知名的社交网络游戏开发商Zynga Game Network,详见NorthScale Memcached Server Speeds Farmville

Windows平台终于有了正宗的Memcached服务器软件。唯一欠缺的就是没有好 的.NET客户端,NorthScale推荐的客户端是Enyim Memcached Client,但官方网站memcached.enyim.com却无法访问。博客园现在用的就是Enyim Memcached Client,当时在codeplex.com下载的,现在codeplex.com上已经不存在这个项目。

下载安装了NorthScale Memcached Server,安装之后通过http://localhost:8080/就可以访问管理控制台:

可以很方便地监控Memcached的运行情况:

由于是刚发现这个软件,还不熟悉,介绍就到这里,感兴趣的朋友可以研究一下,也期待你分享自己的心得。

相关资源:

官方网站http://www.northscale.com/

官方下载:http://www.northscale.com/download.php?a=d

官方博客:http://blog.northscale.com/

介绍文档:NorthScale_MCDS_Datasheet.pdf

[转载]SQL Server 动态SQL

mikel阅读(1118)

[转载]SQL Server 动态SQL – 潇湘隐者 – 博客园.

动态SQL:code that is executed dynamically。它一般是根据用户输入或外部条件动态组合的SQL语句块。动态SQL能灵活的发挥SQL强大的功能、方便的解决一些其它方法难以 解决的问题。相信使用过动态SQL的人都能体会到它带来的便利,然而动态SQL有时候在执行性能(效率)上面不如静态SQL,而且使用不恰当,往往会在安 全方面存在隐患(SQL 注入式攻击)。
动态SQL可以通过EXECUTE 或SP_EXECUTESQL这两种方式来执行。(来自MSDN)

EXECUTE

执 行 Transact-SQL 批中的命令字符串、字符串或执行下列模块之一:系统存储过程、用户定义存储过程、标量值用户定义函数或扩展存储过程。SQL Server 2005 扩展了 EXECUTE 语句,以使其可用于向链接服务器发送传递命令。此外,还可以显式设置执行字符串或命令的上下文

SP_EXECUTESQL

执 行可以多次重复使用或动态生成的 Transact-SQL 语句或批处理。Transact-SQL 语句或批处理可以包含嵌入参数。在批处理、名称作用域和数据库上下文方面,SP_EXECUTESQL 与 EXECUTE 的行为相同。SP_EXECUTESQL stmt 参数中的 Transact-SQL 语句或批处理在执行 SP_EXECUTESQL 语句时才编译。随后,将编译 stmt 中的内容,并将其作为执行计划运行。该执行计划独立于名为 SP_EXECUTESQL 的批处理的执行计划。SP_EXECUTESQL 批处理不能引用调用 SP_EXECUTESQL 的批处理中声明的变量。SP_EXECUTESQL 批处理中的本地游标或变量对调用 SP_EXECUTESQL 的批处理是不可见的。对数据库上下文所作的更改只在 SP_EXECUTESQL 语句结束前有效。

如果只更改了语句中的参数值,则 sp_executesql 可用来代替存储过程多次执行 Transact-SQL 语句。因为 Transact-SQL 语句本身保持不变,仅参数值发生变化,所以 SQL Server 查询优化器可能重复使用首次执行时所生成的执行计划。

一般来说,我们推荐、优先使用SP_EXECUTESQL来执 行动态SQL,一方面它更加灵活、可以有输入输出参数、另外一方面,查询优化器更有可能重复使用执行计划,提高执行效率。还有就是使用 SP_EXECUTESQL能提高安全性;当然也不是说要完全摈弃EXECUTE,在特定场合下,EXECUTE比SP_EXECUTESQL更方便些, 比如动态SQL字符串是VARCHAR类型、不是NVARCHAR类型。SP_EXECUTESQL 只能执行是Unicode的字符串或是可以隐式转换为ntext的常量或变量、而EXECUTE则两种类型的字符串都能执行。

下面我们来对比看看EXECUTE 和SP_EXECUTESQL的一些细节地方。


EXECUTE (NSELECT * FROM Groups) 执行成功
EXECUTE (SELECT * FROM Groups) 执行成功

SP_EXECUTESQL N
SELECT * FROM Groups; 执行成功
SP_EXECUTESQL SELECT * FROM Groups 执行出错

Summary:EXECUTE 可以执行非Unicode或Unicode类型的字符串常量、变量。而SP_EXECUTESQL只能执行Unicode或可以隐式转换为ntext的字符串常量、变量。

代码

DECLARE @GroupName VARCHAR(50);

SET @GroupName = SuperAdmin;

EXECUTE (SELECT * FROM Groups WHERE GroupName=”’ + SUBSTRING(@GroupName, 1,5) + ””); ‘SUBSTRING’ 附近有语法错误。

DECLARE @Sql VARCHAR(200);
DECLARE @GroupName VARCHAR(50);

SET @GroupName = SuperAdmin;
SET @Sql = SELECT * FROM Groups WHERE GroupName=”’ + SUBSTRING(@GroupName, 1,5) + ””
PRINT @Sql;
EXECUTE (@Sql);

Summary:EXECUTE 括号里面只能是字符串变量、字符串常量、或它们的连接组合,不能调用其它一些函数、存储过程等。 如果要使用,则使用变量组合,如上所示。

代码

DECLARE @Sql VARCHAR(200);
DECLARE @GroupName VARCHAR(50);

SET @GroupName = SuperAdmin;
SET @Sql = SELECT * FROM Groups WHERE GroupName=@GroupName
PRINT @Sql;
EXECUTE (@Sql); 出错:必须声明标量变量 “@GroupName”。

SET @Sql = SELECT * FROM Groups WHERE GroupName= + QUOTENAME(@GroupName, ””)
EXECUTE (@Sql); 正确:

DECLARE @Sql NVARCHAR(200);
DECLARE @GroupName NVARCHAR(50);

SET @GroupName = SuperAdmin;
SET @Sql = SELECT * FROM Groups WHERE GroupName=@GroupName
PRINT @Sql;
EXEC SP_EXECUTESQL @Sql, N@GroupName NVARCHAR,@GroupName
查询出来没有结果,没有声明参数长度。

DECLARE @Sql NVARCHAR(200);
DECLARE @GroupName NVARCHAR(50);

SET @GroupName = SuperAdmin;
SET @Sql = SELECT * FROM Groups WHERE GroupName=@GroupName
PRINT @Sql;
EXEC SP_EXECUTESQL @Sql, N@GroupName NVARCHAR(50),@GroupName

Summary:动态批处理不能访问定义在批处理里的局部变量 。 SP_EXECUTESQL 可以有输入输出参数,比EXECUTE灵活。

下面我们来看看EXECUTE , SP_EXECUTESQL的执行效率,首先把缓存清除执行计划,然后改变用@GroupName值SuperAdmin、CommonUser、CommonAdmin分别执行三次。然后看看其使用缓存的信息

代码

DBCC FREEPROCCACHE;

DECLARE @Sql VARCHAR(200);
DECLARE @GroupName VARCHAR(50);

SET @GroupName = SuperAdmin; ‘CommonUser’, ‘CommonAdmin’
SET @Sql = SELECT * FROM Groups WHERE GroupName= + QUOTENAME(@GroupName, ””)
EXECUTE (@Sql);

SELECT cacheobjtype, objtype, usecounts, sql
FROM sys.syscacheobjects
WHERE sql NOT LIKE %cache%
AND sql NOT LIKE %sys.%;

如下图所示

依葫芦画瓢,接着我们看看SP_EXECUTESQL的执行效率.

代码

DBCC FREEPROCCACHE;

DECLARE @Sql NVARCHAR(200);
DECLARE @GroupName NVARCHAR(50);

SET @GroupName = SuperAdmin; ‘CommonUser’, ‘CommonAdmin’
SET @Sql = SELECT * FROM Groups WHERE GroupName=@GroupName
EXECUTE SP_EXECUTESQL @Sql, N@GroupName NVARCHAR(50), @GroupName;

SELECT cacheobjtype, objtype, usecounts, sql
FROM sys.syscacheobjects
WHERE sql NOT LIKE %cache%
AND sql NOT LIKE %sys.%;

执行结果如下图所示:

Summary:EXEC 生成了三个独立的 ad hoc 执行计划,而用SP_EXECUTESQL只生成了一次执行计划,重复使用了三次,试想如果一个库里面,有许多这样类似的动态SQL,而且频繁执行,如果采用SP_EXECUTESQL就能提高性能。