[JavaScript]JavaScript刷新页面方法

mikel阅读(831)

1.无提示刷新网页

大家有没有发现,有些网页,刷新的时候,会弹出一个提示窗口,点“确定”才会刷新。

而有的页面不会提示,不弹出提示窗口,直接就刷新了.

如果页面没有form,则不会弹出提示窗口。如果页面有form表单,

a)< form method="post" …> 会弹出提示窗口

b)< form method="get" …> 不会弹出

2.javascript刷新页面的方法

window.location.reload();

使用window.open()弹出的弹出窗口,刷新父窗口

window.opener.location.reload()

使用window.showDialog弹出的模式窗口

window.dialogArguments.location.reload();

3.javascript弹出窗口代码

下面给两个弹出屏幕居中窗口的例子

window.open()方式

function ShowDialog(url) {
            var iWidth=300; //窗口宽度
            var iHeight=200;//窗口高度
            var iTop=(window.screen.height-iHeight)/2;
            var iLeft=(window.screen.width-iWidth)/2;
            window.open(
            url,"Detail","Scrollbars=no,Toolbar=no,Location=no,Direction=no,Resizeable=no,
            Width="+iWidth+" ,Height="+iHeight+",top="+iTop+",left="+iLeft
            );
           }

window.showModalDialog方式

function ShowDialog(url) {
            var iWidth=300; //窗口宽度
            var iHeight=200;//窗口高度
            var iTop=(window.screen.height-iHeight)/2;
            var iLeft=(window.screen.width-iWidth)/2;
            window.showModalDialog(
            url,window,"dialogHeight: "+iHeight+"px;dialogWidth: "+iWidth+"px;
            dialogTop: "+iTop+"; dialogLeft: "+iLeft+"; resizable: no; status: no;scroll:no"
);
          }

注意这里的第二个参数,window

4.模式窗口数据不刷新(缓存)问题

在jsp页面加入如下语句

<%
       response.setHeader("Pragma","No-Cache");
       response.setHeader("Cache-Control","No-Cache");
       response.setDateHeader("Expires", 0);
%>

5.模式窗口中,链接弹出新窗口问题

在< /head >和< body >间加入< base target="_self" >

6.无提示关闭页面的方法

function CloseWin(){
     var ua = navigator.userAgent; var ie = navigator.appName==
"Microsoft Internet Explorer"?true:false;
     if(ie){
var IEversion = parseFloat(ua.substring(ua.indexOf("MSIE ")+5,
ua.indexOf(";",ua.indexOf("MSIE "))));
     if( IEversion< 5.5){
     var str = '';
     document.body.insertAdjacentHTML("beforeEnd", str);
     document.all.noTipClose.Click();
    } else {
     window.opener =null; window.close();
    }
   }else{
   window.close()
   }
}

[SQL]TSQL查询内幕::(2.3)查询计划与更新计划

mikel阅读(907)

显示计划(Showplan)是表示由查询优化器生成的文本、图形或XML格式的查询计划的术语。他包含了有关SQL Server如何处理查询的信息,对查询计划中的每个表,显示计划可以告诉你是否使用了索引,或者是否有必要执行表扫描,以及不同操作的执行顺序。

在本系列随笔的2.1中对一个显示计划做了初步的分析。

 

SQL Server2005可以生成三种不同格式的显示计划:图形、文本和XML

在计划内容方面,SQL Server可以生成只包含运算符的计划,包含估计成本的计划,以及包含运行时信息的计划。下表列出了生成不同格式计划的命令:

 

内容

格式

文本

XML

图形

运算符

SET SHOWPLAN_TEXT ON

N/A

N/A

运算符和估计成本

SET SHOWPLAN_ALL ON

SET SHOWPLAN_XML ON

在企业管理器中“显示估计的执行计划”

运行时信息

SET STATISTICS PROFILE ON

SET STATISTICS XML ON

在企业管理器中“包含实际的执行计划”

 

首先看一个简单的查询:

SET NOCOUNT ON;

USE Northwind;

GO

SET SHOWPLAN_TEXT ON;

GO

Select ProductName, Products.ProductID

FROM dbo.[Order Details]

 JOIN dbo.Products

    ON [Order Details].ProductID = Products.ProductID

Where Products.UnitPrice > 100;

GO

SET SHOWPLAN_TEXT OFF;

GO

 

运行结果:

StmtText

——————————————————————————————————————————————————————————–

Select ProductName, Products.ProductID

FROM dbo.[Order Details]

 JOIN dbo.Products

    ON [Order Details].ProductID = Products.ProductID

Where Products.UnitPrice > 100;

 

StmtText

——————————————————————————————————————————————————————————————–

 |–Nested Loops(Inner Join, OUTER REFERENCES:([Northwind].[dbo].[Products].[ProductID]))

       |–Clustered Index Scan(OBJECT:([Northwind].[dbo].[Products].[PK_Products]), Where:([Northwind].[dbo].[Products].[UnitPrice]>($100.0000)))

       |–Index Seek(OBJECT:([Northwind].[dbo].[Order Details].[ProductID]), SEEK:([Northwind].[dbo].[Order Details].[ProductID]=[Northwind].[dbo].[Products].[ProductID]) orDERED FORWARD)

 

输出结果表明:该查询由三个运算符组成:Nested LoopsClustered Index ScanIndex Seek

 

首先看第一句:

|–Nested Loops(Inner Join, OUTER REFERENCES:([Northwind].[dbo].[Products].[ProductID]))

Nested Loops对两个表进行内部联接,且外部表为Products表。

然后是:

|–Clustered Index Scan(OBJECT:([Northwind].[dbo].[Products].[PK_Products]), Where:([Northwind].[dbo].[Products].[UnitPrice]>($100.0000)))

SQL Server使用Clustered Index Scan访问物理数据,这里扫描聚集索引相当于描述整个表。

最后是:

|–Index Seek(OBJECT:([Northwind].[dbo].[Order Details].[ProductID]), SEEK:([Northwind].[dbo].[Order Details].[ProductID]=[Northwind].[dbo].[Products].[ProductID]) orDERED FORWARD)

SQL Server使用Index Seek访问索引行,其中的Object显示了索引的完整名称。Seek是查找谓词,这里是要根据外部表的ProductID来进行索引查找。

 

当执行计划时,数据的传递通常是从右到左,从上到下的。缩进多的运算符生成行供缩进少的运算符使用。在这里,Clustered Index Scan运算符和Index Seek运算符是缩进最多的,且Clustered Index ScanIndex Seek的上面,所以先运算Clustered Index Scan,然后是Index Seek,最后是Nested Loops

 

运行示例还可以注意到,当使用了SET SHOWPLAN_TEXT ON后,会阻止执行查询。

 

XML格式的显示计划

有两种格式的显示计划。一个是SET SHOWPLAN_XML ON,他将包括估计的执行计划。另一个是SET STATISTICS XML ON,他包含运行时的信息。

 

生成XML格式的显示计划可以通过以下方法:

1. 上面写的这两个指令,其中SET SHOWPLAN_XML ON在编译批处理时生成,它为整个批处理生成一个XML文档;而SET STATISTICS XML ON在运行时产生输出,他为批处理中的每个语句生成单独的XML文档。

2. 使用企业管理器的“显示图形化显示计划”。

3. 使用SQL Server Profiler

 

名为Showplanxml.xsdXML Schema描述了包括编译时估计、运行时的XML显示计划。在运行时,XML显示计划提供了一些额外的信息。这个文件在安装完SQL Server 2005后,被放置在Microsoft SQL Server"90"tools"Binn"schemas"SQLServer"2004"07"showplan目录下。

 

XML格式的显示计划的内容最详细,包含了计划大小(CachePlanSize属性),和优化该计划是用到的参数值(ParameterList元素),而且只有运行时XML显示计划才包含并行计划中不同线程所处理的行数(RunTimeCountersPerThread元素的ActualRows属性),以及执行查询时的实际平行度(DegreeOfParallelism属性)

 

 

图形化的显示计划

在企业管理器中,有“显示估计的执行计划”和“包括实际的执行计划”两种图形化的显示方法。

显示估计的执行计划:点选后,在结果窗口会立刻显示图形化执行计划。

包含实际的执行计划:点选后,不会立刻显示计划,而是在点击执行后,将实际的计划结果显示出来。

 

无论使用上面哪种方式图形显示计划,都会显示下面的图形:

但可以想到他们之间存在的区别,当查看运算符的详细信息时:

未标题-2.png未标题-2.png

因为“包含实际的执行计划”,所以会给出实际运行时的一些信息。

右键->计划另存为,还可以将显示计划保存为XML显示计划。扩展名为sqlplan

 

显示计划中的运行时信息

SET STATISTICS XML ON|OFF

XML显示计划包含两种运行时信息:每个SQL语句的信息和每个线程的信息。如果语句有参数,它的计划将包含ParameterRuntimeValue属性,表示该语句被执行时每个参数的值。它可能不同于编译该语句时用到的值(ParameterCompiledValue属性),但只有优化器在优化并知道该参数值时,该属性才会出现在计划中,且只与传递到存储过程的参数有关。

 

DegreeOfParallelism属性表示此次执行的实际平行度(DOP,它是单个查询的并发线程数)。它可能与编译时计算的值不同,在编译期间,查询优化器假设为当时的工作负荷为CPU的一半。在执行时,DOP的值会根据执行时开始的工作负荷被调整。如果执行并行计划时DOP=1,当创建执行上下文时,SQL Server将从查询计划中移除Exchange运算符。MemoryGrant属性表示以KB为单位的用于执行该查询的实际内存。SQL Server使用这些内存为哈希联接(hash Join)生成哈希表或在内存中执行排序。

 

