[SQL] 加速SQL Server 2005中的表计数性能

mikel阅读(1008)

【IT168 专稿】当我们希望获得一个表中符合条件的记录的行数时,一般借助于T-SQL函数count(*)来实现。不过,如果你的表中包含了数百万条记录,返回整个表的记录数可能需要花费较长时间,会导致查询性能非常低。

  1.Count()函数

  DBA们都知道如何使用count(*)函数,也知道它对性能的影响。SQL Server需要进行一次完整的索引/表扫描,才能返回表的记录总数。建议DBA不要针对这个表使用聚合函数count(),因为它会影响数据库的性能。 接下来我们在示例数据库AdventureWorkstation的查询分析器中执行以下查询语句:

use AdventureWorks
go
select count (*) from Sales.SalesOrderDetail

  查询分析器返回的结果为121317行。
  当我们点击SQL Server 2005 Management Studio工具栏中的“显示预计的执行计划”图标时,我们可以看到如下图示:


图1 查看count(*)执行计划

  如上图所示,该函数执行过程中将从右至左执行如下操作:

  •对整个表进行索引扫描,这是一个相当耗时的过程。
  •接下来执行流聚合。

  2.新方法row_count()

  在SQL Server 2005的对象目录视图(Object Catalog Views)包含如下信息:sys.partitions和sys.allocation_units被用来获得整个表的记录总数。这个函数可以在SQL Server 2005中使用。

  sys.partitions视图
  sys.partitions视图包含了数据库中所有表和索引的每个分区在表中对应的每一行。即使SQL Server 2005中的所有表和索引并未显式分区,也至少在这个视图中包含一个分区。
  该视图包含如下字段,它们将被用于这个新方法:

字段名称
数据类型
描述
partition_id
bigint
分区的ID,它在一个数据库中是唯一的。
object_id
int
分区所属表的ID。每个表至少包含一个分区。
index_id
int
分区所属对象内索引的ID。
0:heap表
1:具有集群索引
rows
bigint
分区中表的行数。


  sys.allocation_units
视图
  sys.allocation_units视图包含了数据库中的每个分配单元在表中的每一行。 
  该视图中可以被新方法使用的字段如下:

字段名称
数据类型
描述
container_id
bigint
container_id=sys.partitions.partition_id
Type
tinyint
0 = 已删除
1 = 行内数据(除LOB之外的所有数据类型)
2 = 大型对象(LOB)数据(text、ntext、 image、xml)
3 = 行溢出数据

  在这个新用户自定义函数row_count中,[sys.partitions]视图与[sys.allocation_units]视图是相关联的。过滤器的选择基于如下标准:

  ·[sys.allocation_units].type=1,只获得行数据,不包含诸如text、ntext、image等类型的大型对象。
  ·[sys.partitions].index_id为0表示是heap表,为1表示是集群表。
  ·[sys.partitions].rows不为空。
 
  用户自定义函数row_count在每一个数据库中被执行,因此其权限为public。
函数代码
 

IF EXISTS (Select name FROM dbo.sysobjects Where id = Object_id(N'[dbo].[row_count]') )
Drop FUNCTION [dbo].[row_count]
GO
Create FUNCTION dbo.row_count (@table_name sysname)
@table_name we want to get count

RETURNS bigint
/*
——————————————————-
— Function Name: row_count
— Author: Mohamed Hassan
— Email: moh_hassan20@yahoo.com
— Development Date: 08/11/2008
— Version: 1.0
— Description: Return row count of the whole table, as a replacement for count(*) , give extra performance  at least 70% over , than count(*) for large tables with millions of rows
— SQL Server: SQL server 2005
— Usage Example: select dbo.row_count ('Sales.SalesOrderDetail')
— Copyright:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
——————————————————-
*/
AS
BEGIN
DECLARE @nn bigint number of rows

IF @table_name IS NOT NULL  
BEGIN
Select  @nn = sum(  p.rows )  
FROM sys.partitions p
LEFT JOIN  sys.allocation_units a ON  p.partition_id = a.container_id
Where
  p.index_id  
in(0,1) 0 heap table , 1 table with clustered index
       and p.rows is not null
  
and a.type = 1   row-data only , not LOB
  and p.object_id = object_id(@table_name)    
END  
RETURN (@nn)
END
GO
 
  函数用法
 
  函数row_count被调用时,需要完整表名称schema.table name作为其输入参数。
 
  例1:
 
  选择dbo.row_count(schema.[table name]),如下面代码所示:

1use AdventureWorks
2go
3select dbo.row_count ('Sales.SalesOrderDetail')

  在查询分析器中,上述语句返回的结果为12317行,与前面使用count(*)返回的结果一致,但是其执行速度更快,性能更高。

 例2:

Select top 5 TABLE_SCHEMA, TABLE_NAME, (TABLE_SCHEMA +'.'+TABLE_NAME) 'Full table name',
dbo.row_count(TABLE_SCHEMA
+'.'+TABLE_NAME) rows
FROM INFORMATION_SCHEMA.TABLES
where TABLE_TYPE ='BASE TABLE'
ORDER BY rows desc

 

  查询结果如下表:
 
 
  
  表1
 
  通过增加/删除行、截取表、批量插入/批量删除等操作详细的测试该函数,返回的结果与count(*)完全一致。
 
  3.性能评测
 
  我们将通过执行批处理命令来对比count(*)和用户自定义函数row_count的性能,如下代码所示:
 