RunTimeCountersPerThread元素包含5个属性,每个线程都有相应的值:ActualRebindsActualRewindsActualRowsActualEndofScansActualExecutionsActualExecutions值告诉我们该运算符在每个线程中被初始化的次数。如果运算符是一个扫描运算符,ActualEndofScans表示扫描到达数据集结尾的次数。所以用ActualExecutions-ActualEndofScans就可以得到运算符没有扫描的次数。如:如果Select中使用TOP限定了返回的行数,则输出集合将在扫描到达表的结尾之前被收集。

 

SET STATISTICS PROFILE ON

这个指令返回的计划与SET SHOWPLAN_ALL ON相比有两个区别。他在输出中包含了另外的两列:RowsExecutes

Rows :是所有线程的RunTimeCountersPerThread元素RowCount属性的合计,他告诉我们每个运算符实际返回的行数。

Executes:是该元素中ActualExecutions属性的合计,他告诉我们SQL Server为处理一行或多行而初始化该运算符的次数。

 

当检查某个查询计划时,可以找到查询优化器的估计行数与实际行数之间的最大差异。EstimateRows列是每次执行所估计的输出行数,而Rows是运算符所有执行返回的累积行数。因此我们可以先把EstimateRows乘以EstimateExcutions,在把它与SET STATISTICS PROFILE输出的Roes列中返回的实际总行数做比较。

 

SQL跟踪捕获显示计划

使用跟踪来捕获显示计划是非常精确的,这样可以避免在企业管理器中观察到的计划和在应用程序执行起来使用的实际计划之间产生的差异。最常见的,如:用不同的参数调用同一个存储过程、统计信息被自动更新、在编译和运行之间的可用资源(CPU或内存)发生变化。

使用跟踪进行监视非常消耗资源,监视的事件越多,影响越严重。

下表显示了9类性能事件:

跟踪事件类

编译或运行

是否包含运行时信息

是否包含XML显示计划

是否为SQL Server 2000生成跟踪

Showplan ALL

运行

Showplan All for Query Compile

编译

Showplan Statistics Profile

运行

Showplan Text

运行

Showplan Text(未编码)

运行

Showplan XML

运行

Showplan XML for Query Compile

编译

Showplan XML Statistics Profile

运行

Performance Statistics

编译和运行

 

如果在开发或调试中,应该使用Showplan XML Statistics Profile事件。它生成所有的查询计划和运行时信息。

 

即使你的服务器很忙,如果设计了有良好的计划重用率的查询,因为编译率较低,也可以使用Showplan XML For Query Compile事件。他只在有存储过程或语句被编译或重新编译时才生成跟踪记录,但不包括运行时信息。

 

通过为各个列设置筛选值可以减少跟踪的大小。在设置跟踪筛选器时,只有ApplicationNameClientProcessIDHostNameLogionNameLogionSidNTDomainNameNTUserNameSPID这些列上应用筛选器会抑制时间的生成。其他筛选器只有在时间被生成并到达客户端后才会应用,所以并不能减少服务器的开销,事实上会造成更多的开销。

 

另外,相对于在企业管理器中使用显示计划,显示计划跟踪事件进一步扩大了SQL Server所能捕获计划的语句集合。如:CreateInsert INTO … EXEC语句等。

 

从过程缓存中提取显示计划

上面已经介绍,SQL Server当生成计划后,会把它保存到过程缓存之中。我们可以用几个动态管理视图和函数、DBCC PROCCACHE、以及目录视图sys.syscacheobjects来检查过程缓存。

 

Sys.dm_exec_query_plan (DMF)XML格式返回位于过程缓存的计划。DMF要求一个计划句柄作为唯一的参数。计划句柄是一个VARBINARY(64)类型的查询计划标识符,DMV为当前过程缓存中每个查询都可以返回该标识符。

看一个示例:

Select qplan.query_plan AS [Query Plan]

FROM sys.dm_exec_query_stats AS qstats

 CROSS APPLY sys.dm_exec_query_plan(qstats.plan_handle) AS qplan;

这个查询为所有缓存的查询计划返回XML显示计划。

 

但是想这样找到某个查询计划非常困难,因为查询文本被包含在XML显示计划内部。下面的查询使用Xquery value方法从显示计划中提取出序列号(No)和查询文本(Statement Text)。因为每个批处理都有sql_handle,所以用ORDER BY sql_handle可以按这些语句在批处理中的顺序进行排序显示。

WITH XMLNAMESPACES ('http://schemas.microsoft.com/SQLServer/2004/07/showplan' AS sql)

Select

 C.value('@StatementId', 'INT') AS [No],

 C.value('(./@StatementText)', 'NVARCHAR(MAX)') AS [Statement Text],

 qplan.query_plan AS [Query Plan]

FROM (Select DISTINCT plan_handle FROM sys.dm_exec_query_stats) AS qstats

 CROSS APPLY sys.dm_exec_query_plan(qstats.plan_handle) AS qplan

 CROSS APPLY query_plan.nodes('/sql:ShowPlanXML/sql:BatchSequence/sql:Batch/ sql:Statements/ descendant::*[attribute::StatementText]') AS T(C)

ORDER BY plan_handle, [No];

运行结果:

 

更新计划

当优化InsertUpdateDelete这些数据修改语句时,优化器必须要处理几个特殊的问题。IUD计划(InsertUpdateDelete)包括两个阶段。

 

第一个阶段:通过生成用于描述数据更改的数据流来确定哪些行将被IUD对于Insert,数据流包含列值,对于Delete,它包含表键,对于Update,他既包含表键也包含被修改的列的值。

 

第二个阶段,把数据流中的描述的更改应用到表,通过执行约束验证保证数据完整性,它维护非聚集索引和索引视图,如果存在触发器则引发触发器。

UpdateDelete查询计划通常还包含两个对目标表的引用:第一个引用用于标识受影响的行,第二个引用执行更改的地方。Insert计划只包含一个对目标表的引用。

在一些简单的情况是,SQL ServerIUD计划的两个阶段合并在一起。如:把值直接插入表,成为标量插入,或者更新/删除由目标表主键标识的行。

 

如果SQL Server需要执行约束验证,则在第二个阶段会自动包含Assert运算符。SQL Server通过在受影响的行和列上计算一个通常成本较低的标量表达式为InsertUpdate验证CHECK约束。

 

对包含外键约束的表执行InsertUpdate会强制验证CHKECK约束,而且对包含被引用的表所执行的InsertUpdate也会强制验证外键约束。为验证约束,即使不是IUD操作目标的相关表也被扫描。声明主键将自动地在该列创建唯一的索引,但外键不一样,对被引用的外键列执行UpdateDelete必须为每个被更新或删除的主键值访问外键表。如果这个约束是一个级联引用完整性约束,那么将会执行更改,否则将验证被删除的键是否存在。因此,要对键值执行Update或对主表执行Delete,那么应该确保外键上存在索引。

 

在处理InsertDelete语句时,除了在聚集索引或堆上执行IUD操作,还会维护所有非聚集索引,Update查 询还维护包含被修改列的索引。因为非聚集索引包含聚集索引键和分区键以允许高效地访问数据行,所以更新那些参与聚集索引键或分区键的列成本很高,因为它会 修改或重建所有索引。更新分区键还会导致行在分区之间的移动。因此,如果可能的话,应该选择不更新的列作为聚集键或分区键。

 

总的来说,IUD语句的性能与包含目标列的索引数量密切相关,因为他们将会被重建或修改。对索引执行单行InsertDelete操作要求遍历一次索引树。SQL Server更新索引键和分区键的方法是先执行Delete再执行Insert,所以在索引操作上,Update的成本比InsertDelete要多一倍。

 

查询优化器执行IUD语句时有两种不同的策略:每行维护和每索引维护。

首先使用这个文件准备参考数据:3.rar

然后来看两个查询

查询1

Delete FROM dbo.Orders Where orderDate = '2002-01-01'

更新计划:

这就是一个每行查询的例子,SQL Server为该查询所影响的每一行同时维护索引和基表(基表=堆或聚集索引),并且对所有非聚集索引的更新将与对基表中每一行的更新同时执行。这个查询计划没有对第2个索引执行任何删除操作,因为这些工作在聚集索引删除运算符的计算是同时完成。

 

查询2

Delete FROM dbo.Orders Where orderDate < '2006-01-01'

更新计划:

这个查询2与查询1的更新计划完全不同,因为它执行的是每索引维护。

首先,该计划从聚集索引中删除符合条件的行,同时构建一个临时的假脱机表(spool table),其中包含必须进行维护的三个非聚集索引的键值。SQL Server为每个索引读取一次假脱机数据。在读取假脱机数据和从非聚集索引中删除行之间,SQL Server按被维护索引的顺序排序假脱机数据,以确保对索引页的最佳访问。

Sequence运算符强制其分支的执行顺序,在这里索引的按从上倒下的顺序进行删除。

 

每行更新策略在CPU方面是高效的,因为同时更新表和所有索引只需很短的代码路径。每索引维护的代码稍微有些复杂,但这样更节省I/O。如果对键排序后再单独更新非聚集索引,即使同一页中有许多行都被更新,我们也只须访问索引页一次。这也就是为什么SQL Server查询优化器认为每行策略需要多次读取被维护索引的同一页才能完成维护,这时它通常选择每索引维护策略。

[ASP.Net]翻译-ASP.NET MVC Contact Manager开发之旅迭代4 -

mikel阅读(705)

本翻译系列为asp.net mvc官方实 例教程。在这个系列中,Stephen Walther将演示如何通过ASP.NET MVC framework结合单元测试、TDD、Ajax、软件设计原则及设计模式创建一个完整的Contact Manager应用。本系列共七个章节,也是七次迭代过程。本人将陆续对其进行翻译并发布出来,希望能对学习ASP.NET MVC 的各位有所帮助。由于本人也是个MVC菜鸟,且E文水平亦是平平,文中如有疏漏敬请见谅。
注:为保证可读性,文中Controller、View、Model、Route、Action等ASP.NET MVC核心单词均未翻译。

 

ContactManager开发之旅-索引页

ContactManager开发之旅 迭代1 – 创建应用程序

ContactManager开发之旅 迭代2 – 修改样式,美化应用

ContactManager开发之旅 迭代3 – 验证表单

迭代4 利用设计模式松散耦合

本次迭代

这是ContactManager的第四次迭代,本次迭代中我们将重构应用程序,通过合理的利用设计模式松散其耦合。松耦合的程序更有弹性,更易维 护。当应用程序面临改动时,你只需修改某一部分的代码,而不会出现大量修改与其耦合严重的相关代码这种牵一发而动全身的情况。

在当前的ContactManager应用中,所有的数据存储及验证逻辑都分布在controller类中,这并不是个好主意。要知道这种情况下一 旦你需要修改其中一部分代码,你将同时面临为其他部分增加bug的风险。比如你需要修改验证逻辑,你就必须承担数据存储或controller部分的逻辑 会随之出现问题的风险。

(SRP-单一职责原则), 就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因。将controller、验证及数据存储逻辑耦合在一起严重的违反了SRP。

需求的变更,个人想法的升华、始料未及的情况如此频繁,你不得不就当前的应用作出一些改变:为应用程序添加新功能、修复bug、修改应用中某个功能的实现等。就应用程序而言,它们很难处于一种静止不动的状态,他们无时无刻在被不停的改变、改善。

现在的情况是,ContactManager应用中使用了Microsoft Entity Framework处理数据通信。想象一下,你决定对数据存储层实现做出一些改变,你希望使用其它的一些方案:如ADP.NET Data Services或NHibernate。由于数据存储相关的代码并不独立于验证及controller中的代码,你将无法避免修改那些原本应该毫无干系 的代码。

而另一方面,对于一个松耦合的程序来说,上面的问题就不存在了。一个经典的场景是:你可以随意切换数据存储方案而不用管验证逻辑、controller中的那些劳什子代码。

在这次迭代中,我们将利用软件设计模式的优点重构我们的Contact Manager应用程序,使其符合我们上面提到的“松耦合”的要求。尽管做完这些以后,我们的应用程序并不会表现的与以往有任何不同,但是我们从此便可轻松驾驭其未来的维护及修改过程。

重构就是指在不改变程序外在行为的前提下,对代码做出修改,改进程序内部结构的过程。

 

使用Repository模式

我们的第一个改动便是使用叫做Repository的设计模式改善我们的应用。我们将使用这个模式将数据存储相关的代码与我们应用中的其他逻辑独立开来。

要实现Repository模式,我们需要做以下两件事

  1. 新建一个接口
  2. 新建一个类实现上面的接口。

首先,我们需要新建一个接口约定所有我们需要实现的数据存储方法。IContactManagerRepository接口代码如下。这个接口约定 了五个方法:CreateContact()、DeleteContact()、EditContact()、GetContact()及 ListContacts()方法:

using System;
using System.Collections.Generic;
namespace ContactManager.Models
{
public interface IContactRepository
{
Contact CreateContact(Contact contactToCreate);
void DeleteContact(Contact contactToDelete);
Contact EditContact(Contact contactToUpdate);
Contact GetContact(int id);
IEnumerable<Contact> ListContacts();
}
}

接着,我们需要新建一个具体的类来实现IContactManagerRepositoyr接口。由于我们这里使用Microsoft Entity Framework操作数据库,所以我们为这个类命名为“EntityContactManagerRepository”,这个类的代码如下:

using System.Collections.Generic;
using System.Linq;
namespace ContactManager.Models
{
public class EntityContactManagerRepository : ContactManager.Models.IContactManagerRepository
{
private ContactManagerDBEntities _entities = new ContactManagerDBEntities();
public Contact GetContact(int id)
{
return (from c in _entities.ContactSet
where c.Id == id
select c).FirstOrDefault();
}
public IEnumerable<Contact> ListContacts()
{
return _entities.ContactSet.ToList();
}
public Contact CreateContact(Contact contactToCreate)
{
_entities.AddToContactSet(contactToCreate);
_entities.SaveChanges();
return contactToCreate;
}
public Contact EditContact(Contact contactToEdit)
{
var originalContact = GetContact(contactToEdit.Id);
_entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
_entities.SaveChanges();
return contactToEdit;
}
public void DeleteContact(Contact contactToDelete)
{
var originalContact = GetContact(contactToDelete.Id);
_entities.DeleteObject(originalContact);
_entities.SaveChanges();
}
}
}

注意,EntityContactManagerRepository类实现了IContactManagerRepository接口约定的5个方法。

为什么我们一定要要建立个接口再建立一个类来实现它呢?

应用程序中的其他部分将与接口而不是具体的类进行交互。也就是说,它们将调用接口声明方法而不是具体的类中的方法。

所以,我们可以以一个新的类实现某个接口但不用修改应用程序中其他的部分。例如,将来我们可能需要建立一个 DataServicesContactManagerRepository类实现IContactManagerRepository接口。 DataServicesContactManagerRepository类使用ADO.NET Data Services,我们用它代替Microsoft Entity Framework.与数据库通信进行数据存储。

如果我们的应用程序代码是基于IContactManagerRepository接口而不是 EntityContactManagerRepository这个具体的类,那么我们可以只改变不同的类名而非代码中的其他部分。例如我们可以将 EntityContactManagerRepository修改成DataServicesContactManagerRepository而不用 去碰数据存储和验证逻辑相关的代码。

面向接口(虚类)编程使我们的应用程序更有弹性,更易修改。

通过在VS中选择“重构”菜单->“提取接口”,你可以根据一个具体的类方便快速的创建出一个与之对应的接口。例如你可以先建立一个 EntityContactManagerRepository类,然后使用如上文所述的方法自动生成 IContactManagerRepository接口。

使用依赖注入

现在,我们已经将数据访问相关的代码独立到了Repository类中。而后,我们需要修改Contact controller以适应这些改变。这里我们将使用依赖注入的方式。