select dbo.row_count ('Sales.SalesOrderDetail')
go
select count (*) from Sales.SalesOrderDetail
go
  当查看执行计划时,我们发现第一个查询(row_count)的开销只占整个批处理开销的7%,用于此操作以及同一子树内此操作之前的所有操作的总开销只有0.03,如下图所示:


图2

  而第二个查询(count(*))的开销则占整个批处理开销的93%,用于此操作以及同一子树内此操作之前的所有操作的总开销则为0.37,如下图所示:


图3 

  毫无疑问,用户自定义函数row_count()的性能是最好的,它占用了数据库极少的资源。两者的开销比例是93:7,这意味着row_count的性能比count(*)的性能高10倍多。
 
  根据数据表的记录数不同,该对比结果可能会有所不同,但是总体来说,我们已经从中看出,新用户自定义函数要比count(*)函数好得多。在你需要获得整个表的记录数时,这个用户自定义函数可以大大提高你的查询语句性能。
 
  4.结论
 
  SQL Server 2005的内置函数count(*)在获得整个表的记录数时非常耗时,尤其对于具有数百万条记录的表时更是如此。
 
  通过用户自定义函数“row_count”这个新方法,同样可以获得准确的结果,而且速度更快,可以获得更高的性能。
 
  该函数可以在SQL Server 2005中使用,因为这些视图只有从这个版本才开始增加。
 

[C#]公交车路线查询系统后台数据库设计——换乘算法改进与优化

mikel阅读(998)

 

公交车路线查询系统后台数据库设计系列文章

数据库下载 (该数据库已经输入了广州市350条公交车路线作为测试数据) 

 

在《查询算法》 一文中已经实现了换乘算法,但是,使用存储过程InquiryT2查询从“东圃镇”到“车陂路口”的乘车路线时,发现居然用了5分钟才查找出结果,这样的 效率显然不适合实际应用。因此,有必要对原有的换乘算法进行优化和改进。在本文中,将给出一种改进的换乘算法,相比原有的算法,改进后的算法功能更强,效 率更优。

1. “压缩”RouteT0

假设RouteT0有以下几行

clip_image002

如下图所示,当查询S1到S4的二次换乘路线时,将会产生3×2×4=24个结果

clip_image004

从图中可以看出,第1段路线中的3条线路的起点和站点都相同(第2、3段路线也是如此),事实上,换乘查询中关心的是两个站点之间有无线路可通,而不关心是乘坐什么路线,因此,可以将RouteT0压缩为:

clip_image006

如下图所示,压缩后,查询结果有原来的24条合并1组

查询结果为:

那么,为什么要对视图RouteT0进行压缩呢,原因如下:

(1)RouteT0是原有换乘算法频繁使用的视图,因此,RouteT0的数据量直接影响到查询的效率,压缩RouteT0可以减少RouteT0的数据量,加速查询效率。

(2)压缩RouteT0后,将中转站点相同的路线合并为1组,加速了对结果集排序的速度。

2.视图GRouteT0

在数据库中,将使用GRouteT0来描述压缩的RouteT0,由于本文使用的数据库的关系图与《查询算法》中有所不同,在给出GRouteT0的代码前,先说明一下:

clip_image009

主要的改变是Stop_Route使用了整数型的RouteKey和StopKey引用Route和Stop,而不是用路线名和站点名。

GRouteT0定义如下: 

create view GRouteT0
as
select 
    StartStopKey,
    EndStopKey,
    
min(StopCount) as MinStopCount,
    
max(StopCount) as MaxStopCount
from RouteT0
group by StartStopKey,EndStopKey

 

注意,视图GRouteT0不仅有StartStopKey和EndStopKey列,还有MinStopCount列,MinStopCount是指从StartStop到EndStop的最短线路的站点数。例如:上述RouteT0对应的GRouteT0为:

clip_image011

3.二次查询算法

以下是二次换乘查询的存储过程GInquiryT2的代码,该存储过程使用了临时表来提高查询效率: 

GInquiryT2

 

4.测试

(1) 测试环境

    测试数据:广州市350条公交车路线

    操作系统:Window XP SP2

    数据库:SQL Server 2000 SP4 个人版

    CPU:AMD Athlon(tm) 64 X2 Dual 2.4GHz

    内存:2G

(2)选择用于测试的的站点

二次换乘查询的select语句使用的三张表#R1,#R2,#R3,因此,这三张表的数据量直接影响到二次换乘查询使用的时间:

显然,R1的数据量由起点决定,查询起始站点对应的#R1的数据量的SQL语句如下: 

select Stop.StopName as '站点',count(StartStopKey) '#R1的数据量'
from RouteT0 full join Stop on RouteT0.StartStopKey=Stop.StopKey
group by Stop.StopName
order by count(StartStopKey) desc

 

运行结果如下:

clip_image013

显然,但起点为“东圃镇”时,#R1的数据量最大,同理可得终点和#R3数据量关系如下:

clip_image015

因此,在仅考虑数据量的情况下,查询“东圃镇”到“车陂路口”所用的时间是最长的。在下文中,将使用“东圃镇”作为起点,“车陂路口”为终点对二次查询算法进行效率测试

(3)效率测试

测试语句如下:

exec GInquiryT2 '东圃镇','车陂路口'

 

测试结果:

查询结果如下:

clip_image017

输出如下:  

测试输出

 

从消息窗口的信息可以看出,查询大概用了1秒

5.效率优化

用GInquiryT2查询“东圃镇”到“车陂路口”仅用了1秒钟,那么,还能不能再优化呢?仔细分析输出的结果,可发现查询时最耗时的并不是换乘查询语句(140ms),而是筛选出第2段查询路线的语句(825ms),如图所示:

clip_image019

那么有没有方法可以提高筛选第2段路线的效率呢?答案是肯定的。只需把GRouteT0改成实表,并创建索引就行了。改成实表后,就不需要把第2段路线缓存到临时表#R2中,修改后的GInquiryT2(重命名为GInquiryT2_1)如下:

GInquiryT2_1

 

下面,仍然用查询“东圃镇”到“车陂路口”为例测试改成实表后GInquiryT2的效率,测试语句如下:

exec GInquiryT2_1 '东圃镇','车陂路口'

 

消息窗口输出如下:

测试输出

 

从输出可以看出,大概用了250ms

6.展开路线组

GInquiryT2查询给出的结果是10组最短路线,那么,怎样才能得到最短的10条路线,显然,只需将这10组路线展开即可(展开后的路线 数>=10),而最短的10条路线必然在展开的结果中。查询10条最短路线的存储过程GInquiryT2_Expand如下: 

GInquiryT2_Expand

下面,仍然以查询“东圃镇”到“车陂路口”为例测试GInquiryT2_Expand,代码如下:

exec GInquiryT2_Expand '东圃镇','车陂路口'

查询结果如下:

消息窗口输出如下:

测试输出

 

由输出结果可看出,大约用了300ms

7.总结

下面,对本文使用的优化策略做一下总结:

(1)使用临时表;

(2)将频繁使用的视图改为表;

(3)从实际出发,合并RouteT0中类似的行,从而“压缩”RouteT0的数据量,减少查询生成的结果,提高查询和排序效率。

作者:

卢春城

转载:http://www.cnblogs.com/lucc/archive/2009/03/03/1401863.html

[应用]jimu:积木在线电脑

mikel阅读(895)

在网上看到了一个本土的 webOS :积木在线电脑 。它是一个基于 Flash 的应用,操作流畅,用户体验很好。功能也很强悍,内置了ZOHO提供的文档编辑功能和IM,还有一堆应用可以自主“安装”使用。

ijimu-screenshoot.png

我觉得我的网速不是很好,但是使用起来基本没有卡的感觉,也没有把浏览器拖慢,作为一个全 Flash 的应用,我觉得很不容易,他们真的是用心在做。这里帮忙宣传一下,表示支持。

BTW,现在它们好像还是内测阶段,想要体验的可以点击下面的链接获取邀请:

http://ijimu.cn/?register=true&inviteCode=7298b866-5ddf-4e29-af1c-7ec174cff86c

本文链接: http://www.zhuoqun.net/html/y2009/1215.html 转载请注明出处,谢谢。

[SQL]SQL Server 2008中的用户自定义表类型

mikel阅读(906)

目前的批量删除和批量更新,很是烦人,解决方案无非三种:

XML,SQL自定义函数split,和CLR实现split。这几种都比较烦人,代码很多,维护麻烦,很不爽。

现在SQL2008新增的一个功能,我也不知道中文名怎么翻译,暂且叫他表参数吧。

大家可以看看示例:

这个就是用户定义的表类型:

 

 

然后给他定义一个类型:

 


 ================================
 Create User-defined Table Type
 ================================
USE Test
GO
 Create the data type
Create TYPE dbo.MyType AS TABLE 
(
    col1 
int NOT NULL
    col2 
varchar(20NULL
    col3 
datetime NULL
    
PRIMARY KEY (col1)
)
GO

 

可以看到,生成的表类型的组成情况,并且居然可以给表类型建立索引,呵呵

 

这个是操作 表类型的脚本:

 


DECLARE @MyTable MyType
Insert INTO @MyTable(col1,col2,col3)
VALUES (1,'abc','1/1/2000'),
    (
2,'def','1/1/2001'),
    (
3,'ghi','1/1/2002'),
    (
4,'jkl','1/1/2003'),
    (
5,'mno','1/1/2004')
    
Select * FROM @MyTable 

 

 

下面演示如何将表参数作为一个存储过程参数传递,以及ADO.NET的代码

sql部分:

 


USE [Test]
GO
Create TABLE [dbo].[MyTable] (
    
[col1] [int] NOT NULL PRIMARY KEY,
    
[col2] [varchar](20NULL,
    
[col3] [datetime] NULL,
    
[UserID] [varchar] (20NOT NULL
    )    
GO
Create PROC usp_AddRowsToMyTable @MyTableParam MyType READONLY,
    
@UserID varchar(20AS
    
    
Insert INTO MyTable([col1],[col2],[col3],[UserID])
    
Select [col1],[col2],[col3],@UserID    
    
FROM @MyTableParam
    
GO

 

 

如何在sql中调用此存储过程:

 


DECLARE @MyTable MyType
Insert INTO @MyTable(col1,col2,col3)
VALUES (1,'abc','1/1/2000'),
    (
2,'def','1/1/2001'),
    (
3,'ghi','1/1/2002'),
    (
4,'jkl','1/1/2003'),
    (
5,'mno','1/1/2004')
EXEC usp_AddRowsToMyTable @MyTableParam = @MyTable@UserID = 'Kathi'
Select * FROM MyTable

 

其中还涉及到一个权限问题,需要执行以下代码:

 


GRANT EXECUTE ON TYPE::dbo.MyType TO TestUser;

 

从.net app那调用此存储过程:

 


'Create a local table
Dim table As New DataTable("temp")
Dim col1 As New DataColumn("col1", System.Type.GetType("System.Int32"))
Dim col2 As New DataColumn("col2", System.Type.GetType("System.String"))
Dim col3 As New DataColumn("col3", System.Type.GetType("System.DateTime"))
table.Columns.Add(col1)
table.Columns.Add(col2)
table.Columns.Add(col3)
        
'Populate the table
For i As Integer = 20 To 30
    
Dim vals(2As Object
    vals(
0= i
    vals(
1= Chr(i + 90)
    vals(
2= System.DateTime.Now
    table.Rows.Add(vals)
Next

 

 

 


'Create a command object that calls the stored proc
Dim command As New SqlCommand("usp_AddRowsToMyTable", conn)
command.CommandType = CommandType.StoredProcedure
'Create a parameter using the new type
Dim param As SqlParameter = command.Parameters.Add("@MyTableParam", SqlDbType.Structured)
command.Parameters.AddWithValue("@UserID""Kathi")

 

 


'Set the value of the parameter
param.Value = table
'Execute the query
command.ExecuteNonQuery()

 

详情可以参看:

http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters

[SQL]SQL Server 2005中xml类型和函数的简单应用

mikel阅读(915)

大家都知道SQL Server 2005新增了xml字段类型,我们可以利用它来实现批量操作数据库的需要,减少应用程序频繁、反复的建立数据库连接的情况发生,比如批量删除,我们可以在应用程序中构建如下xml:

<Delete><ID>1</ID><ID>2</ID><ID>3</ID></Delete>

在数据库中可以通过下面的脚本获得这些ID:

Select 
        T.ID.value(
'.''int'As ID
From
        
@xmlParam.nodes('/Delete/ID'as T(ID)

运行结果如下:

将xml类型作为存储过程的参数,批量删除的存储过程如下:

Create Procedure pro_Batch_Delete
@xmlParam xml
As

Delete from 
    t_TableName 
Where ID in 
    (
Select 
        T.ID.value(
'.''int'As ID
    
From
        
@xmlParam.nodes('/Delete/ID'as T(ID))

大家也知道,DataSet中的数据可以直接序列化成xml,通过dataSet.GetXml(),我们可以将这个xml作为存储过程参数,实现稍微复杂的功能,比如批量插入数据,在数据库中怎样将这个xml转换为表呢,我们先看DataSet序列化的xml结构:

<DataSet>
  
<Table><ID>1</ID><Count>1</Count></Table>
  
<Table><ID>2</ID><Count>2</Count></Table>
  
<Table><ID>3</ID><Count>3</Count></Table>
</DataSet>

当然也可以包含多个表,这里我们以一个表举例,转换脚本如下:

Declare @Xml xml
Set @Xml = '
<DataSet>
  <Table><ID>1</ID><Count>1</Count></Table>
  <Table><ID>2</ID><Count>2</Count></Table>
  <Table><ID>3</ID><Count>3</Count></Table>
</DataSet>
'
Select 
    T2.ID.value(
'.''int'As ID, T3.[Count].value('.','int'As [Count]
From 
    (
Select  
        T.Records.query(
'ID'As ID, 
        T.Records.query(
'Count'As [Count]
    
From 
        
@Xml.nodes('/DataSet/Table'As T(Records)
    ) 
As T1
Cross Apply T1.ID.nodes('ID'As T2(ID)
Cross Apply T1.[Count].nodes('Count'As T3([Count])

执行此脚本,可以得到我们想要的表:

批量插入记录的存储过程如下:

Create Procedure pro_Batch_Insert
@xmlParam xml
As

Insert Into t_TableName (ID, [Count])
(
    
Select 
        T2.ID.value(
'.''int'As ID, T3.[Count].value('.','int'As [Count]
    
From 
        (
Select  
            T.Records.query(
'ID'As ID, 
            T.Records.query(
'Count'As [Count]
        
From 
            
@xmlParam.nodes('/DataSet/Table'As T(Records)
        ) 
As T1
    
Cross Apply T1.ID.nodes('ID'As T2(ID)
    
Cross Apply T1.[Count].nodes('Count'As T3([Count])
)

同样,可以实现批量update的功能,这里不具体列举了,实现核Insert是基本相同的。
这里只使用部分SQL2005关于xml几个谓词和函数,在MSDN中都有说明,还有其他更强大的功能等大家一起去发掘.

[资源]国外的模板素材网站

mikel阅读(655)

下面推荐这几个国外模板素材网站:(如果大家有更好希望不要吝啬,拿出来大家共同分享)

FreeCssTemplates(免费下载网页模板)   :http://www.freecsstemplates.org/ 

这个网站主要用于公开的,无需注册,而且分类清楚,页面简洁找东西很方便,但唯一的不足是东西有点少

FreeWebTemplates(免费网页模板):http://www.freewebtemplates.com/

分类很清晰,每个分类的模板我认为做的都很专业,下载就直接修改使用,而且重要这个网站常常更新最新的模板(应该可以看出这个公司的业务量不错)

TemplateMonster(模板怪物圈):http://www.templatemonster.com/

这个布局太过混乱了,但素材还不错,仅作参考

CoolHomePage(门户主页模板):http://www.coolhomepages.com/ 

DesignGalaxy:http://www.designgalaxy.net/  主要是一些模板设计爱好者聚集地方

AplusTemplates:http://www.aplustemplates.com/  

WebDesignHelper:http://www.webdesignhelper.co.uk/   

TemplateHeaven:http://www.templateheaven.com/ 

CoolTemplates:http://www.coolesttemplates.com/

[C#]从零开始学习ASP.NET MVC(一) 开天辟地入门篇

mikel阅读(674)

一.摘要

和自身水平有关, 我总喜欢写入门级别的文章.比如虽然做项目用过一个内部的MVC框架, 但是当看到ASP.NET MVC时我却还是不知道从哪里入手开始学习的好.于是我写下这篇入门级的系列文章,  将老赵(Jeffrey Zhao)的ASP.NET MVC系列视频课程学到的知识再理解和再消化, 让最笨的人也能跟着我学懂ASP.NET MVC

二.人物简介

本 文的很多资料都来自老赵 , Jeffrey Zhao. 此人相信不必我多说了吧, 高手,前辈,偶像, 对老赵的敬仰如滔滔江水连绵不绝, 如黄河泛滥一发不可收拾……(省下口水文字若干). 在我搜索有没有更简单并且系统的介绍ASP.NET MVC的资料时, 搜索到了老赵在Webcast上面的系列视频教程. 当然了高手的讲解都是比较全面的, 有时侯听了1小时, 真正理解的只有几句话.于是有了一边学习一边写此系列文章的想法.其中的知识都是从老赵的教程中学习到得.

三.Hello ASP.NET MVC

现在开始跟着我一步一步学习ASP.NET MVC.在什么都不知道之前, 还是让我们先下载一个ASP.NET MVC程序. 当看到程序运行后, 我的心才会踏实很多.

首先去ASP.NET MVC的官网看看:

http://www.asp.net/mvc/

这里我们可以下载ASP.NET MVC的安装文件.目前最新版本是RC1 (注:RC, 即Release Candidate版本, 是距离正式版最近的一个版本.一般要经历RC1,2,3后即推出正式版)

另外我们可以在CodePlex上获取源代码:

http://www.codeplex.com/aspnet

在官网上下载ASP.NET MVC安装文件, 一路回车完成安装. 启动VS2008 SP1(不是VS2008?不是SP1?作为一个专业的IT人士,水平可以菜,软件咱一定要用新的!去找个新版本吧!),点击新建,在Web中可以找到:

image

点击确定即创建了一个默认的MVC项目.同时还会创建一个测试项目. 这些我现在都不懂也不关注, 直接将Web项目中的default.aspx设置为启动页, 运行项目, 一个ASP.NET MVC的项目已经运行在我的电脑上了:

image

而且那个Home和About还能点击!虽然现在我什么都不懂, 不过看着能运行的实例, 心里就踏实多了!

四.学习MVC的概念

老赵的视频教程第一讲是"MVC模式与ASP.NET MVC框架概述".主要讲解MVC的概念.那么让我们先来学习概念.

我是如此理解MVC的:

我们现在的一个ASP.NET页面通常需要做这些事情:

1.获取一个页面需要的数据. 

2.在页面的Page_Load(页面加载)方法中为我们的页面控件绑定数据

3.浏览器显示页面

MVC即Model, View, Controller

Model就是我们1中获取的网页需要的数据

Controller就是我们获取数据,然后将数据绑定到页面控件的这个业务流程.不十分正确但是可以先这样理解: Controller就是我们的Page_Load方法逻辑.

View就是我们的aspx页面,注意这是一个不包含后台代码文件的aspx页面.(其实带有.asp.cs文件也不会有编译错误,但是这样写代码就违背了MVC的设计初衷)

下面这张图很好的概括了MVC的作用:

image

一 个URL请求, ASP.NET MVC引擎会分析这个URL要使用那个Controller, 这个Controller(实际上真正的方法是Controller的Action)从数据库或者其他数据源获取数据,通常这些数据是一个业务的模型类 (即MVC中的Model). Controller将Model对象传递给页面(即MVC中的View),  页面显示在浏览器上.(这一步是ViewEngine的功能, 我们一般的ASPX页面使用的是WebForm的ViewEngine,当然也可以替换.)

五.学习实例首页

简单的概念也有了.实例也能运行了.现在就是看看这个实例是如何使用ASP.NET MVC的.从首页下手.

1.寻找入口方法

首 页网站的地址应该是 http://localhost:1847/home/index , 为什么地址不是具体的页面但是最后却将请求提交给了view/home/Index.aspx 页面? 很明显是使用了URL重写. ASP.NET中叫做UrlRouting,对应的程序集是System.Web.Routing, 打开项目的Global.asax.cs文件, 会找到我们建立的页面重写规则:

        public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",                                              // Route name
"{controller}/{action}/{id}",                           // URL with parameters
new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}

 

 

关于Routing的深入了解将在以后介绍, 这里我们完全可以照葫芦画瓢, 按照实例添加新的重写规则.

最后, http://localhost:1847/home/index 就被解析为:

Controller为Home, Action为Index, 没有id参数.

2.寻找Controller

在Controllers文件夹下我们可以找到HomeController.cs, 这里使用了一个约定, 就是如果URL中获取到的Controller名字是Home, 则他的Controller类名就是HomeController. 在URL中的名字后加上”Controller”.

实例中Controller都放在Controllers文件夹, 所以我们可以按照命名约定很容易就可以找到HomeController类

3.寻扎Action

打开HomeControllers.cs, 发现里面有两个方法:

        public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}

其中的Index和About都是Action.这个Action是个什么东东呢?目前我只知道一个Controller可以包含多个Action, MVC模式中Controller角色的具体实现逻辑都是在Action中的.

因为我们的Action是Index, 所以自然就要调用Index()方法.这里将"Webcome to ASP.NET MVC!", 这里的Model角色就是这句话, 也就是一个字符串对象.

Controller的一个重要作用就是将Model对象传递给View,也就是具体的页面. 传递的方法就是将这个字符串放到ViewData这个集合对象中, 然后在页面上也调用这个对象获取数据. 这个ViewData 对象一定是静态的, 或者至少是和HttpContext对象关联的, 否则到了View页面上是不能够调用的.但是也不知道ASP.NET MVC是在什么时候创建了这个对象.有心深入的一会去源代码里找找就能找到答案了.

4.寻找View

Model有了,数据有了, 接下来要跳转到View去显示数据了.第一个问题就是如何从Controller中跳到View上? return View() 这句话实现了此功能.其实如果这样写大家会更清楚:

        public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View("index");
}

View方法中可以带一个名字, 这个名字就是View的名字.如果把index改成about,那么访问/home/index就会跳转到about页!

虽然知道了我们要返回到名称是"index"的View上, 但是这个View的页面在哪里呢?在Web中有一个Views文件夹:

image

这里面存放的都是View对象, 也就是只有显示功能的aspx页面文件.但是aspx文件要遵循约定: Views下面要按照Controller创建文件夹, 比如HomeController就对应Home文件夹, 然后在里面添加view, 比如index.aspx, 那么在HomeController中返回到名为Index的View对象实际就会返回Views/Home/Index.aspx页面.

如果不写View的名字, 则认为Action的名称就是View的名称.

5.页面展示

最后的工作就是View页面使用Model数据完整页面显示工作, 在index.aspx这个没有后台代码的View对象中, 通过ViewData对象获取Model:

<%= Html.Encode(ViewData["Message"]) %>

 

接下来ViewEngine即页面引擎会将aspx中的HTML部分以及上面的数据部分和在一起返回给浏览器.

关于View对象我注意到此页面是继承自System.Web.Mvc.ViewPage而不是直接继承自System.Web.UI.Page, 而这个ViewData对象就是ViewPage中的一个属性. 这里的ViewData一定是页面级别的,当页面编译完毕这个对象就会被注销(HTTP是无状态的协议,每次请求其实都是生成一个新的ViewPage对 象).

六.总结

虽然仅仅是第一篇入门文章, 但是是不是觉得已经会用ASP.NET MVC了? 虽然学习不可以骄傲, 但是可以自豪的是ASP.NET MVC我已经不再陌生, 即使不能驾轻就熟, 偶尔说出来几个概念,给你解释解释啥是MVC, 还是很能吓唬人的. 这篇文章相当于老赵前两篇视频的知识. 本着每日一篇的原则, 我将会继续学习老赵的系列教程,  将自己的理解贡献出来. 不求最深, 但求易懂.

[C#]从零开始学习ASP.NET MVC(二) 识别URL的Routing组件

mikel阅读(766)

一.摘要

本篇文章从基础到深入的介绍ASP.NET MVC中的Routing组件. Routing翻译过来是"路由选择", 负责ASP.NET MVC的第一个工作:识别URL, 将一个Url请求"路由"给Controller. 由于今天下午参加了博客园北京俱乐部的聚会, 所以本篇文章的完工时间晚了点, 还好也是在今天发表, 总算兑现了"每日一篇"的承诺. 不久丁学就会发布北京博客园聚会活动的资料了, 我在这里先预告了!

二.承上启下

第一篇文章中我们已经学会了如何使 用ASP.NET MVC, 虽然其中还有很多的细节没有深入了解, 但是对基本的处理流程已经有了认识:来了一个Url请求, 从中找到Controller和Action的值, 将请求传递给Controller处理. Controller获取Model数据对象, 并且将Model传递给View, 最后View负责呈现页面.

而Routing的作用就是负责分析Url, 从Url中识别参数, 如图:

image

这一讲就让我们细致的了解System.Web.Routing及其相关的扩展知识.

三.Routing的作用

第一讲中实例的首页地址是: localhost/home/index

我们发现访问上面的地址, 最后会传递给 HomeController中名为index的action(即HomeController类中的index方法).

当然服务器端不会自己去实现这个功能,  关键点就是在Global.asax.cs文件中的下列代码:

        public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",                                              // Route name
"{controller}/{action}/{id}",                           // URL with parameters
new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}

 

回来看我们的Url: localhost/home/index

localhost是域名, 所以首先要去掉域名部分: home/index

对应了上面代码中的这种URL结构: {controller}/{action}/{id}

因为我们建立了这种Url结构的识别规则, 所以能够识别出 Controller是home, action是index, id没有则为默认值"".

这就是Routing的第一个作用:

1.从Url中识别出数据.比如controller,action和各种参数.

如果跟踪程序, 接下来我们会跳转到HomeController中的Index()方法.  这是Routing内部为实现的第二个作用:

2.根据识别出来的数据, 将请求传递给Controller和Action.

但从实例中我们并不知道Routing如何做的这部份工作.第五部分我做了深入讲解.

四.Routing的使用

在分析Routing的实现原理前, 先学习如何使用Routing为ASP.NET MVC程序添加路由规则.

1. 使用MapRoute()方法.

这是最简单的为ASP.NET MVC添加识别规则的方法.此方法有如下重载:

MapRoute( string name, string url);
MapRoute( string name, string url, object defaults);
MapRoute( string name, string url, string[] namespaces);
MapRoute( string name, string url, object defaults, object constraints);
MapRoute( string name, string url, object defaults, string[] namespaces);
MapRoute( string name, string url, object defaults, object constraints, string[] namespaces);
 

name参数:

规则名称, 可以随意起名.当时不可以重名,否则会发生错误:
路由集合中已经存在名为“Default”的路由。路由名必须是唯一的。

url参数:

url获取数据的规则, 这里不是正则表达式,  将要识别的参数括起来即可, 比如: {controller}/{action}

最少只需要传递name和url参数就可以建立一条Routing(路由)规则.比如实例中的规则完全可以改为:

            routes.MapRoute(
"Default",
"{controller}/{action}");

defaults参数:

url参数的默认值.如果一个url只有controller: localhost/home/

而且我们只建立了一条url获取数据规则: {controller}/{action}

那么这时就会为action参数设置defaults参数中规定的默认值. defaults参数是Object类型,所以可以传递一个匿名类型来初始化默认值:

new { controller = "Home", action = "Index" }

实例中使用的是三个参数的MapRoute方法:

            routes.MapRoute(
"Default",                                              // Route name
"{controller}/{action}/{id}",                           // URL with parameters
new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
);

constraints参数:

用来限定每个参数的规则或Http请求的类型.constraints属性是一个RouteValueDictionary对象,也就是一个字典表, 但是这个字典表的值可以有两种:

  • 用于定义正则表达式的字符串。正则表达式不区分大小写。

  • 一个用于实现 IRouteConstraint 接口且包含 Match 方法的对象。

  • 通过使用正则表达式可以规定参数格式,比如controller参数只能为4位数字:

    new { controller = @"\d{4}"}
     

    通过第IRouteConstraint 接口目前可以限制请求的类型.因为System.Web.Routing中提供了HttpMethodConstraint类, 这个类实现了IRouteConstraint 接口. 我们可以通过为RouteValueDictionary字典对象添加键为"httpMethod", 值为一个HttpMethodConstraint对象来为路由规则添加HTTP 谓词的限制, 比如限制一条路由规则只能处理GET请求:

    httpMethod =  new HttpMethodConstraint(  "GET", "POST"  )
    

    完整的代码如下:

                routes.MapRoute(
    "Default",                                              // Route name
    "{controller}/{action}/{id}",                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" },  // Parameter defaults
    new { controller = @"\d{4}" , httpMethod = new HttpMethodConstraint( "GET", "POST" ) }
    );

    当然我们也可以在外部先创建一个RouteValueDictionary对象在作为MapRoute的参数传入, 这只是语法问题.

    namespaces参数:

    此参数对应Route.DataTokens属性. 官方的解释是:

    获取或设置传递到路由处理程序但未用于确定该路由是否匹配 URL 模式的自定义值。

    我目前不知道如何使用. 请高手指点

    2.MapRoute方法实例

    下面通过实例来应用MapRoute方法. 对于一个网站,为了SEO友好,一个网址的URL层次不要超过三层:

    localhost/{频道}/{具体网页}

    其中域名第一层, 频道第二层, 那么最后的网页就只剩下最后一层了. 如果使用默认实例中的"{controller}/{action}/{其他参数}"的形式会影响网站SEO.

    假设我们的网站结构如下:

    image

    下面以酒店频道为例, 是我创建的Routing规则:

            public static void RegisterRoutes(RouteCollection routes)
    {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    #region 酒店频道部分
    // hotels/list-beijing-100,200-3
    routes.MapRoute(
    "酒店列表页",
    "hotels/{action}-{city}-{price}-{star}",
    new { controller = "Hotel", action = "list", city = "beijing", price="-1,-1", star="-1" },
    new { city=@"[a-zA-Z]*",price=@"(\d)+\,(\d)+", star="[-1-5]"}
    );
    //hotels/所有匹配
    routes.MapRoute(
    "酒店首页",
    "hotels/{*values}",
    new { controller = "Hotel", action = "default", hotelid = "" }
    );
    #endregion
    //网站首页.
    routes.MapRoute(
    "网站首页",
    "{*values}",
    new { controller = "Home", action = "index"}
    );
    }
     

    实现的功能:

    (1)访问 localhost/hotels/list-beijing-100,200-3 会访问酒店频道的列表页,并传入查询参数

    (2)访问 localhost/hotels 下面的任何其他页面地址, 都会跳转到酒店首页.

    (3)访问 localhost 下面的任何地址, 如果未匹配上面2条, 则跳转到首页.

    简单总结:

    (1)Routing规则有顺序(按照添加是的顺序), 如果一个url匹配了多个Routing规则, 则按照第一个匹配的Routing规则执行.

    (2)由于上面的规则, 要将具体频道的具体页面放在最上方, 将频道首页 和 网站首页 放在最下方.

    (3) {*values} 表示后面可以使任意的格式.

    3.使用Route类

    MapRoute方法虽然简单, 但是他是本质也是通过创建Route类的实例, 为RouteCollection集合添加成员.

    下载最新版本的MSDN-Visual Studio 20008 SP1, 已经可以找到Route类的说明.

    创建一个Route类实例,最关键的是为以下几个属性赋值:

    属性名称 说明 举例
    Constraints 获取或设置为 URL 参数指定有效值的表达式的词典。 {controller}/{action}/{id}
    DataTokens 获取或设置传递到路由处理程序但未用于确定该路由是否匹配 URL 模式的自定义值。 new RouteValueDictionary { { "format", "short" } }
    Defaults 获取或设置要在 URL 不包含所有参数时使用的值。 new { controller = "Home", action = "Index", id = "" }
    RouteHandler 获取或设置处理路由请求的对象。 new MvcRouteHandler()
    Url 获取或设置路由的 URL 模式。 new { controller = @"[^\.]*" }

    这些属性除了RouteHandler以外, 其他的都对应MapRoute方法的参数.RouteHandler是实现了IRouteHandler接口的对象.关于此接口的作用在第五部分Routing深入解析中做讲解.

    五.Routing深入解析

    对于一个一般开发人员来说, 上面的知识已经完全足够你使用ASP.NET MVC时使用Routing了.

    接下来的部分我将深入Routing的机制讲解Routing的高级应用.但是因为是"高级应用", 加上这篇文章已经太长了, 再加上马上今天就过去了, "每日一篇"的承诺一定要兑现的, 所以不会对所有细节进行讲解. 或者也可以略过此部分.

    Routing如何将请求传递给Controller?上面讲解Routing作用的时候, 我们就分析出Routing会将请求传递给Controller, 但是Routing如何做的这部份工作我们却看不到.关键在于MapRoute()这个方法封装了具体的细节.

    虽然MapRoute方法是RouteCollection对象的方法,但是却被放置在System.Web.Mvc程序集中, 如果你的程序只引用了System.Web.Routing, 那么RouteCollection对象是不会有MapRoute方法的. 但是如果你同又引用了System.Web.Mvc, 则在mvc的dll中为RouteCollection对象添加了扩展方法:

           public static void IgnoreRoute(this RouteCollection routes, string url);
    public static void IgnoreRoute(this RouteCollection routes, string url, object constraints);
    public static Route MapRoute(this RouteCollection routes, string name, string url);
    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults);
    public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces);
    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints);
    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces);
    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces);

    RouteCollection是一个集合,他的每一项应该是一个Route对象. 但是我们使用MapRoute时并没有创建这个对象, 这是因为当我们将MapRoute方法需要的参数传入时, 在方法内部会根据参数创建一个Route对象:

            public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) {
    if (routes == null) {
    throw new ArgumentNullException("routes");
    }
    if (url == null) {
    throw new ArgumentNullException("url");
    }
    Route route = new Route(url, new MvcRouteHandler()) {
    Defaults = new RouteValueDictionary(defaults),
    Constraints = new RouteValueDictionary(constraints)
    };
    if ((namespaces != null) && (namespaces.Length > 0)) {
    route.DataTokens = new RouteValueDictionary();
    route.DataTokens["Namespaces"] = namespaces;
    }
    routes.Add(name, route);
    return route;
    }

    上面就是MapRoute方法的实现, 至于在创建Route对象时第二个参数是一个MvcRouteHandler, 它是一个实现了IRouteHandler接口的类. IRouteHandler十分简单只有一个方法:

    IHttpHandler GetHttpHandler(RequestContext requestContext);

    参数是一个RequestContext 类实例, 这个类的结构也很简单:

        public class RequestContext
    {
    public RequestContext(HttpContextBase httpContext, RouteData routeData);
    public HttpContextBase HttpContext { get; }
    public RouteData RouteData { get; }
    }

    其中的一个属性RouteData就包含了Routing根据Url识别出来各种参数的值, 其中就有Controller和Action的值.

    归根结底, ASP.NET MVC最后还是使用HttpHandler处理请求. ASP.NET MVC定义了自己的实现了IHttpHandler接口的Handler:MvcHandler,  因为MvcRouteHandler的GetHttpHandler方法最后返回的就是MvcHandler. 

    MvcHandler的构造函数需要传入RequestContext 对象, 也就是传入了所有的所有需要的数据, 所以最后可以找到对应的Controller和Action, 已经各种参数.

    六.测试Routing

    因为一个Url会匹配多个routing规则, 最后常常会遇到规则写错或者顺序不对的问题.于是我们希望能够看到Url匹配Routing的结果.

    其中最简单的办法就是使用RouteDebug辅助类. 这个类需要单独下载dll组件, 我将此组件的下载放在了博客园上:

    http://files.cnblogs.com/zhangziqiu/RouteDebug-Binary.zip

    解压缩后是一个DLL文件, 将这个DLL文件添加到项目中并且添加引用.

    使用方法很简单, 只需要在Application_Start方法中添加一句话:

    RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);

    比如下面是我的示例中的代码:

            protected void Application_Start()
    {
    RegisterRoutes(RouteTable.Routes);
    RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
    }

    现在你访问任何URL, 都会出现RouteDebug页面, 如下:

    image

    其中不仅有你的所有Routing规则, 还显示了是否匹配.并且按照顺序列出. 还有识别的参数列表.

    当你不想测试Routing规则的时候则注释掉这一段, 即可回复跳转到View对象上.

    七.总结

    本文讲解了ASP.NET MVC中一个关键的组件:Routing的使用. System.Web.Routing在Framework3.5 SP1中已经集成, 也就是说虽然我们还没有ASP.NET MVC的正式版, 但是Routing组件却已经提早发布了. 因为Routing是一个相对独立的组件, 不仅能和ASP.NET MVC配额使用, 也可以用于任何需要URL路由的项目. 另外Routing的作用和Url重写(Url Rewrite)是有区别的, 你会发现Routing和Url Rewrite相比其实很麻烦, 无论是添加规则还是传递参数.对UrlRewite感兴趣的可以去寻找UrlRewrite.dll这个组件, 很简单很强大, 有关两者的异同以及如何使用UrlRewrite这里不在多说了.

    另外今天博客园北京俱乐部聚会, 听了2位大师的讲座, 见到了至今没记住名字和样子的同行们, 很是激动和兴奋. 期待俱乐部领导将活动照片和资料放出!

    本文的示例下载地址:

    http://files.cnblogs.com/zhangziqiu/Demo-2.rar