修改后的Contact controller代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using ContactManager.Models;
namespace ContactManager.Controllers
{
public class ContactController : Controller
{
private IContactManagerRepository _repository;
public ContactController()
: this(new EntityContactManagerRepository()) { }
public ContactController(IContactManagerRepository repository)
{
_repository = repository;
}
protected void ValidateContact(Contact contactToValidate)
{
if (contactToValidate.FirstName.Trim().Length == 0)
ModelState.AddModelError("FirstName", "First name is required.");
if (contactToValidate.LastName.Trim().Length == 0)
ModelState.AddModelError("LastName", "Last name is required.");
if (contactToValidate.Phone.Length > 0 && !Regex.IsMatch(contactToValidate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
ModelState.AddModelError("Phone", "Invalid phone number.");
if (contactToValidate.Email.Length > 0 && !Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
ModelState.AddModelError("Email", "Invalid email address.");
}
//
// GET: /Home/
public ActionResult Index()
{
return View(_repository.ListContacts());
}
//
// GET: /Home/Details/5
public ActionResult Details(int id)
{
return View();
}
//
// GET: /Home/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Home/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
{
//Validation logic
ValidateContact(contactToCreate);
if (!ModelState.IsValid)
{
return View();
}
else
{
try
{
_repository.CreateContact(contactToCreate);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
//
// GET: /Home/Edit/5
public ActionResult Edit(int id)
{
return View(_repository.GetContact(id));
}
//
// POST: /Home/Edit/5
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Contact contactToEdit)
{
ValidateContact(contactToEdit);
if (!ModelState.IsValid)
return View();
try
{
_repository.EditContact(contactToEdit);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
//
// GET: /Home/Delete/5
public ActionResult Delete(int id)
{
return View(_repository.GetContact(id));
}
//
// POST: /Home/Delete/5
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(Contact contactToDelete)
{
try
{
_repository.DeleteContact(contactToDelete);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}

注意上面代码中,Contact controller包含两个构造函数。第一个构造函数向第二个构造函数传递一个基于IContactManagerRepository接口的实例。这就是“构造子注入”。

EntityContactManagerRepository类仅仅在第一个构造函数中被使用。其他的地方一律使用IContactManagerRepository接口代替确切的EntityContactManagerRepository类。

在这种情况下,如果以后我们想改变IContactManagerRepository的实现也就很方便了。比如你想使用 DataServicesContactRepository类代替EntityContactManagerRepository类,则只需要修改第一 个构造函数即可。

不仅如此,构造子注入更使Contact controller的可测试性变得更强。在你的单元测试用,你可以通过传递一个mock的IContactManagerRepository的实现进 而实例化Contact controller。依赖注入所带来的特性将在我们对Contact Manager的下一次迭代—进行单元测试—时显得非常重要。

如果你希望将Contact controller类与具体的IContactManagerRepository接口的实现彻底解耦,则可以使用一些支持依赖注入的框架,如 StructureMap或Microsoft Entity Framework (MEF)。有了这些依赖注入框架的帮忙,你就不必在代码中面向具体的类了。

建立service层

你应该注意到了,我们的验证逻辑仍与上面代码中修改过的controller逻辑混合在一起。像我们独立数据存储逻辑一样,将验证逻辑独立出来同样是个好注意。

So,我们应当建立service层。在这里,它作为独立的一层以衔接controller和repository类。service层应当包括所有的业务逻辑,我们的验证逻辑当然也不例外。

ContactManagerService的代码如下,我们将验证逻辑转移到了这里:

using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using ContactManager.Models.Validation;
namespace ContactManager.Models
{
public class ContactManagerService : IContactManagerService
{
private IValidationDictionary _validationDictionary;
private IContactManagerRepository _repository;
public ContactManagerService(IValidationDictionary validationDictionary)
: this(validationDictionary, new EntityContactManagerRepository())
{ }
public ContactManagerService(IValidationDictionary validationDictionary, IContactManagerRepository repository)
{
_validationDictionary = validationDictionary;
_repository = repository;
}
public bool ValidateContact(Contact contactToValidate)
{
if (contactToValidate.FirstName.Trim().Length == 0)
_validationDictionary.AddError("FirstName", "First name is required.");
if (contactToValidate.LastName.Trim().Length == 0)
_validationDictionary.AddError("LastName", "Last name is required.");
if (contactToValidate.Phone.Length > 0 && !Regex.IsMatch(contactToValidate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
_validationDictionary.AddError("Phone", "Invalid phone number.");
if (contactToValidate.Email.Length > 0 && !Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
_validationDictionary.AddError("Email", "Invalid email address.");
return _validationDictionary.IsValid;
}
#region IContactManagerService Members
public bool CreateContact(Contact contactToCreate)
{
// Validation logic
if (!ValidateContact(contactToCreate))
return false;
// Database logic
try
{
_repository.CreateContact(contactToCreate);
}
catch
{
return false;
}
return true;
}
public bool EditContact(Contact contactToEdit)
{
// Validation logic
if (!ValidateContact(contactToEdit))
return false;
// Database logic
try
{
_repository.EditContact(contactToEdit);
}
catch
{
return false;
}
return true;
}
public bool DeleteContact(Contact contactToDelete)
{
try
{
_repository.DeleteContact(contactToDelete);
}
catch
{
return false;
}
return true;
}
public Contact GetContact(int id)
{
return _repository.GetContact(id);
}
public IEnumerable<Contact> ListContacts()
{
return _repository.ListContacts();
}
#endregion
}
}

需要注意的是,ContactManagerService的构造函数中需要一个ValidationDictionary参数。service层 通过这个ValidationDictionary与controller层进行交互。我们将在接下来讨论装饰者模式时来说明它。

更值得注意的是,ContactManagerService实现了IContactManagerService接口。你需要时刻努力进行面向接 口变成。Contact Manager应用中的其他类都不与具体的ContactManagerService类直接交互。它们皆需面向 IContactManagerService接口。

IContactManagerService接口的代码如下:

 

using System.Collections.Generic; namespace ContactManager.Models { public interface IContactManagerService { bool CreateContact(Contact contactToCreate); bool DeleteContact(Contact contactToDelete); bool EditContact(Contact contactToEdit); Contact GetContact(int id); IEnumerable<Contact> ListContacts(); } }

修改后的Contact controller类代码如下,这里Contact controller类已不再与ContactManager service交互,每一层都尽可能的与其他层独立开来。

using System.Web.Mvc;
using ContactManager.Models;
using ContactManager.Models.Validation;
namespace ContactManager.Controllers
{
public class ContactController : Controller
{
private IContactManagerService _service;
public ContactController()
{
_service = new ContactManagerService(new ModelStateWrapper(this.ModelState));
}
public ContactController(IContactManagerService service)
{
_service = service;
}
public ActionResult Index()
{
return View(_service.ListContacts());
}
public ActionResult Create()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
{
if (_service.CreateContact(contactToCreate))
return RedirectToAction("Index");
return View();
}
public ActionResult Edit(int id)
{
return View(_service.GetContact(id));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Contact contactToEdit)
{
if (_service.EditContact(contactToEdit))
return RedirectToAction("Index");
return View();
}
public ActionResult Delete(int id)
{
return View(_service.GetContact(id));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(Contact contactToDelete)
{
if (_service.DeleteContact(contactToDelete))
return RedirectToAction("Index");
return View();
}
}
}

我们的应用程序已经不再违反SRP原则了。上面所示代码的Contact controller中,所有的验证逻辑都被转移到service层中,所有的数据库存储逻辑都被转移到repository层中。

使用装饰者模式

我们欲将service层与controller层完全解耦,原则上讲也就是我们应当可以在独立的程序集中编译service层而无需添加对MVC应用程序的引用。

然而我们的service层需要将验证错误信息回传给controller层,那么我们如何才能在service层和controller不耦合的前提下完成这项任务呢?答案是:装饰者模式。

Contrlooer使用名为ModelState的ModelStateDictionary表现验证错误信息。因此我们可能会想将 ModelState从controller层传递到sercice层。然而在service层中使用ModelState会使你的服务层依赖于 ASP.NET MVC framework提供的某些特性。这可能会很糟,假设某天你想在一个WPF应用程序中使用这个service层,你就不得不添加对ASP.NET MVC framework的引用才能使用ModelStateDictionary类。

装饰者模式通过将已有的类包装在新的类中从而实现某接口。我们的Contact Manager项目中包含的ModelStateWrapper类的代码如下:

using System.Web.Mvc;
namespace ContactManager.Models.Validation
{
public class ModelStateWrapper : IValidationDictionary
{
private ModelStateDictionary _modelState;
public ModelStateWrapper(ModelStateDictionary modelState)
{
_modelState = modelState;
}
public void AddError(string key, string errorMessage)
{
_modelState.AddModelError(key, errorMessage);
}
public bool IsValid
{
get { return _modelState.IsValid; }
}
}
}

其接口代码如下:

namespace ContactManager.Models.Validation
{
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
}

仔细观察IContactManagerService接口中的代码,你会发现ContactManager service层中仅使用了IValidationDictionary接口。ContactManager service不依赖ModelStateDictionary类。当Contact controller创建ContactManager service时,controller将其ModelState包装成如下的样子:

_service = new ContactManagerService(new ModelStateWrapper(this.ModelState));

总结

本次迭代中,我们并没有对Contact Manager应用添加任何的功能。本次迭代的目的是通过重构应用程序,使Contact Manager更易维护、更易修改。

首先,我们实现了Repository软件设计模式。我们将所有的数据存取相关的代码提取到独立的ContactManager repository类中。同时,我们也将验证逻辑从controller逻辑中独立出来,将其放入我们另外创建的独立的service层中。 controller层与service层交互,service层则与repository层交互。

然后我们通过装饰者模式将ModelState从service层中独立出来。在我们的service层中,我们只需针对IValidationDictionary接口进行编码,而非针对ModelState类。

最后,我们使用了依赖注入这种软件设计模式。该模式使得我们在开发中可以避开具体类,而针对接口(虚类)编码。得益于依赖注入模式,我们的代码变得更具测试性。在下一次迭代中,我们将向项目中添加单元测试。

作者:紫色永恒
出处:http://024hi.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利
Tag标签: asp.net mvc

posted on 2009-04-12 19:27 紫色永恒 阅读(1032) 评论(18)  编辑 收藏 网摘 所属分类: Asp.Net MVC

Feedback

#1楼  2009-04-12 19:55 ·风信子·      

又见博主的大作,支持   回复  引用  查看    

#2楼  2009-04-12 20:50 EntLib      

不错,很专业!

我们也在翻译NerdDinner 范例程序的创建过程。

一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序 (Part 1-10) – 强烈推荐!
一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序 (Part 11-20) – 强烈推荐! 
欢迎交流

  回复  引用  查看    

#3楼  2009-04-12 21:07 Jeffrey Zhao      

这是Repository吗?这是Decorator吗?不过是一路打通的简单逻辑,分为了业务逻辑数据访问层,再根据接口进行访问——哪来Repository?哪来Decorator?   回复  引用  查看    

#4楼 [楼主] 2009-04-12 21:22 紫色永恒      

@Jeffrey Zhao
其实我也有此问,但翻译就是翻译   回复  引用  查看    

#5楼  2009-04-12 23:18 Q.Lee.lulu      

@Jeffrey Zhao
这不是Repository么?那么对于真正的Repository还差些什么呢?   回复  引用  查看    

#6楼 [楼主] 2009-04-13 07:57 紫色永恒      

@Q.Lee.lulu
我主要觉得这里面的DI实在有点勉强   回复  引用  查看    

#7楼  2009-04-13 09:32 假正经哥哥      

Repository?扫下盲。。。   回复  引用  查看    

#8楼  2009-04-13 19:56 Jeffrey Zhao      

Repository应该是一个collection like的interface,提供查询/修改操作,而不是包含一系列CRUD方法的接口。
例如Repository的查询:
var r = new Repository()
var q = new SomeQueryObject();
return r.List(q);
可以看出,linq是个不错的内置的query object。
修改呢?get/modify/save
添加呢?create/add/save
而不是一个功能一个接口,否则就是个普通的数据访问层而已,什么Repository……   回复  引用  查看    

#9楼  2009-04-13 20:51 EntLib      

@Jeffrey Zhao
在Scott Gu的ASP.NET MVC 1.0 – NerdDinner 范例程序,是这样定义IDinnerRepository接口的:
public interface IDinnerRepository {
IQueryable<Dinner> FindAllDinners();
IQueryable<Dinner> FindByLocation(float latitude, float longitude);
IQueryable<Dinner> FindUpcomingDinners();
Dinner GetDinner(int id);
void Add(Dinner dinner);
void Delete(Dinner dinner);
void Save();
}
和你的想法似乎不一致。
  回复  引用  查看    

#10楼  2009-04-13 21:53 Jeffrey Zhao      

@EntLib
我说的是PoEAA那本书上的样子(也就是Query Object),ScottGu也只是多加了一些额外的查询接口,和我的说法大体一致阿,都是针对集合的操作,比如Add/Delete/Save。
不像现在的文章,CreateXxx,DeleteXxx,EditXxx,都不是针对集合的操作,只是一个数据访问层对象而已。试想如果要更多接口的会怎么样呢?
UpdateContactsForCompany(int companyId, …)
这又算什么Repository呢?   回复  引用  查看    

#11楼  2009-04-13 22:36 EntLib      

@Jeffrey Zhao
根据我的理解,我认为Scott Gu定义的IDinnerRepository 接口,和本文中定义了IContactRepository 接口基本思路是一样的。
不知道其他朋友有什么想法?
  回复  引用  查看    

#12楼  2009-04-13 22:44 Jeffrey Zhao      

@EntLib
嗯,我总结一下我的观点:
Repository有其特定的概念和操作方式,这也是为什么Scott和我认为Repository都需要一个Save方法的缘故。 Repository是一个集合,NHibernate,Linq to SQL都适合实现Repository,它是把Domain和关系进行映射(Mapping),而Repository是一个抽象的“容器”。
这片文章里就是个普通的数据访问层,和我们随便写一个程序,其中的数据访问层就会是这样的,说白了,这更像是最普通的Trasaction Scripts,缺少Mapping的概念。
所以说,看起来有接口类似(部分查询功能),但是操作理念是大相径庭的,这也就是我说这篇文章在胡搞的原因。
// Decorator更不用说了,没有组合,没有扩展,一点Decorator的特点都没有。   回复  引用  查看    

#13楼  2009-04-13 23:15 EntLib      

@Jeffrey Zhao,
首先,很高兴和你讨论这个问题。谢谢!
关于引入Repository Class,Scott Gu 在文章中是这样解释的:

创建DinnerRepository
对于一个小应用程序而言,有时让Conrollers类直接使用LINQ to SQL DataContext类,并将LINQ 查询语句写在Controllers中。但随着应用程序越来越大,这一方法的维护和测试将变得麻烦,并且导致重复的LINQ查询在多个地方出现。
 

让维护和测试更方便的方法是使用Repository 模式。Repository类帮助封装数据查询和存储逻辑,从应用程序中抽象隔离具体的数据存储实现。除了是应用程序代码更加简洁外,使用 Repository模式使将来更改数据库存储实现更加方便,并且有助于在没有真实数据库的情况下,进行应用程序进行单元测试。
原文链接:一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序,Part 5

[SQL]TSQL查询内幕::(2.2)编译

mikel阅读(1004)

批处理是作为一个单元编译的一个或多个T-SQL语句的组合,存储过程就是一个批处理的例子。另一个例子就是在查询管理器中,输入一组语句,GO命令把多条语句分隔为单独的批处理。GO不是一个T-SQL语句,只是SQL Server的企业管理器的一个关键字,企业管理器通过这个关键字来分隔批处理(SQLCMDOSQL也是使用GO来表示批处理的)

 

SQL Server把批处理中的语句编译到一个被称为“执行计划”(execution plan)的可执行单元。在编译期间,编译器展开这些语句,其中包含该语句执行期间需要执行的相关约束、触发器以及级联操作。如果经过编译的批处理包含对其他存储过程或函数的调用,且缓存中没有他们的执行计划,则这些存储过程和函数也将被递归的编译。

 

批处理编译的主要步骤:

编译和执行查询处理是两个截然不同的阶段,有可能一个编译需要几个小时,可是执行该查询只需要几毫秒。处理即席查询时缓存中通常不包含其执行计划,因此它被编译后会立即执行。而经常执行的存储过程的已编译计划可能会在过程缓存中保留很长时间。在需要释放存储空间时,SQL Server会从过程缓存中首先移除不常用的计划

 

SQL Server准备处理一个批处理时,如果缓存中不存在该批处理的执行计划,则编译该批处理并生成执行计划。

 

首先,SQL Server执行分析。分析是检查语法并把SQL批处理转换为分析树(parse tree)的过程,如检查表或列明是否以数字开头,但他不会检查Where中的列是否存在于FROM子句中所写的表中。

 

然后,SQL Server进行绑定。绑定过程确定SQL语句所引用对象的特征,他检查请求语义是否有意义,参见下面的Algebrizer

 

优化是编译的最后一步。优化器必须把基于几何的非过程SQL语句转换为可以高效执行并返回期望结果的程序。与绑定类似,优化器一次只优化批处理中的一条语句。在编译器为该批处理生成执行计划并存储到过程缓存之后,将执行该计划的执行上下文的一个特殊副本。SQL Server像缓存查询计划一样缓存执行上下文。SQL Server并不优化批处理中的每条语句。它只优化那些访问表而且可能生成多个执行计划的语句。SQL Server优化所有的DML语句(SelectInsertDeleteUpdate)。除了DML ,一些T-SQL语句也会被优化,Create INDEX便是其中之一。只有被优化过的语句才会生成查询计划。

 

Algebrizer

Algebrizer执行的整个过程称为绑定。

分析阶段输出的分析树,正是Algebrizer的输入。遍历几次分析树后,Algebrizer生成查询处理器树(query processor tree),用于查询优化。

 

运算符平展:

Algebrizer展开二元运算符UNIONANDOR

在分析阶段,分析器把这些逻辑运算符都看作是二元的,就像下图左侧的样子。

 

而分析器之后的所有步骤则会把多个二元运算符组合成一个n元运算符,像右侧所示。这对于非常长的IN列表是非常重要的,分析器会把它转换为一连串的OR运算符。此外,展开运算符还会避免在后续传递中由非常深的树所引起的大部分堆溢出问题。SQL Server内部用于执行运算符展开的代码都尽可能的使用迭代而非递归,这样Algebrizer本身就不容易出现同样的问题。

 

名称解析:

分析树中的每个表名称和列名称都关联到相应表或列对象的引用。表示相同对象的名称具有相同的引用。Algebrizer检查查询中的每个对象名称是否真的引用了系统目录中已经存在的有效表或列,以及是否在特定的查询范围内可见。然后,Algebrizer把目录中的信息关联到该对象的名称。

其中,对视图的名称解析使用视图树替换视图引用的过程。如果试图还引用了其他视图,他们将被递归地进行解析。

 

类型派生:

T-SQL中的数据类型是静态确定的,Algebrizer负责确定分析树中每个节点的类型。

 

聚合绑定:

首先看一个查询,T1T2是两个表,T1c1,c2两列,T2只有x一列。其中,T1.c2列与T2.x列具有同样的数据类型。

 

Select c1 FROM dbo.T1

GROUP BY c1

HAVING EXISTS

 (Select * FROM dbo.T2

   Where T2.x > MAX(T1.c2));

 

对于上面这个查询,SQL Server会在外层查询Select c1 FROM dbo.T1 GROUP BY c1时计算MAX聚合。MAX不是在内部查询中进行的吗?为什么不是在子查询中计算呢?因为每个聚合都需要绑定到他的宿主查询,这样才可以正确的计算聚合。

 

分组绑定:

看一个查询,T1是一个表,包含了c1c2c3列。

Select c1 + c2, MAX(c3) FROM dbo.T1 GROUP BY c1 + c2;

这个表达式是合法的,但如果只在Select列表中使用c1,则不合法。因为分组查询与非分组查询有不同的语义。

Select列表中,所有非聚合的列或表达式,必须在GROUP BY列表中有直接的对应项。检验是否满足这一规则的过程就叫做分组绑定。

不光如此,根据SQL的规定,只要出现绑定到特定列表的聚合函数,及时没有GROUP BYHAVING,也会使Select列表被分组。

看这个例子:

Select c1, MAX(c2) FROM dbo.T1;

这是一个分组Select,因为查询中包含了一个MAX聚合,所以因为Select中有c1,所以这个查询是不合法的。

再看一个例子:

Select c1,

       (Select T2.y

          FROM dbo.T2

        Where T2.x = MAX(T1.c2))

 FROM dbo.T1;

这个查询也不合法,在聚合绑定中,我们知道MAX(T1.c2)是在外部查询中计算的,所以外部查询是一个分组,所以c1Select列表中是非法的。

 

优化

处理查询时所涉及的最重要最复杂的组件是查询优化器。优化器的任务是为批处理或存储过程中的每个查询生成高校的执行计划。

这里先请查询优化器做一个自我介绍:“我是个基于成本的优化器,我会尝试为每个SQl语句生成成本最低的执行计划,我不会分析所有可能的组合,但会尝试找出成本非常接近理论最小值的一个执行计划”。

这里,查询分析器先生所说的成本,表现为估计的完成查询所需的时间。然而,最低时间成本不一定是最低资源成本。比如,使用多个CPU处理一个查询,有时会减少时间成本,但是却增加了资源成本。

查询分析器这时作出了补充:“我主张使用并行计划,而且如果对该服务器上的负载不会产生负面影响,SQL Server会使用并行计划来执行”。

 

优化本身包括几个步骤。细微计划优化是第一步。这一步产生的原因,是因为查询优化器做成本优化的本身成本较高。如果SQL Server通过分析该查询发现只有一种可行的计划,他就可以避免查询优化器做成本优化时初始化和执行所需的大量工作。

比如有这样一个情况:带有VALUES子句的Insert语句,这个插入语句的目标表不参与任何索引视图,那么就只有一个可能的计划。

再比如,执行一个对没有索引的表,进行不带GROUP BYSelect查询。

查询优化器生成的这个细微计划的成本很低。

 

如果优化器没有找到细微计划,SQL Server将进行一些简化,以寻找可以被重新整理的可交换属性和操作,说白了就是对查询语句进行语法转换的简化。

比如,在联接之前计算表的Where筛选器就是一个简化的例子。因为在逻辑查询顺序中[TSQL::(1)逻辑查询处理],筛选器在联接之后计算,但在联接之前计算筛选器也可以得到正确的结果,而且是永远是更高效的,所以在简化这步会做出类似的语法优化。

再比如,看下面这个查询

USE Northwind;

Select

 [Order Details].OrderID,

 [Products].ProductName,

 [Order Details].Quantity,

 [Order Details].UnitPrice

FROM dbo.[Order Details]

 LEFT OUTER JOIN dbo.Products

    ON [Order Details].ProductID = Products.ProductID

Where Products.UnitPrice > 10;

 

这个加红的LEFT OUTER,会被简化为INNER,原因大家应该都知道。

下图是简化后的执行计划:

 

SQL Server做完了简化之后,查询优化器开始启动基于成本的优化过程。

通常,正确的结果都是相同的,但是有些不相同的结果也被认为是正确的。如:

Select TOP (10) <select_list> FROM Orders;

 

如果优化器比较所有可用计划的成本并选择成本最低的一个,优化过程会花费非常长的时间,因为可用的计划数量可能非常大。因此,优化被分成三个搜索阶段。

每个阶段关联一组转换规则。每个阶段之后,SQL Server会得到这个阶段成本最低的查询计划。当在某一个阶段的成本足够低,SQL Server将不再进行下一步的优化,而采用这个满足要求的计划。

 

阶段0:应用于至少包括4个表的查询。只使用有限的联接顺序数量,只考虑Hash联接和嵌套循环。如果找到成本低于0.2的计划,优化即结束。阶段0产生查询计划的查询通常出现在事务处理应用程序中,所以这个阶段也叫做事务处理阶段。

 

阶段1(非并行):快速计划优化。他使用更多的转换规则并尝试不同的联接顺序。该阶段完成后,如果最佳计划的成本低于1.0,则优化结束。

 

阶段1(并行):将再次执行阶段1的并行版本。当阶段1的两个版本都执行之后,将会比较两个版本的最佳计划,然后进入完全优化阶段。

 

阶段2:完全优化阶段。阶段2包括额外的规则,如:使用Outer Join重新排序并用索引视图自动替换多表视图。

 

总结以上的步骤,可以画出SQL Server查询优化的流程图:

 

SQL Server 2005中可以使用DMV(动态管理视图)sys.dm_exec_query_optimizer_info。这个视图提供了从SQL Server启动以来执行的所有优化的累积信息。

使用如下代码进行查询:

SET NOCOUNT ON;

USE Northwind;  这里使用你自己的数据库

DBCC FREEPROCCACHE;  清空过程缓存

GO

 我们将用tempdb..OptStats 表来捕获执行几次

— sys.dm_exec_query_optimizer_info所得到的信息

IF (OBJECT_ID('tempdb..OptStats') IS NOT NULL)

 Drop TABLE tempdb..OptStats;

GO

 这条语句用于创建临时表tempdb..OptStats

Select 0 AS Run, *

INTO tempdb..OptStats

FROM sys.dm_exec_query_optimizer_info;

GO

 该语句的计划将被保存到过程缓存

 这样当下一次执行时不产生任何优化器事件

 后面的GO用于确保下次执行这段脚本时

 可以重用Insert的计划

GO

Insert INTO tempdb..OptStats

 Select 1 AS Run, *

 FROM sys.dm_exec_query_optimizer_info;

GO

 原因同上,只是用"2"替换"1"

 这样我们将得到不同的计划

GO

Insert INTO tempdb..OptStats

 Select 2 AS RUN, *

 FROM sys.dm_exec_query_optimizer_info;

 清空临时表

TRUNCATE TABLE tempdb..OptStats

GO

 存储运行前的信息

 sys.dm_exec_query_optimizer_info的输出

 保存到临时表,RUn列的值为"1"

GO

Insert INTO tempdb..OptStats

 Select 1 AS Run, *

 FROM sys.dm_exec_query_optimizer_info;

GO

 在这里执行你自己的语句或批处理

 下面是一个示例

Select C.CustomerID, COUNT(O.OrderID) AS NumOrders

FROM dbo.Customers AS C

 LEFT OUTER JOIN dbo.Orders AS O

    ON C.CustomerID = O.CustomerID

Where C.City = 'London'

GROUP BY C.CustomerID

HAVING COUNT(O.OrderID) > 5

ORDER BY NumOrders;

 示例结束

GO

 存储运行后的信息,

 sys.dm_exec_query_optimizer_info的输出

 保存到临时表,Run列的值为"2"

GO

Insert INTO tempdb..OptStats

 Select 2 AS Run, *

 FROM sys.dm_exec_query_optimizer_info;

GO

 从临时表中提取出Runs1Runs2之间

— OCCURRENCE Value值发生变化的所有事件

 然后显示执行批处理或查询之前(Run1OccourenceRun1Value)

 和之后所有这些事件(Run2OccurrenceRun2Value)

 OccurrenceValue

WITH X (Run, Counter, Occurrence, Value)

AS

(

 Select *

 FROM tempdb..OptStats Where Run=1

),

Y (Run, Counter, Occurrence, Value)

AS

(

 Select *

 FROM tempdb..OptStats

 Where Run=2

)

Select X.Counter, Y.OccurrenceX.Occurrence AS Occurrenc,

 CASE (Y.OccurrenceX.Occurrence)

    WHEN 0 THEN (Y.Value*Y.Occurrencex.Value*x.Occurrence)

      ELSE (Y.Value*Y.OccurrenceX.Value*X.Occurrence)/(Y.Occurrencex.Occurrence)

 END AS Value

FROM X JOIN Y

 ON (X.Counter=Y.Counter

      AND (X.Occurrence<>Y.Occurrence OR x.Value<>Y.Value));

GO

 删除临时表

Drop TABLE tempdb..OptStats;

GO

执行结果:

[PHP]PHP的三种典型开发环境(downmoon)

mikel阅读(844)

最近在完成一个过渡项目,用到了PHP,以前看php怎么看怎么别扭,维护性差,代码乱是我的印象,这次决定花点时间看看到底有没有了解的必要,呵呵,

采用了三种方案来尝试:前提是Apache服务器,推荐下载xamppLite ,目前最新版本1.7.0

下载地址: http://www.apachefriends.org/en/xampp-windows.html

第一种:VS.PHP+VSS,整个环境非常适合.net平台。

一个很不错的PHP插件,可以集成在vs.net环境下,有三个版本:

 

下载地址:http://www.jcxsoftware.com/vs.php

 


 

 

第二种方案:NetBeans6.5以上+SVN/CVS

NetBeans6.5以上集成了PHP开发环境。易用,强大!下载地址:http://www.netbeans.org/downloads/index.html

 


 

 

 

 

第三种方案: Eclipse平台下PDT,目前最新版本是PDT 2.0 All In Ones

 

下载地址:http://www.eclipse.org/pdt/

下载后解压即用!

 

 

 

 

 

 

 小结:本文简要介绍了开发PHP的三种开发环境!第一种方案,易用性强,但要99$,呵呵。第二种方案易用性也不错!完全免费,问题是听说SUN要卖给IBM了!感觉有点别扭!第三种方案真的不错!开源无敌!

 

助人等于自助!   3w@live.cn

[iPhone]Windows下载iPhone开发环境搭建

mikel阅读(1000)

iPhone开发平台Win版放出(中文安装教程)

首先准备下载几个软件
cygwin:http://www.cygwin.com/setup.exe
Xcode开发包:http://rapidshare.com/files/66764258/MacOSX10.4u.sdk.rar
Iphone系统镜像:http://rapidshare.com/files/66747414/iphone_image.rar
上面两个RAR压缩包解压密码为:aksblog.co.nr
例如下载都放在C:\tmp\
教程中cygwin默认安装在C:\cygwin\ 如果您更改为其他路径请自行改变
都下载完成后开始下面的操作
首先先安装默认的cygwin 环境,否则接下来的操作会失败
—————————————————————————————————————————————————————-
1.双击setup.exe

2.按照安装提示“下一步”
3.选择从“互联网”安装:

4.选择安装的路径,一般是C:\Cygwin,选择本地临时目录,用于存放下载的软件;选择您连接互联网的方式,一般选“直接连接”就可以了。
5.选择下载源,您可以在下载列表里选择:http://www.cygwin.cn 或者,直接在URL里输入http://www.cygwin.cn/pub/

6.根据安装提示,选择需要安装的软件包,完成安装。
———————————————————————————————————————————-
打开C:\tmp\setup.exe
1.jpg (64.03 KB)
2008-2-3 03:35迅雷专用高速下载
点击安装界面上的”下一步”
来到下图页面
2.jpg (38.81 KB)
2008-2-3 03:35迅雷专用高速下载
3.jpg (78.93 KB)
2008-2-3 03:35迅雷专用高速下载
4.jpg (41.44 KB)
2008-2-3 03:35迅雷专用高速下载
5.jpg (61.83 KB)
2008-2-3 03:35迅雷专用高速下载
6.jpg (63.46 KB)
2008-2-3 03:35迅雷专用高速下载
到上面的步骤,User URL 这里填写:http://www.iphonegameover.com/cygwin 然后点击Add按钮,然后点击下一步
7.jpg (61.91 KB)
2008-2-3 03:35迅雷专用高速下载
下面的操作全部直接按下一步即可,直到下载安装完毕。
8.jpg (35.52 KB)
2008-2-3 03:42迅雷专用高速下载
完成后把 上图 的2个沟打上,方便运行cygwin
(下载可能会比较慢,请耐心等待下载完毕)
接下来打开下载的MacOSX10.4u.sdk.rar
把压缩包里面的Archive.pax.gz

解压到C:\cygwin\home\Administrator\ 目录下,没有此目录的话则手工建立该目录
接下来打开下载的 iphone_image.rar
把里面的iphone image 解压到 C:\cygwin\usr\local\arm-apple-darwin\ 目录下
解压完后把iphone image 文件夹改名为 filesystem

完成以上操作后,基本安装已经完成,接下来在开始菜单运行那里输入”C:\cygwin\Cygwin.bat”
屏幕显示
Checking if /Developer/SDKs/MacOSX10.4u.sdk exists… no!
Do you wish to install the XCode 2.5 header files now? (y/n)
按Y 继续
然后屏幕显示
Found ‘Archive.pax.gz’ in current directory.
Extracting /home/Administrator/Archive.pax.gz…
这里会很长时间停顿,大概1分钟左右
然后屏幕显示
Checking if /usr/local/arm-apple-darwin/include exists… checking for a BSD-com
patible install… /usr/bin/install -c
checking for /Developer/SDKs/MacOSX10.4u.sdk/usr/include/TargetConditionals.h…
no
configure: error: You must have a copy of the Mac OS X 10.4 Universal SDK. Pleas
e see the wiki at http://iphone-dev.googlecode.com/ for information on how to ob
tain this. If you already have the SDK use a –with-macosx-sdk=path option to sp
ecify the path.
Error: Files don’t appear to have been installed!
Would you like to delete this script? (y/n):
按 n 之后关闭cygwin
打开 C:\cygwin\Developer\Developer
把该目录下的SDKs 移动到C:\cygwin\Developer\ 目录下
删除C:\cygwin\Developer\Developer 目录
重新打开cygwin
接下来会自动完成安装,此时屏幕应该显示如下内容
Checking if /usr/local/arm-apple-darwin/filesystem exists… yes
Checking if /Developer/SDKs/MacOSX10.4u.sdk exists… yes
Checking if /usr/local/arm-apple-darwin/include exists… checking for a BSD-com
patible install… /usr/bin/install -c
checking for /Developer/SDKs/MacOSX10.4u.sdk/usr/include/TargetConditionals.h…
yes
configure: creating ./config.status
config.status: creating install-headers.sh
Making directory /usr/local/arm-apple-darwin/include
Finding framework header directories…
Finding base header directories…
Installing header files…
Making directory /usr/local/arm-apple-darwin/include/CoreGraphics
Entering directory /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/App
licationServices.framework/Frameworks/CoreGraphics.framework/Headers
Installing CGAffineTransform.h
Installing CGBase.h
Installing CGBitmapContext.h
Installing CGColor.h
Installing CGColorSpace.h
Installing CGContext.h
Installing CGDataConsumer.h
Installing CGDataProvider.h
Installing CGDirectDisplay.h
Installing CGDirectPalette.h
Installing CGDisplayConfiguration.h
Installing CGDisplayFade.h
Installing CGError.h
Installing CGEvent.h
Installing CGEventSource.h
Installing CGEventTypes.h
Installing CGFont.h
Installing CGFunction.h
Installing CGGeometry.h
Installing CGGLContext.h
Installing CGImage.h
此部分安装耗时比较久,大概10来分钟。
耐心等待操作完成
完成之后,屏幕显示
Installing vmutils/_NSDWARFAttribute.h
Installing vmutils/_NSSourceInfo.h
All done!
Your toolchain installation is now complete!
你的iphone开发环境已经搭建完毕!
迅雷专用高速下载迅雷专用高速下载迅雷专用高速下载

[SQL]SQL性能优化

mikel阅读(993)

1.查询的模糊匹配
     尽量避免在一个复杂查询里面使用 LIKE '%parm1%'—— 红色标识位置的百分号会导致相关列的索引无法使用,最好不要用.
解决办法:
其实只需要对该脚本略做改进,查询速度便会提高近百倍。改进方法如下:
        a、修改前台程序——把查询条件的供应商名称一栏由原来的文本输入改为下拉列表,用户模糊输入供应商名称时,直接在前台就帮忙定位到具体的供应商,这样在调用后台程序时,这列就可以直接用等于来关联了。
        b、直接修改后台——根据输入条件,先查出符合条件的供应商,并把相关记录保存在一个临时表里头,然后再用临时表去做复杂关联

2.索引问题
        在做性能跟踪分析过程中,经常发现有不少后台程序的性能问题是因为缺少合适索引造成的,有些表甚至一个索引都没有。这种情况往往都是因为在设计表时,没去 定义索引,而开发初期,由于表记录很少,索引创建与否,可能对性能没啥影响,开发人员因此也未多加重视。然一旦程序发布到生产环境,随着时间的推移,表记 录越来越多,这时缺少索引,对性能的影响便会越来越大了。
        这个问题需要数据库设计人员和开发人员共同关注

法则:不要在建立的索引的数据列上进行下列操作:
避免对索引字段进行计算操作
避免在索引字段上使用not,<>,!=
避免在索引列上使用IS NULL和IS NOT NULL
避免在索引列上出现数据类型转换
避免在索引字段上使用函数
避免建立索引的列中使用空值。

3.复杂操作
部分Update、Select 语句 写得很复杂(经常嵌套多级子查询)——可以考虑适当拆成几步,先生成一些临时数据表,再进行关联操作

4.update
同一个表的修改在一个过程里出现好几十次,如:
                update table1
                set col1=…
                where col2=…;
               
                update table1
                set col1=…
                where col2=…
                ……

        象这类脚本其实可以很简单就整合在一个Update语句来完成(前些时候在协助xxx项目做性能问题分析时就发现存在这种情况)

5.在可以使用UNION ALL的语句里,使用了UNION
UNION 因为会将各查询子集的记录做比较,故比起UNION ALL ,通常速度都会慢上许多。一般来说,如果使用UNION ALL能满足要求的话,
                务必使用UNION ALL。还有一种情况大家可能会忽略掉,就是虽然要求几个子集的并集需要过滤掉重复记录,但由于脚本的特殊性,不可能存在重复记录,这时便应该使用 UNION ALL,如xx模块的某个查询程序就曾经存在这种情况,见,由于语句的特殊性,在这个脚本
                中几个子集的记录绝对不可能重复,故可以改用UNION ALL)

6.在Where 语句中,尽量避免对索引字段进行计算操作
                这个常识相信绝大部分开发人员都应该知道,但仍有不少人这么使用,我想其中一个最主要的原因可能是为了编写方便吧,但如果仅为了编
                写简单而损害了性能,那就不可取了

                9月份在对XX系统做性能分析时发现,有大量的后台程序存在类似用法,如:

                ……
                where trunc(create_date)=trunc(:date1)
                虽然已对create_date 字段建了索引,但由于加了TRUNC,使得索引无法用上。此处正确的写法应该是
                where create_date>=trunc(:date1) and create_date<trunc(:date1)+1
                或者是
                where create_date between trunc(:date1) and trunc(:date1)+1-1/(24*60*60)
                注意:因between 的范围是个闭区间(greater than or equal to low value and less than or equal to high value.),
                故严格意义上应该再减去一个趋于0的小数,这里暂且设置成减去1秒(1/(24*60*60)),如果不要求这么精确的话,可以略掉这步

7.对Where 语句的法则
7.1 避免在Where子句中使用in,not  in,or 或者having。
可以使用 exist 和not exist代替 in和not in。
可以使用表链接代替 exist。
Having可以用where代替,如果无法代替可以分两步处理。
例子
Select *  FROM orDERS Where CUSTOMER_NAME NOT IN
                    (Select CUSTOMER_NAME FROM CUSTOMER)
优化
Select *  FROM orDERS Where CUSTOMER_NAME not exist
                    (Select CUSTOMER_NAME FROM CUSTOMER)
7.2 不要以字符格式声明数字,要以数字格式声明字符值。(日期同样)
否则会使索引无效,产生全表扫描。
例子
使用:Select emp.ename, emp.job FROM emp Where emp.empno = 7369;
不要使用:Select emp.ename, emp.job FROM emp Where emp.empno = ‘7369’

8.对Select语句的法则
在应用程序、包和过程中限制使用select * from table这种方式。

例子
使用
Select empno,ename,category FROM emp Where empno = '7369‘
而不要使用
Select * FROM emp Where empno = '7369'

9. 排序
避免使用耗费资源的操作
带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎 执行,耗费资源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要执行两次排序

10.临时表
慎重使用临时表可以极大的提高系统性能

[SQL]在Sql语句中使用正则表达式来查找你所要的字符

mikel阅读(1061)

这几天有个项目需要从表的某个字段判断是否存在某些规则的字符串,大概如下:

有个表叫TaskDeploy(任务部署的),其中字段Participants以格式 用户名,用户名 具体如下:

 

admin,小明,samlin
小明,samlin,test
samlin,test,小明
samlin,admin
samlin,test
samlin,test

 

好了,现在我要查找有指派给 'samlin'用户的任务记录怎么办?

我首先想到的是正则表达式,但SQL提供的功能真是少啊就是一个 like 加几个通配符,显然是满足不了我的要求的

 

于是从网上搜集了一些资料,整理了一下。
下面这个是一个自定义函数,用户可以调用这个函数判断指定的字符串是否符合正则表达式的规则.

Create FUNCTION dbo.find_regular_expression
 (
  
@source varchar(5000),   需要匹配的源字符串
  @regexp varchar(1000),  正则表达式
  @ignorecase bit = 0  是否区分大小写,默认为false
 )
RETURNS bit  返回结果0-false,1-true
AS
 
BEGIN
 
0(成功)或非零数字(失败),是由 OLE 自动化对象返回的 HRESULT 的整数值。
  DECLARE @hr integer 
用于保存返回的对象令牌,以便之后对该对象进行操作
  DECLARE @objRegExp integer   DECLARE @objMatches integer
保存结果
  DECLARE @results bit
  
/*
创建 OLE 对象实例,只有 sysadmin 固定服务器角色的成员才能执行 sp_OACreate,并确定机器中有VBScript.RegExp类库
*/
  
EXEC @hr = sp_OACreate 'VBScript.RegExp'@objRegExp OUTPUT
  
IF @hr <> 0 BEGIN
   
SET @results = 0
   
RETURN @results
  
END
/*
以下三个分别是设置新建对象的三个属性。下面是'VBScript.RegExp'中常用的属性举例:
    Dim regEx,Match,Matches         '建立变量。
    Set regEx = New RegExp         '建立一般表达式。
    regEx.Pattern= patrn         '设置模式。
    regEx.IgnoreCase = True         '设置是否区分大小写。
    regEx.Global=True                             '设置全局可用性。
    set Matches=regEx.Execute(string)             '重复匹配集合
    RegExpTest = regEx.Execute(strng)      '执行搜索。
   for each match in matches                    '重复匹配集合
   RetStr=RetStr &"Match found at position "
   RetStr=RetStr&Match.FirstIndex&".Match Value is '"
   RetStr=RetStr&Match.Value&"'."&vbCRLF Next
   RegExpTest=RetStr
*/
  
EXEC @hr = sp_OASetProperty @objRegExp'Pattern'@regexp
  
IF @hr <> 0 BEGIN
   
SET @results = 0
   
RETURN @results
  
END
  
EXEC @hr = sp_OASetProperty @objRegExp'Global', false
  
IF @hr <> 0 BEGIN
   
SET @results = 0
   
RETURN @results
  
END
  
EXEC @hr = sp_OASetProperty @objRegExp'IgnoreCase'@ignorecase
  
IF @hr <> 0 BEGIN
   
SET @results = 0
   
RETURN @results
  
END 
调用对象方法
  EXEC @hr = sp_OAMethod @objRegExp'Test'@results OUTPUT, @source
  
IF @hr <> 0 BEGIN
   
SET @results = 0
   
RETURN @results
  
END
释放已创建的 OLE 对象
  EXEC @hr = sp_OADestroy @objRegExp
  
IF @hr <> 0 BEGIN
   
SET @results = 0
   
RETURN @results
  
END
 
RETURN @results
 
END

 

下面是一个简单的测试SQL语句,可以直接在查询分析器中运行。

 

DECLARE @intLength AS INTEGER
DECLARE @vchRegularExpression AS VARCHAR(50)
DECLARE @vchSourceString as VARCHAR(50)
DECLARE @vchSourceString2 as VARCHAR(50)
DECLARE @bitHasNoSpecialCharacters as BIT
 初始化变量
SET @vchSourceString = 'Test one This is a test!!'
SET @vchSourceString2 = 'Test two This is a test'
 我们的正则表达式应该类似于
 [a-zA-Z ]{}
 如: [a-zA-Z ]{10}    一个十字符的字符串

 获得字符串长度
SET @intLength = LEN(@vchSourceString)
 设置完整的正则表达式
SET @vchRegularExpression = '[a-zA-Z ]{' + CAST(@intLength as varchar+ '}'
 是否有任何特殊字符
SET @bitHasNoSpecialCharacters = dbo.find_regular_expression(@vchSourceString@vchRegularExpression,0)
PRINT @vchSourceString
IF @bitHasNoSpecialCharacters = 1 BEGIN
 
PRINT 'No special characters.'
END ELSE BEGIN
 
PRINT 'Special characters found.'
END
PRINT '**************'
 获得字符串长度
SET @intLength = LEN(@vchSourceString2)
 设置完整的正则表达式
SET @vchRegularExpression = '[a-zA-Z ]{' + CAST(@intLength as varchar+ '}'
 是否有任何特殊字符
SET @bitHasNoSpecialCharacters = dbo.find_regular_expression(@vchSourceString2@vchRegularExpression,0)
PRINT @vchSourceString2
IF @bitHasNoSpecialCharacters = 1 BEGIN
 
PRINT 'No special characters.'
END ELSE BEGIN
 
PRINT 'Special characters found.'
END
GO

 

最后得出的查询语句如下:

 

select * FROM TaskDeploy  where  1=1  And dbo.find_regular_expression(Participants,'\bsamlin\b',0= 1 

查找出包含 samlin 用户名记录的任务

但用函数的可能效率不知怎么样,下次测试一下

不知大家有没更好的方法?

 

转载请注明出处[http://samlin.cnblogs.com/

[服务器]Lighttpd 与 Apache

mikel阅读(821)

Security, speed, compliance, and flexibility — all of these describe lighttpd (pron. lighty) which is rapidly redefining efficiency of a webserver; as it is designed and optimized for high performance environments. With a small memory footprint compared to other web-servers, effective management of the cpu-load, and advanced feature set (FastCGI, SCGI, Auth, Output-Compression, URL-Rewriting and many more) lighttpd is the perfect solution for every server that is suffering load problems. And best of all it's Open Source licensed under the revised BSD license.

网址:http://www.Lighttpd.net

简介:

Web 2.0

lighttpd powers several popular Web 2.0 sites like YouTube, wikipedia and meebo. Its high speed io-infrastructure allows them to scale several times better with the same hardware than with alternative web-servers.

This fast web server and its development team create a web-server with the needs of the future web in mind:

Its event-driven architecture is optimized for a large number of parallel connections (keep-alive) which is important for high performance AJAX applications.

Downloads

[C#]Asp.Net Mvc: 浅析TempData机制

mikel阅读(864)

环境:

Windows 2008, VS 2008 SP1, ASP.NET Mvc 1.0

——————————————————————————

一. ASP.NET Mvc中的TempData

ASP.NET MVC框架的ControllerBase中存在一个叫做TempData的Property,它的类型为TempDataDictionary,顾名思 义是一个字典类。TempData在ASP.NET MVC中的作用是:可用于在Action执行过程之间传值。简单的说,你可以在执行某个Action的时候,将数据存放在TempData中,那么在下一 次Action执行过程中可以使用TempData中的数据。

如:

上面的代码中,Index()给TempData添加了一个键值对,假设我们先请求Index这个Action,接着请求Index2这个 Action,那么在Index2中,我们便可以得到之前添加到TempData的键值对。有趣的是,这时如果再次请求Index2,那么从 TempData中读到的MyName的值会是null。于是,我们需要了解TempData的生命周期。

二. TempData的生命周期

我们知道Http是无状态的,为什么TempData可以在两次请求之前传递数据呢?很明显,这个数据必定是已某种形式保存了。查看Controller类的源代码,很容易的找到了我们想要的东西:

从上面的代码可以看出,每次在执行Action之前,都要调用一下TempData.Load()方法,执行完Action之后,再调用一下TempData.Save()方法。另外这里还有一个重要成员TempDataProvider。

阅读了相关源代码之后,真相大白了。

TempData.Load()

TempData.Save()

TempDataProvider 用于暂存数据。在TempData.Load()方法中,TempDataProvider中保存的数据会被读到TempData中,供Action调用 过程中使用。Action执行完后,TempData.Save()所作的事情则是,移除TempData中任何没有被更新的键值对,然后再将 TempData中的数据保存,供下一次调用使用(注:也就是说,只有更新过的,以及新添加的键值对才能再下次request中继续使用)。为什么 TempData中的数据需要迅速被清除呢?很简单,节约内存嘛。

三. ITempDataProvider

前面提到的TempDataProvider是Controller的一个Property,它的定义是这样的:

这里我们看到了一个默认实现的SessionStateTempDataProvider类。也就是说,默认情况下,ASP.NET MVC通过SessionStateTempDataProvider来保存TempData的数据。很明显,数据是存在Session中的,也就是说, 如果你禁用SessionState,那么你的页面就报异常了。

Asp.Net Mvc生来就是被设计为易扩展的,我们可以很容易通过实现自己的ITempDataProvider类来替换这个默认的 SessionStateTempDataProvider。需要注意的是,TempDataProvider存放的数据必需具有用户独立性。

ITempDataProvider接口定义非常简单:

在MvcFutures中,你也可以找到一个CookieTempDataProvider,提供了将TempData存储在Cookie中的实现。

Copyright

作者: Tristan G
本文版权归作者和博客园共有,欢迎转载 🙂