[转载]也谈WEB打印(二):简单的分析一下IE的打印原理并实现简单的打印和预览

mikel阅读(980)

[转载]也谈WEB打印(二):简单的分析一下IE的打印原理并实现简单的打印和预览 – 悼念死难同胞 – 博客园.

在《也谈WEB打印():目前的几种方式及我们的任务中,分析了一下当前Web打印的几种方式以及我们所遇到的问题,并提出了我们的要求,本文简单的分析一下IE的打印原理,并实现简单的打印和预览功能。
首先,我们介绍一下IE架构:

IExplore.exe位于最上层,他是一个很小的应用程序,当IE装载的时候他就被实例化。该可执行程序使用IE的各种组件来执行导航,历史记录维护,收藏夹维护,HTML解析和渲染等,同时为独立的浏览器提供工具栏和框架。IExplorer.exeShdocvw.dll 组件的直接宿主。

Shdocvw.dll依次寄宿Mshtml.dll,当有其他的活动文档组件(例如MS Office应用),当用户导航到这些特定的文档的时候,可以就地装入浏览器。Shdocvw.dll 提供这些和导航联系在一起的功能:就地链接、收藏夹和历史记录管理、PICS支持。该动态链接库也向其宿主暴露了一些接口,以允许这些宿主可以把他当作ActiveX控件而分别寄宿。

Shdocvw.dll中有一个接口叫做IWebBrowser2,我们所见到的IE, 其实基本上就是对该接口的一个包装。他有一个很重要的成员函数“ExecWB”,其原型如下:

HRESULT ExecWB(
OLECMDID cmdID,
OLECMDEXECOPT cmdexecopt,
VARIANT *pvaIn,
VARIANT *pvaOut
);

通过给这个函数的cmdIDcmdexecopt参数指定适当的值,我们几乎可以做能够IE界面上所做的所有事情。下面举例说明如何在js中调用该函数:

html页面中加 入如下语句:
<OBJECT classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2 height=0 id=WebBrowser width=0></OBJECT>
<input type=”button” value=”直接打印” onclick=”browser.ExecWB(6,1);”/>
<input type=”button” value=”打印预览” onclick=”browser.ExecWB(7,1);”/>
<input type=”button” value=”页面设置” onclick=”browser.ExecWB(8,1);”/>
然后再单击3ButtonIE就会相应的执行打印,打印预览,页面设置3个动作。

关于ExecWB的 更多使用,在MSDN中有更多的描述,在此不再多说。该命令实际上就是调用IOleCommandTarget 接口的Exec函 数,该函数的原型如下:
HRESULT Exec(
const GUID *pguidCmdGroup,  // Pointer to command group
DWORD nCmdID,               // Identifier of command to execute
DWORD nCmdExecOpt,         // Options for executing the command
VARIANTARG *pvaIn,           // Pointer to input arguments
VARIANTARG *pvaOut          // Pointer to command output
);

如果我们获得了一个IWebBrowser2的实例,并把他转换为IOleCommandTarget接口,然后给Exec函 数赋予适当的值,就可以实现我们的预期的功能了。MSDN上说必须用C++才能实现这些功能,实际上用DelphiC#一样可以实现这些功能。下面我们就实现一个C#的简 单实例。

首先,我们新建一个WinForm项目,然后在窗体上添加加一个控件,使得主界面看起来像下图 所示:

其中最下一个是System.Windows.Forms.WebBrowser控件,命名为Browser
然后我们定义如下的InterfaceClass
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
[ComImport(), Guid(“B722BCCB-4E68-101B-A2BC-00AA00404770”),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleCommandTarget
{
[PreserveSig()]
int QueryStatus([In, MarshalAs(UnmanagedType.Struct)] ref Guid
pguidCmdGroup, [MarshalAs(UnmanagedType.U4)] int cCmds,
[In, Out] IntPtr prgCmds, [In, Out] IntPtr pCmdText);
[PreserveSig()]
int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdExecOpt,
[In, MarshalAs(UnmanagedType.LPArray)] object[] pvaIn,
[In, Out, MarshalAs(UnmanagedType.LPArray)] object[] pvaOut);
}

public
enum OLECMDEXECOPT
{
OLECMDEXECOPT_DODEFAULT,
OLECMDEXECOPT_PROMPTUSER,
OLECMDEXECOPT_DONTPROMPTUSER,
OLECMDEXECOPT_SHOWHELP
}

public static class GlobalConst
{
public const int MSOCMDEXECOPT_DONTPROMPTUSER = 2;
public const int IDM_PRINT = 0x1b;
public const int IDM_PRINTPREVIEW = 0x7d3;
public static readonly Guid CGID_MSHTML = new Guid(“DE4BA900-59CA-11CF-9592-444553540000”);
public static readonly Guid IID_OleCommandTarget = new Guid(“B722BCCB-4E68-101B-A2BC-00AA00404770”);
}

我们给“GO”按钮 添加Click事件处理代码:
this.Browser.Navigate(txtAddr.Text);
注意,txtAddr.Text所代表的地址必须存在,否则,在WebBrowser控件中会出现“无法显示网页”的错误。不过对于我们的演示并没有什么影响。
然后我们给“Preview”按钮添加Click事 件处理代码:
IOleCommandTarget pCmdTarg = Browser.ActiveXInstance as IOleCommandTarget;
Guid CGID_MSHTML = GlobalConst.CGID_MSHTML;
string vTemplatePath = txtTemplateAddr.Text;
pCmdTarg.Exec(ref CGID_MSHTML,
GlobalConst.IDM_PRINTPREVIEW,
(uint)OLECMDEXECOPT.OLECMDEXECOPT_PROMPTUSER,
null,
null);

上述代码中中的Browser.ActiveXInstance实际上就是一个IWebBrowser2接口的实现。
启动程序,在Document Address文本框中输入www.cnblogs.com,单击Go按钮, 然后单击Preview按钮,你看到了什么?
我这里效果如下图所示:

然后我们再给Print按钮添加Click事件的处理代码:
IOleCommandTarget pCmdTarg = Browser.ActiveXInstance as IOleCommandTarget;
Guid CGID_MSHTML = GlobalConst.CGID_MSHTML;
string vTemplatePath = txtTemplateAddr.Text;
pCmdTarg.Exec(ref CGID_MSHTML,
GlobalConst.IDM_PRINT,
(uint)OLECMDEXECOPT.OLECMDEXECOPT_PROMPTUSER,
null,
null);

启动程序,在Document Address文本框中输入www.cnblogs.com,单击Go按钮, 然后单击Print按钮,你看到了什么?
我这里效果如下图所示:

到现在为止,我们获取了一个IWebBrowser2的实例,并且可以操 作调用他的PrintPrint Preview功能了。但是,我们还不能对如何打印进行控制。
那么我们如何对打印进行控制呢?关键就是IOleCommandTarget接口Exec函数的第4个参数,该参数指定了IWebBrowser2进行打印的模板,如果为NULL,那么IWebBrower2使用IE缺省的模板,如果指定的模板文件不存在,那么单击Preview的时候就会显示一个空的环境,什么都不显示,就像下图所示 的这样:

好了,今天就讲到这里,在下篇文章,我们继续讨论如何实现自己的打印模板。
欢迎大家拍砖。

[转载]也谈WEB打印(-):目前的几种方式及我们的任务

mikel阅读(977)

[转载]也谈WEB打印(-):目前的几种方式及我们的任务 – 悼念死难同胞 – 博客园.

B/S平台下的MIS软件,打印是不可缺少的功能,好的报表输出,能让你的产品在用户的心中增加不少的分量。但是打印模块 所花的时间也是非常的多的。其实现方式有如下几种:

1、使用Crystal Report等通用报表组件来完成。

优点:功能强大,对于CS/BS都可以通用,Crystal Report等甚至与还有Report Service等功能,对于业务流程非常复杂的大项目应该是很适合的了。这种项目人手充足,时间充足,资金也充足, 老板也重视。(这是个人猜测,因为我没做过大项目^_^)。

缺点:工作量大,对每一个报表都需要设计模板,这个需要花费不少的时间和精 力。我们这种小项目,流程不复杂,Report Service等功能用 不上,也不用CS/BS通用,也许将来某个时候需要需要在C/S下用,但还不知道那是什么时候。就是需要控制页边距、纸张大小等,然后把当前界面或者某个报表(很 少的情况)打印出来就可以了。另外,我们因为项目小,所以项目成员也少,也就5~6个人,项目的开发周期也短,也就2~3个月,如果还需要抽出人 手来专门做报表模板,还真有点紧张。看着兄弟们加班加点,还真不忍心再加大工作量。还有一个最要命的是,这些东西真的贵。资金本来就少,还需要买这些服 务,一谈起这个事情,老板总是摇头。

2、使用IE自带的打印功能,加上CSS的支持。

优点:不用专人花时间来写打印模板,节省时间,也不要另外花钱,老板很高 兴。似乎是一个好的方案。

缺点:功能太弱小 了。不能控制页边距、纸张大小等,更不用说不打印当前界面而是打印另外一个报表了。这样,客户就不高兴了,干嘛非得每次让我自己设置页边距、设置纸张大 小、设置页眉页脚等,或者是有些报表根本就没有提供。

3、使用meadroid等提供的免费客户端打印组件。

优点:同2

缺点:meadroid的这个东西很好,可惜只能使用半年。半年之后你必须重新下载,并更改该组件的GUID,真麻烦。如果用户这个时候正好需要打印,那真是臭大了。而且,如果你想避免这种情况的发生,那就 请交钱吧,即使是1年的费用好像也不便宜。这样的话,我们似乎又回到了第 一个解决方案。当然还有许多其他公司提供的免费组件,但是性质和meadroid公司提供的都差不多,费用有便宜的,也有贵的,总的说来,老板都总是摇头。

4、使用服务器端生成PDF等文件的方式。

优点:现在生成PDF文件的免费组件很多,不用另外花钱。效果也很好。

缺点:学习困难。 代码量也非常大。而且,对于我们这种需求多变的小项目,一旦客户的要求变了,那么就必须相应的更改代码。维护的代价真的高。

看了这么几种方案,都不 适合我们使用。还是毛主席老人家讲得好,“自己动手,丰衣足食”,看来得自己动手写一个IE客户端打印组件了,他实现如下功能:

1、 可以用js控制页边 距、纸张大小、页眉页脚、横向纵向等。

2、 可以用js控制打印 内容。

找了几天的资料,终于找到了2篇好文:《Beyond Print Preview: Print Customization for Internet Explorer 5.5、《Print Preview 2: The Continuing Adventures of Internet Explorer 5.5 Print Customization》。看了半天,终于摸出了一点头绪,在那个Sample的基础上改了改,做了个ActiveX,现供朋友们使用。可以用js控制页边距,设置当前打印的URL,至于纸张大小,页眉页脚等 功能,容我实现后再提供给朋友们使用。至于源码,因涉及公司机密,所以……请朋友们见谅。

使用方法:

1、首先在被打印页面中包含如下语句:

<OBJECT ID=”DLPrinter” CLASSID=”CLSID:5C230622-45E5-4e3c-893C-3BFDDC4DB5E4″ height=”0″ width=”0″ codebase=”DLPrinter.CAB” ></OBJECT>

2、打印预览:<input type=”button” id=”btnPrint” value=”Print Preview” onclick=”DLPrinter.PrintPreview()” />

3、设置页边距:

DLPrinter.MarginLeft=20; //这是毫米制的

DLPrinter.MarginRight=20;

DLPrinter.MarginTop=20;

DLPrinter.MarginBottom=20;

4、不打印当前页面 而是另外一个页面:

DLPrinter.ContentURL=”http://www.cnblogs.com/Yahong111/archive/2007/09/19/898326.html”;

在后续的文章中,我会讲述是如何实现这些功能的。欢迎拍砖。
DLPrinter.CAB.rar, 大家把扩展名.rar去掉即可(因为不能上传.cab文件,所以把DLPrinter.cab改成了DLPrinter.cab.rar)

[转载]简单但有用的SQL脚本

mikel阅读(1070)

[转载]简单但有用的SQL脚本 – 我帅故我在 – 博客园.

create table test(id int,name varchar(20),quarter int,profile int)
insert into test values(1,a,1,1000)
insert into test values(1,a,2,2000)
insert into test values(1,a,3,4000)
insert into test values(1,a,4,5000)
insert into test values(2,b,1,3000)
insert into test values(2,b,2,3500)
insert into test values(2,b,3,4200)
insert into test values(2,b,4,5500)
select * from test

行转列
select id,name,
[1] as “一季度”,
[2] as “二季度”,
[3] as “三季度”,
[4] as “四季度”,
[5] as5
from
test
pivot
(
sum(profile)
for quarter in
(
[1],[2],[3],[4],[5])
)
as pvt

create table test2(id int,name varchar(20), Q1 int, Q2 int, Q3 int, Q4 int)
insert into test2 values(1,a,1000,2000,4000,5000)
insert into test2 values(2,b,3000,3500,4200,5500)
select * from test2

列转行
select id,name,quarter,profile
from
test2
unpivot
(
profile
for quarter in
(
[Q1],[Q2],[Q3],[Q4])
)
as unpvt

SQL 替换字符串 substring replace

例子1:
update tbPersonalInfo set TrueName = replace(TrueName,substring(TrueName,2,4),**) where ID = 1

例子2:
update tbPersonalInfo set Mobile = replace(Mobile,substring(Mobile,4,11),********) where ID = 1

例子3:
update tbPersonalInfo set Email = replace(Email,chinamobile,******) where ID = 1

SQL查 询一个表内相同纪录 having

如果一个ID可以区分的话,可以这么写

select * fromwhere ID in (
select ID fromgroup by ID having sum(1)>1))

如 果几个ID才能区分的话,可以这么写

select * fromwhere ID1+ID2+ID3 in
(
select ID1+ID2+ID3 fromgroup by ID1,ID2,ID3 having sum(1)>1))

其他回答:数据表是zy_bho,想找出ZYH字段名相同的记录

方法1:
SELECT *FROM zy_bho  a WHERE EXISTS
(
SELECT 1 FROM zy_bho WHERE [PK] <> a.[PK] AND ZYH = a.ZYH)

方法2:
select a.* from zy_bho  a join zy_bho  b
on (a.[pk]<>b.[pk] and a.zyh=b.zyh)

方法3:
select * from zy_bbo where zyh in
(
select zyh from zy_bbo group by zyh having count(zyh)>1)
其中pk是主键或是 unique的字段。

把多行SQL 数据变成一条多列数据,即新增列

Select
DeptName
=O.OUName,
9G=Sum(Case When PersonalGrade=9 Then 1 Else 0 End),
8G=Sum(Case When PersonalGrade=8 Then 1 Else 0 End),
7G4=Sum(Case When PersonalGrade=7 AND JobGrade =4 Then 1 Else 0 End),
7G3=Sum(Case When PersonalGrade=7 AND JobGrade =3 Then 1 Else 0 End),
6G=Sum(Case When PersonalGrade=6 Then 1 Else 0 End),
5G3=Sum(Case When PersonalGrade=5 AND JobGrade =3 Then 1 Else 0 End),
5G2=Sum(Case When PersonalGrade=5 AND JobGrade =2 Then 1 Else 0 End),
4G=Sum(Case When PersonalGrade=4 Then 1 Else 0 End),
3G2=Sum(Case When PersonalGrade=3 AND JobGrade =2 Then 1 Else 0 End),
3G1=Sum(Case When PersonalGrade=3 AND JobGrade =1 Then 1 Else 0 End),
2G=Sum(Case When PersonalGrade=2 Then 1 Else 0 End),
1G=Sum(Case When PersonalGrade=1 Then 1 Else 0 End),
‘ 未定级’=Sum(Case When PersonalGrade=NULL Then 1 Else 0 End)

表 复制

insert into PhoneChange_Num ([IMSI],Num)
SELECT [IMSI]
,
count([IMEI]) as num
FROM [Test].[dbo].[PhoneChange] group by [IMSI] order by num desc

语法1:Insert INTO table(field1,field2,…) values(value1,value2,…)

语法2:Insert into Table2(field1,field2,…) select value1,value2,… from Table1(要求目标表Table2必须存在,由于目标表Table2已经存在,所以我们除了插入源表Table1的字段外,还可以插入常量。)

语 法3:SELECT vale1, value2 into Table2 from Table1(要求目标表Table2不存在,因为在插入时会自动创建表Table2,并将Table1中指定字段数据复制到Table2中。)

语 法4:使用导入导出功能进行全表复制。如果是使用【编写查询以指定要传输的数据】,那么在大数据表的复制就会有问题?因为复制到一定程度就不再动了,内存 爆了?它也没有写入到表中。而使用上面3种语法直接执行是会马上刷新到数据库表中的,你刷新一下mdf文件就知道了。

利用带关联子查询Update语句更新数据

方法1:
Update Table1 set c = (select c from Table2 where a = Table1.a) where c is null

方法2:
update A
set newqiantity=B.qiantity
from A,B
where A.bnum=B.bnum

方法3:
update
(
select A.bnum ,A.newqiantity,B.qiantity from A left join B on A.bnum=B.bnum) AS C
set C.newqiantity = C.qiantity
where C.bnum =XX

连接远程服务器

方法1:
select * from openrowset(SQLOLEDB,server=192.168.0.67;uid=sa;pwd=password,SELECT * FROM BCM2.dbo.tbAppl)

方法2:
select * from openrowset(SQLOLEDB,192.168.0.67;sa;password,SELECT * FROM BCM2.dbo.tbAppl)

TRUNCATE TABLE [Table Name]

下面是对Truncate语句在MSSQLServer2000中用法和原 理的说明:

Truncate是SQL中的一个删除数据表内容的语句,用法是:

Truncate table 表名 速度快,而且效率高,因为:
TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。
DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。
TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。
对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。
TRUNCATE TABLE 不能用于参与了索引视图的表。

参 考文献

数 据库表行转列,列转行终极方案

行 列互转(动态脚本)

SELECT INTO 和 INSERT INTO SELECT 两种表复制语句

非 常有用的sql脚本

——————-华丽分割线——————-

作者:听风吹雨
版权:本文版权归作者和博客园共有
转载:欢迎转载,不过记得留下买路钱
格言:不喜欢是因为你不会 && 因为会所以喜欢

[转载]面向对象思想的头脑风暴(一)

mikel阅读(1028)

[转载]面向对象思想的头脑风暴(一) – 你听海是不是在笑 – 博客园.

团队中对面向对象的理论研究已经做了很长时间,大家对接口,封装,继承,多态以及设计模式什么的似乎都能说出点东西来,但当看代码时发现大家其实并 不十分清楚具体怎么做,所以我就想了个题目让大家来做,然后进行了一次头脑风暴,过程记录如下:

题目内容:

需要处理三种产品图书,数码,消费,需要计算产品的税率,图书的税率为价格的0.1,数码和消费类产品为价格的0.11,需要获得三种产品的信息,图书和消费类产品的信息为:名 字:” + Name;,数码产品的信息为:数码类名字:” + Name;

要求:符合ocp原则(不懂ocp原则的到 网上去查,变化点在计税的方式可能改变,信息打印方式可能改变)

这里我给大家一个面向过程的版本,方便大家 理解需求

代码

public class Product
{
public string Name { get; set; }
public double Price { get; set; }
public int Type { get; set; }
public string GetProductInfo()
{
switch (Type)
{
case 1:
case 2:
return 名字: + Name;
case 3:
return 数码类名字: + Name;
default:
return string.Empty;
}
}
public double ComputeTax()
{
switch (Type)
{
case 1:
return Price * 0.1;
case 2:
case 3:
return Price * 0.11;
default:
return Price;
}
}
}

测试代码

代码

[TestMethod()]
public void GetProductInfoTest()
{
Product book
= new Product() { Name = C#编程, Price = 50, Type = 1 };
Product consume
= new Product() { Name = 桌子, Price = 100, Type = 2 };
Product digital
= new Product() { Name = 数码相机, Price = 1000, Type = 3 };

Assert.AreEqual(名字:C#编程, book.GetProductInfo());
Assert.AreEqual(
名字:桌子, consume.GetProductInfo());
Assert.AreEqual(
数码类名字:数码相机, digital.GetProductInfo());

}

这个过程化的版本的问题如下:

1、当更改税率或获得信息时需要修改product类,这不符合ocp原则

2、product类的职责也太多了

当然如果就只是这么简单的需求的话,这个过程化的版本也不错,至少简单。

第一个方案:

Product类

class Product
{
public string Name
{
get { return name; }
set { name = value; }
}
private string name;

public double Price
{
get { return price; }
set { price = value; }
}
private double price;

public double TaxRate
{
get { return taxRate; }
set { taxRate = value; }
}
private double taxRate;

public int Type
{
get { return type; }
set { type = value; }
}
private int type;

public Product(string name,double price,double taxRate,int type)
{
this.Name = name;
this.Price = price;
this.TaxRate = taxRate;
this.Type = type;
}

Tax tax = new Tax();

public double GetTax()
{
return tax.ComputeTax(price,taxRate);
}

ProductInfo productInfo = new ProductInfo();

public string GetProductInfo()
{
return productInfo.GetProductInfo(type, name);
}
}

Tax类

class Tax
{
public double ComputeTax(double price, double taxRate)
{
return price * taxRate;
}
}
ProductInfo

class ProductInfo
{
public string GetProductInfo(int type,string name)
{
switch (type)
{
case 1:
return 名字: + name;
case 2:
return 数码类名字: + name;
default:
return string.Empty;
}
}
}

这个方案其实完全没有解决问题,而且还比原来的方案更复杂了。新的税率仍然要改代码,新的信息仍然要改代码。

第二个方案:

Product

public class Product
{

public delegate double GetTax(double price);
public GetTax gt;

public string Name
{
get;
set;
}
public int type
{
get;
set;
}
public double Price
{
get;
set;
}

public double ComputeTax()
{
return gt(this.Price);
}

public override string ToString()
{
return base.ToString();
}
}

CreateProduct

class CreateProduct
{
static Product pp;
public static ITax Tax
{
get;
set;
}
public static IPrint print
{
set;
get;
}
public static Product PProduct
{
get
{

return pp;

}
set { pp = value; pp.gt = Tax.CoumputeTax; }
}

}

interface ITax
{
double CoumputeTax(double price);
}
Main

static void Main(string[] args)
{

Product book = new Product();
book.Name
= 图书;
book.Price
= 1;
book.type
= 1;

CreateProduct.Tax = new BookTax();
CreateProduct.PProduct
= book;

Console.Write(this product name is {0}, price is {1}, book.Name,book.Price);

Console.Read();
}

这个方案似乎是解决了问题,新的税率变化添加一个新类继承ITax接口,在客户端用即可。但这种方案显然属于知识学太多了,混用了委托与接口,无端 的增加的复杂度,CreateProduct完全没有存在的必要。

第三种方案:

Product类

class Product
{
public delegate double DComputeTax(double price);
public DComputeTax dComputeTax;
public string Name { get; set; }
public double Price { get; set; }
public int Type { get; set; }
public string GetProductInfo()
{

return “”;
}
public double ComputeTax()
{
if (dComputeTax!=null)
{
return dComputeTax(Price);
}

return 0;
}

}

代码

[TestMethod()]
public void ComputeTaxTest()
{
ProductN.DComputeTax d
= new ProductN.DComputeTax(c=>c*0.1);

ProductN target = new ProductN() { Price = 100 }; // TODO: 初始化为适当的值
target.dComputeTax = d;
double expected = 10; // TODO: 初始化为适当的值
double actual;
actual
= target.ComputeTax();
Assert.AreEqual(expected, actual);

}

这是使用委托来实现的方案,我觉得这个方案符合了需求,也符合了ocp原则,但和接口方案还是有区别的,我们先看看接口组合方案:

第四种方案:

代码

class Product
{
public string Name { get; set; }
public double Price { get; set; }

IGetInfo getInfo;
IGetTax getTax;
public Product(IGetInfo getInfo, IGetTax getTax)
{
this.getInfo = getInfo;
this.getTax = getTax;
}
public string GetInfo()
{
return getInfo.GetInfo(Name);
}
public double GetTax()
{
return getTax.GetTax(Price);
}
}
interface IGetTax
{
double GetTax(double price);
}
interface IGetInfo
{
string GetInfo(string name);
}
class BookTax:IGetTax
{
#region IGetTax 成 员

public double GetTax(double price)
{
return price*0.1;
}

#endregion
}
class ConsumTax:IGetTax
{
#region IGetTax 成员

public double GetTax(double price)
{
return price*0.11;
}

#endregion
}
class DigitalInfo:IGetInfo
{
#region IGetInfo 成员

public string GetInfo(string name)
{
return 数码类名字: + name;
}

#endregion
}
class SampleInfo:IGetInfo
{
#region IGetInfo 成员

public string GetInfo(string name)
{
return 名字:+name;
}

#endregion
}

测试代码

代码

[TestMethod]
public void GetInfoTest()
{
Product book
= new Product(new SampleInfo(), new BookTax()) { Name = C#编程, Price = 50 };
Assert.AreEqual(
名字:C#编程, book.GetInfo());
Assert.AreEqual(
5, book.GetTax());
Product consume
= new Product(new SampleInfo(), new ConsumTax()) { Name = 桌子, Price = 100 };
Assert.AreEqual(
名字:桌子, consume.GetInfo());
Assert.AreEqual(
11, consume.GetTax());
Product digital
= new Product(new DigitalInfo(), new ConsumTax()) { Name = 数码相机, Price = 1000 };
Assert.AreEqual(
数码类名字:数码相机, digital.GetInfo());
Assert.AreEqual(
110, digital.GetTax());

}

我觉得对于这个需求来说方案三和方案四都应该算是符合要求的比较好的解决方案,这两种方案各有优缺点。

方案三(委托方案)的优点:

1、足够灵活

2、代码简单,类少

缺点:

1、缺乏限制,只要符合计税委托签名的方法就可以计算税率,往往会造成已实现的业务代码职责不够清晰。

方案四(接口方案)的优点:

1、职责明确

2、也足够灵活

缺点:

1、使用的类往往过多

接口和委托的区别:

接口(interface)用来定义一种程序的协定。实现接口的类或者结构要与接口的定义严格一致。接口(interface)是向客户承诺类或结 构体的行为方式的一种合同,当实现某个接口时,相当于告诉可能的客户:“我保证支持这个接口的方法,属性等”,接口不能实例化,接口只包含成员定义,不包 含成员的实现,成员的实现需要在继承的类或者结构中实现。
C#中的委托是一种引用方法的类型,一旦为委托分配了方法,委托将与该方法具有完全相 同的行为,委托方法的使用可以像其他任何方法一样具有参数和返回值。委托对象能被传递给调用该方法引用的代码而无须知道哪个方法将在编译时被调用。
从 定义上来看似乎委托和接口没什么相似之处,但从隔离变化这个角度来看他们倒是有些相似之处,所以这里我们把他们放到一起来比较一番。

委托和接口都允许类设计器分离类型声明和实现。给定的接口可由任何类或结构继承和实现;可以为任何类中的方法创建委托,前提是该方法符合委托的方法 签名。接口引用或委托可由不了解实现该接口或委托方法的类的对象使用。既然存在这些相似性,那么类设计器何时应使用委托,何时又该使用接口呢?

在以下情况中使用委托:

当使用事件设计模式时。委托是事件的基础,当需要某个事件触发外界响应时,使用委托事件比较合适。

当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。

需要方便的组合,使用委托可以利用+=,-=方便的组合方法。

当类可能需要该方法的多个实现时,使用多播委托。

在以下情况中使用接口:

当存在一组可能被调用的相关方法时。

当类只需要方法的单个实现时。

当使用接口的类想要将该接口强制转换为其他接口或类类型时。

当正在实现的方法链接到类的类型或标识时:例如比较方法。

使用单一方法接口而不使用委托的一个很好的示例是 IComparable 或 IComparable。IComparable 声明 CompareTo 方法,该方法返回一个整数,以指定相同类型的两个对象之间的小于、等于或大于关系。IComparable 可用作排序算法的基础,虽然将委托比较方法用作排序算法的基础是有效的,但是并不理想。因为进行比较的能力属于类,而比较算法不会在运行时改变,所以单一 方法接口是理想的。

[转载]数据库访问的性能问题与瓶颈问题

mikel阅读(1026)

[转载]数据库访问的性能问题与瓶颈问题 – 深山老林 – 博客园.

声明

本文是一篇有争议的文章,甚至有可能是一篇争议非常大的文章,可能争来争去依然无法得到一个统一的意见。

场景

个别公司的技术决策者要求团队的开发人员在编写数据访问层的时候,禁止在程序中出现任何的SQL语句,禁止使用Entity Library,禁止使用NBear、NHibernate、IBatis、Entity Framework等ORM框架,只允许使用存储过程。试想一下,您的公司是否是这样子的?您的身边有没有这样的朋友,他们的公司存在这样或类似这样的情 况吗?

矛盾点

cry 对于开发人员来说,使用存储过程的话,工作量比以前要大很多,而且涉及到表的字段更改,项目重构也是个非常麻烦的问题,使用ORM很方便就可以实现数据的 CRUD功能,多表操作也非常的容易,原来写很少的代码就可以操作数据,现在却要写很多的代码。本来公司给的时间就短,项目紧,现在这么一搞,工作量一下 就增加了不少,再者,如果需求一旦发生变化,不可避免的数据库就得增加或修改某些字段,相应的存储过程跟数据访问层的方法都得做调整,开发人员的日子不好 过了。

smile 对于公司的决策者来说,性能问题是不可以妥协的,无论付出多大代价,既然做出了决定,那么就没什么好讨论的。

结果

结果可想而知,最终是按照决策者的决定,开发人员加班加点的做开发,既然决策者都已经做了决定,似乎再讨论用不用存储过程就是一个非常敏感的话题, 再讨论类似的问题的话,开发人员所面临的处境就可能是尴尬的,在某些公司甚至是危险的。无论怎样,开发人员可能最终都很难改变决策者的决定。

决策者的心声

带着这些疑惑,我跟多位决策者进行沟通,搜集了他们的意见,总结下来的话,大概就是以下几点:

  1. 性能问题。经过测试,使用ORM比直接调用存储过程慢10倍。如果是做软件项目或软件产品,使用ORM问题不大,可是如果是以运营为主 (访问量较大)的Web网站,性能上就会有问题。
  2. 并发问题。一旦访问量较大并且达到一定数量级的时候,ORM就可能会出现并发问题。
  3. DBA 的能力受限。一旦出现性能问题,如果是按照写存储过程的方式来做,公司可以招来技术实力强的DBA直接改存储过程进行优化,而如果是使用ORM的话,那么 DBA就很难进行优化,因为DBA很难读懂ORM写的程序,更不知道ORM内部的实现方式,这样一来,DBA的能力就得不到施展。
  4. 不愿 意被微软绑架。以Entity Framework为例,Entity Framework不是开源的,如果出现性能问题,不能够看到源代码,这可能是一个风险。再者,一个强有力的公司强有力的团队,如果没有自己的技术,总是 使用微软的不开源的框架,这怎么可以?
  5. Entity Framework是微软的产品,微软的产品只适合中小型的公司做开发,大的互联网公司是不敢用的,甚至他们可能采用Java+Oracle来做,一旦达 到一定数量级,微软的东西就可能会出现问题。

笔者的心声

在充分理解了决策者的心声以后,我思绪万千,心中久久不能平静。终于,在把很多东西看淡,抛开杂念,在这样一个宁静的夜晚,也坦诚的把我的想法一一 阐述,分别针对决策者的心声,谈一谈我的个人看法。

1.到底什么是性能问题?存在不存在性能问题?
来看下测试是如何做的,使用存储过程进行插入或删除操作,分别使用存储过程和 Entity Framework,循环10000次或者1000000次操作,然后整体上存储过程要比使用Entity Framework要快10倍。实际场景是怎样的呢?
实际的场景是用户点击页面上的按钮,执行了1次操作,我们假定按照写存储过程的方式来做, 用户这1次操作可能需要0.001秒,而使用Entity Framework要慢10倍,用了0.01秒,那么这0.001秒比0.01秒的确是快10倍,但是对用户来说,可能根本就没有明显的差距,因为这么微 弱的时间用户是体会不出来的。我们开发的程序,对用户来说,我们的产品会不会因为这0.001秒跟0.01秒的差距而打折扣呢?这微弱的差距是严重性能问 题?还是可以忽略不计?

2.到底存在不存在并发问题?

诚然,可能之前有团队使用ORM开发高并发的项目,他们在运营中出现了并发问题,可是DBA又无法查出来到底是什么地方导致了并发问题,最终把一切 的一切归咎在ORM上。

亲爱的朋友们,让我们理智的冷静的来分析下两者的技术实现上的不同吧。

直接调用存储过程:打开数据库连接–》执行编译好的数据库语句–》关闭数据库连接

ORM:打开数据库连接–》把对象解释成SQL语句–》执行SQL操作–》关闭数据库连接

通过比对我们可以发现,ORM就可以比作是一个SQL生成器,它把对象解释,拼一个SQL语句出来,然后在执行这个SQL语句,由于还需要解释,就 相当于多了一步翻译的工作,因此,就比存储过程慢了一点点,那么慢的这一点点会不会出现并发问题?我的意见是并发问题多半是由于锁的影响,只要不产生锁, 就不会有并发问题。正因为如此,高并发的项目开发,多半是忌讳使用事务,有的程序员手写异常后的数据库回滚语句(有些滑稽哈,但事实上就是这样),项目中 也不推荐使用游标跟触发器。

3.DBA能力受限。诚然,DBA看不懂ORM写的程序,更加不明白ORM内部的实现原理。但是,DBA是可以跟程序员配合,利用 SQLProfile等工具,看到最终SQL语句是如何执行的。也就是说,DBA的能力也是依然可以发挥出来的,只不过是需要跟程序员配合而已,或者 DBA需要熟悉如何调试、跟踪。如果说全部写存储过程了,DBA能力是放开了,可是程序员的能力就受限了,譬如说,在进行大批量的数据插入的时候,大家都 知道,.ADO.NET2.0的一个新特性SqlBulkCopy是多么的快,估计这是DBA无法优化的。对SqlBulkCopy不熟悉的朋友,请参考 《SQLServer中批量插入数据方式的性能对比》。

很多时候,一个软件性能的优化,需要从整体去考虑,并不一定是说出现数据库性能问题,就一定是DBA的责任,或者说一定是程序员的责任。在DBA跟 程序员之间难道就真的像插销跟插板之间那样,职责分的特别的清楚?很多时候我们得充分利用存储过程的特性,跟.NET平台的一些优良特性,选择适合我们的 来进行开发,没有什么是最佳的,但是对我们来说,适合我们就好。

从另外的角度考虑,其实在项目初期,DBA就应该参与进来,进行数据库的设计了,而一旦数据库设计好了,设计得并不规范,存储过程也些了成千上万 了,将来一旦出现性能问题,相信也够DBA喝一壶的。

4.不愿意被微软绑架。这个观点倒是让我感觉到意外,至少我们很多都在用微软的.NET Framework,我们使用微软的SQL Server数据库,如果说我们被绑架,可能现在就已经被绑架了,SQLServer的存储过程跟Oracle、Mysql的存储过程是不一样的,如果将 来进行数据库的迁移,那么可想而知后果是怎样的。到底怎样才是真正的被绑架?

5..Net与Java孰好孰坏?

关于这个问题的讨论,一直就是个无休止的讨论。scottgu把这个比作是“带有宗教性质的技术争论”。诚然,讨论这样的问题的确令人讨厌,而且是 浪费时间,而且讨论的双方都深切的关注着。讨论来讨论去最终也不会有结果。

在目前所运行的软件系统中,我们可以看到其背后的平台、语言等是各种各样,MySpace是基于.NET平台的,淘宝网是基于Java的,而 Google则推崇使用Python,许多大型的电力系统还依然运行在C++平台上,最关键的一点,.NET并不是没有在大型项目中应用。只不过是 Java起步早,.NET起步晚而已,要在前几年,Java做的大型项目的确是比.NET的大型项目要多。

很多时候,即使是使用相同的开发语言,不同的程序员开发的程序效率就差30倍以上,甚至几千倍以上,这点好不夸张。诚然,每门技术自有其缺点,但它 们也都自有其优点,如果它的优点恰好能符合你的需要,用它就好了。重要的是,你有没有使用好它的能力。

总结

其实总结就不必了,说点题外话吧。存储过程在单条执行操作的时候,的确要比使用ORM要快,可是如果是执行批量的操作,使用存储过程就会非常的费 劲。之前我是这样做的。假定更新1000条数据,数据库里只有2个字段,循环调用1000次存储过程需要2分钟左右,当时我把要更新的id以参数的形式逗 号分隔传入存储过程,在存储过程中循环执行1000条数据,发现时间跟循环调用1000次存储过程的时间是差不太多的,最终进行了改进,改进的方法嘛,还 是把要更新的Id以参数形式逗号分隔传入存储过程,然后使用update table set value=’value’ where id in select id in 分隔函数(id1,id2,id3…..),经过这种方式,更新1000条数据的时间从2分钟变成了200毫秒,可是问题依然不完美,方法存在局限性。

首先,使用这种方法参数的长度是有限制的,varchar类型最大不超过8000,nvarchar类型最大长度不超过4000.

其次,如果表中有多个列,要更新的也是多列,存储过程的局限性就出来了。

再次声明:文中观点仅代表个人观点,如果您有不同意见,欢迎共同讨论。

最后,给大家分享个幽默视频,来缓解下这种紧张而激烈的争论吧。

《办公室里有这样的女人你能安心工作吗?(中文字幕)》

#MySignature { BORDER-RIGHT: #e5e5e5 1px solid; PADDING-RIGHT: 10px; BORDER-TOP: #e5e5e5 1px solid; PADDING-LEFT: 60px; BACKGROUND: url(http://wlb.cnblogs.com/images/cnblogs_com/wlb/1.jpg) #fffefe no-repeat 1% 50%; PADDING-BOTTOM: 10px; BORDER-LEFT: #e5e5e5 1px solid; PADDING-TOP: 10px; BORDER-BOTTOM: #e5e5e5 1px solid } #MySignature A:link { COLOR: #f60 } #MySignature A:visited { COLOR: #f60 } #MySignature A:active { COLOR: #f60 } #MySignature A:hover { COLOR: #f60; TEXT-DECORATION: underline } 作者:深山老林
出处:http://wlb.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]SQLServer中批量插入数据方式的性能对比

mikel阅读(1122)

[转载]SQLServer中批量插入数据方式的性能对比 – 深山老林 – 博客园.

昨天下午快下班的时候,无意中听到公司两位同事在探讨批量向数据库插入数据的性能优化问题,顿时来了兴趣,把自己的想法向两位同事说了一下,于是有了本 文。

公司技术背景:数据库访问类(xxx.DataBase.Dll)调用存储过程实现数据库的访问。

技术方案一:

压 缩时间下程序员写出的第一个版本,仅仅为了完成任务,没有从程序上做任何优化,实现方式是利用数据库访问类调用存储过程,利用循环逐条插入。很明显,这种 方式效率并不高,于是有了前面的两位同事讨论效率低的问题。

技术方案二:

由于是考虑到大数据量的批量插入,于是我想到了 ADO.NET2.0的一个新的特性:SQLBulkCopy。有关这个的性能,很早之前我是亲自做过性能测试的,效率非常高。这也是我向公司同事推荐的 技术方案。

技术方案三:

利用SQLServer2008的新特性–表值参数(Table-Valued Parameter)。表值参数是SQLServer2008才有的一个新特性,使用这个新特性,我们可以把一个表类型作为参数传递到函数或存储过程里。 不过,它也有一个特点:表值参数在插入数目少于 1000 的行时具有很好的执行性能。

技术方案四:

对于单列字段,可以 把要插入的数据进行字符串拼接,最后再在存储过程中拆分成数组,然后逐条插入。查了一下存储过程中参数的字符串的最大长度,然后除以字段的长度,算出一个 值,很明显是可以满足要求的,只是这种方式跟第一种方式比起来,似乎没什么提高,因为原理都是一样的。

技术方案五:

考虑 异步创建、消息队列等等。这种方案无论从设计上还是开发上,难度都是有的。

技术方案一肯定是要被否掉的了,剩下的就是在技术方案二跟技术 方案三之间做一个抉择,鉴于公司目前的情况,技术方案四跟技术方案五就先不考虑了。

接下来,为了让大家对表值参数的创建跟调用有更感性的 认识,我将写的更详细些,文章可能也会稍长些,不关注细节的朋友们可以选择跳跃式的阅读方式。

再说一下测试方案吧,测试总共分三组,一组 是插入数量小于1000的,另外两组是插入数据量大于1000的(这里我们分别取10000跟1000000),每组测试又分10次,取平均值。怎么做都 明白了,Let’s go!

1.创建表。

为了简单,表中只有一个字段,如下图所示:

2.创建表值参数类型

我们打开查询分析器,然后在查询分析器中执行下列代码:

Create Type PassportTableType as Table
(
PassportKey nvarchar(50)

)

执行成功以后,我们打开企业管理器,按顺序依次展开下列节点–数据库、展开可编程性、类型、用户自定义表类型,就可以看到我们创建好的表值类型了 如下图所示:

说明我们创建表值类型成功了。

3.编写存储过程

存储过程的代码为:

USE [TestInsert]

GO
/****** Object: StoredProcedure [dbo].[CreatePassportWithTVP] Script Date: 03/02/2010 00:14:45 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:	<Kevin>
-- Create date: <2010-3-1>
-- Description:	<创建通行证>
-- =============================================
Create PROCEDURE [dbo].[CreatePassportWithTVP] 

@TVP PassportTableType readonly

AS
BEGIN
SET NOCOUNT ON;

Insert into Passport(PassportKey) select PassportKey from @TVP

END
可能在查询分析器中,智能提示会提示表值类型有问题,会出现红色下划线(见下图),不用理会,
继续运行我们的代码,完成存储过程的创建

4.编写代码调用存储过程。

三种数据库的插入方式代码如下,由于时间比较紧,代码可能不那么易读,特别代码我加了些注释。

主要部分的代码

using System;
using System.Diagnostics;
using System.Data;
using System.Data.SQLClient;
using com.DataAccess;

namespace ConsoleAppInsertTest
{
class Program
{
static string connectionString = SqlHelper.ConnectionStringLocalTransaction; //数据库连接字符串
static int count = 1000000; //插入的条数
static void Main(string[] args)
{
//long commonInsertRunTime = CommonInsert();
//Console.WriteLine(string.Format(“普通方式插入{1}条数据所用的时间是{0}毫 秒”, commonInsertRunTime, count));

long sqlBulkCopyInsertRunTime = SqlBulkCopyInsert();
Console.WriteLine(
string.Format(使用SqlBulkCopy插入{1}条数据所用的时间是{0}毫秒, sqlBulkCopyInsertRunTime, count));

long TVPInsertRunTime = TVPInsert();
Console.WriteLine(
string.Format(使用表值方式(TVP)插入{1}条数据所用的时间是{0}毫秒, TVPInsertRunTime, count));
}

/// <summary>
/// 普通调 用存储过程插入数据
/// </summary>
/// <returns></returns>
private static long CommonInsert()
{
Stopwatch stopwatch
= new Stopwatch();
stopwatch.Start();

string passportKey;
for (int i = 0; i < count; i++)
{
passportKey
= Guid.NewGuid().ToString();
SqlParameter[] sqlParameter
= { new SqlParameter(@passport, passportKey) };
SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure,
CreatePassport, sqlParameter);
}
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}

/// <summary>
/// 使用 SqlBulkCopy方式插入数据
/// </summary>
/// <param name=”dataTable”></param>
/// <returns></returns>
private static long SqlBulkCopyInsert()
{
Stopwatch stopwatch
= new Stopwatch();
stopwatch.Start();

DataTable dataTable = GetTableSchema();
string passportKey;
for (int i = 0; i < count; i++)
{
passportKey
= Guid.NewGuid().ToString();
DataRow dataRow
= dataTable.NewRow();
dataRow[
0] = passportKey;
dataTable.Rows.Add(dataRow);
}

SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(connectionString);
sqlBulkCopy.DestinationTableName
= Passport;
sqlBulkCopy.BatchSize
= dataTable.Rows.Count;
SqlConnection sqlConnection
= new SqlConnection(connectionString);
sqlConnection.Open();
if (dataTable!=null && dataTable.Rows.Count!=0)
{
sqlBulkCopy.WriteToServer(dataTable);
}
sqlBulkCopy.Close();
sqlConnection.Close();

stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}

private static long TVPInsert()
{
Stopwatch stopwatch
= new Stopwatch();
stopwatch.Start();

DataTable dataTable = GetTableSchema();
string passportKey;
for (int i = 0; i < count; i++)
{
passportKey
= Guid.NewGuid().ToString();
DataRow dataRow
= dataTable.NewRow();
dataRow[
0] = passportKey;
dataTable.Rows.Add(dataRow);
}

SqlParameter[] sqlParameter = { new SqlParameter(@TVP, dataTable) };
SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure,
CreatePassportWithTVP, sqlParameter);

stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}

private static DataTable GetTableSchema()
{
DataTable dataTable
= new DataTable();
dataTable.Columns.AddRange(
new DataColumn[] { new DataColumn(PassportKey) });

return dataTable;
}

}
}

比较神秘的代码其实就下面这两行,该代码是将一个dataTable做为参数传给了我们的存储过程。简单吧。

SqlParameter[] sqlParameter = { new SqlParameter("@TVP", dataTable) };

SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, "CreatePassportWithTVP", sqlParameter);
5.测试并记录测试结果
第一组测试,插入记录数1000

第二组测试,插入记录数10000

第三组测试,插入记录数1000000

通 过以上测试方案,不难发现,技术方案二的优势还是蛮高的。无论是从通用性还是从性能上考虑,都应该是
优先被选择的,还有一点,它的技术复杂度要比 技术方案三要简单一些,

设想我们把所有表都创建一遍表值类型,工作量还是有的。因此,我依然坚持我开始时的决定,
向公司推荐使用 第二种技术方案。

写到此,本文就算完了,但是对新技术的钻研仍然还在不断继续。要做的东西还是挺多的。

为了方便大家学习 和交流,代码文件已经打包并上传了,欢迎共同学习探讨。

代码下载
#MySignature { BORDER-RIGHT: #e5e5e5 1px solid; PADDING-RIGHT: 10px; BORDER-TOP: #e5e5e5 1px solid; PADDING-LEFT: 60px; BACKGROUND: url(http://wlb.cnblogs.com/images/cnblogs_com/wlb/1.jpg) #fffefe no-repeat 1% 50%; PADDING-BOTTOM: 10px; BORDER-LEFT: #e5e5e5 1px solid; PADDING-TOP: 10px; BORDER-BOTTOM: #e5e5e5 1px solid } #MySignature A:link { COLOR: #f60 } #MySignature A:visited { COLOR: #f60 } #MySignature A:active { COLOR: #f60 } #MySignature A:hover { COLOR: #f60; TEXT-DECORATION: underline } 作者:深山老林
出处:http://wlb.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]通用存储过程分页(使用ROW_NUMBER()和不使用ROW_NUMBER()两种情况)性能分析

mikel阅读(1137)

[转载]通用存储过程分页(使用ROW_NUMBER()和不使用ROW_NUMBER()两种情况)性能分析 – Jiangrod Sky – 博客园.

表结构:

CREATE TABLE [dbo].[Xtest](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[XName] [nvarchar](50) NULL,
[XDemo] [nvarchar](500) NULL,
CONSTRAINT [PK_xtest] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

插入50万条数据:
declare @i int
set @i=1
while(@i<500001)
begin
insert into dbo.Xtest(XName,XDemo) values(CAST(@i as varchar)+’name’+CAST(@i as varchar),’这是第’+CAST(@i as varchar)+’的内容’)
set @i=@i+1
end

另外对XName建立索引。

存储过程如下:

代码


=============================================
Author:        <jiangrod>
Create date: <2010-03-03>
Description:    <SQL2000 通用分页存储过程>调用方 法: sp_Pager2000 ‘xtest’,’*’,’ID’,’ORDER BY ID ASC’,’xname like ”%222name%”’,3,20,0,0
=============================================
ALTER PROCEDURE [dbo].[sp_Pager2000]
@tblName varchar(255), 表名 如:’xtest’
@strGetFields varchar(1000) = *, 需要返回的列如:’xname,xdemo’
@pkName nvarchar(50)=ID, 主键名
@strOrder varchar(255)=, 排序的字段名如:’order by id desc’
@strWhere varchar(1500) = , 查询条件(注意:不要加where) 如:’xname like ”%222name%”’
@pageIndex int = 1, 页码如:2
@pageSize int = 20, 每页记录 数如:20
@recordCount int=0 out, 记录总数
@doCount bit=0 非0则统计,为0则不统计(统计会影响效率)
AS

Declare @SQL nvarchar(4000)
Declare @sqltemp nvarchar(1000)
set @sql= From +@tblName
if(@strWhere!=) set @sql=@sql+ Where +@strWhere

declare @strCount nvarchar(1000)
总记录条数
if(@doCount!=0)
begin
if(@strWhere !=)
begin
set @strCount=set @num=(select count(1) from + @tblName + where +@strWhere+ )
end
else
begin
set @strCount=set @num=(select count(1) from + @tblName + )
end
EXECUTE sp_executesql @strCount ,N@num INT output,@RecordCount output
end

if @strWhere !=
begin
set @strWhere= where +@strWhere
end

if (@PageIndex>1)
begin
set @sqltemp=@PKName+ not in (Select Top +cast((@PageSize*(@PageIndex1)) as nvarchar)+ +@PKName+ +@sql
if(@strOrder!=)set @sqltemp=@sqltemp+ +@strOrder
set @sqltemp=@sqltemp+)
set @sql=Select Top +cast(@PageSize as nvarchar)+ +@strGetFields+ +@sql
if(@strWhere!=)set @sql=@sql+ And +@sqltemp
else set @sql=@sql+ Where +@sqltemp
end
else
begin
set @sql=Select Top +cast(@PageSize as nvarchar)+ +@strGetFields+ +@sql
end
if(@strOrder!=) set @sql=@sql+ +@strOrder
exec(@sql)

=============================================
Author:        <jiangrod>
Create date: <2010-03-03>
Description:    <SQL2005 及后续版本通用分页存储过程>调用方 法: sp_Pager2005 ‘xtest’,’*’,’ORDER BY ID ASC’,’xname like ”%222name%”’,2,20,0,0
=============================================
ALTER PROCEDURE [dbo].[sp_Pager2005]
@tblName varchar(255), 表名 如:’xtest’
@strGetFields varchar(1000) = *, 需要返回的列如:’xname,xdemo’
@strOrder varchar(255)=, 排序的字 段名如:’order by id desc’
@strWhere varchar(1500) = , 查询条件(注意:不要加 where)如:’xname like ”%222name%”’
@pageIndex int = 1, 页码如:2
@pageSize int = 20, 每页记录 数如:20
@recordCount int output, 记录总数
@doCount bit=0 非0则统计,为0则不统计(统计会影响效率)
AS

declare @strSQL varchar(5000)
declare @strCount nvarchar(1000)
总记录条数
if(@doCount!=0)
begin
if(@strWhere !=)
begin
set @strCount=set @num=(select count(1) from + @tblName + where +@strWhere+ )
end
else
begin
set @strCount=set @num=(select count(1) from + @tblName + )
end
EXECUTE sp_executesql @strCount ,N@num INT output,@RecordCount output
end

if @strWhere !=
begin
set @strWhere= where +@strWhere
end
set @strSQL=SELECT * FROM (SELECT ROW_NUMBER() OVER (+@strOrder+) AS ROWID,
set @strSQL=@strSQL+@strGetFields+ FROM [+@tblName+] +@strWhere
set @strSQL=@strSQL+) AS sp WHERE ROWID BETWEEN +str((@PageIndex1)*@PageSize+1)
set @strSQL=@strSQL+ AND +str(@PageIndex*@PageSize)
exec (@strSQL)

对上面两个存储过程进行了测试,测试环境:酷睿2双核2.20Gh,1G内存,SQL SERVER2008

对50万条数据进行了分页查询测试

查询的条件是:xname like ‘%222name%’

“page1:2秒937毫秒”:表示打开第一页使用的时间,其他依次类推

不使用ROW_NUMBER()每页都要统计:
page1:2秒937毫秒
page2:3秒140毫秒
page3:3秒 453毫秒
page4:3秒609毫秒
page5:3秒843毫秒
page6:4秒156毫秒

不使用ROW_NUMBER()仅第一页统计:
page1:2秒937毫秒
page2:0秒343毫秒
page3:0秒 593毫秒
page4:0秒812毫秒
page5:1秒46毫秒
page6:1秒281毫秒

使用ROW_NUMBER()每页都要统计:
page1:2秒937毫秒
page2:3秒31毫秒
page3:3秒156 毫秒
page4:3秒296毫秒
page5:3秒421毫秒
page6:3秒515毫秒

使用ROW_NUMBER()仅第一页统计:
page1:2秒937毫秒
page2:0秒218毫秒
page3:0秒 359毫秒
page4:0秒468毫秒
page5:0秒578毫秒
page6:0秒687毫秒

结论:在存储过程当中如果每次都统计记录总数将会严重影响效率,相同的查询条件记录总数必定是相同的,所以如果第一页执行之后把记录总数保存起来, 点击其他页次的时候命令存储过

程不再统计记录总数将会大大提高系统性能。通过测试结果看出每次都要统计总记录数使用ROW_NUMBER()和不使用ROW_NUMBER()差 别不是太大,如果仅第一次统计总记录数使用ROW_NUMBER()将

会比不使用ROW_NUMBER()的效率提高很多。

[转载]网址缩短程序是怎么做的

mikel阅读(1023)

[转载]网址缩短程序是怎么做的 – 草屋主人的blog – 博客园.

近来微博很火,因为字数的限制,出现了很多网址缩短这种服务,比如sina自己用了自家的sinaurl.cn,想到自己曾经也注册了一个很短的域名k6.hk很久了,一直闲着,不知道干嘛用,突然想到可以用来做网址缩短,还不错。说干就干,一会就 写好了。比如我的博客地址就可以缩短成:http://k6.hk/u

程序的设计很简单,下面说下原理,

数据库只有两个字段seq(自增长数字)和url(数字的url地址,建立索引)。

用户输入一个url地址,查询表是否包含此url,如果存在,则返回seq的数字,

如果不存在,则插入数据库,得到一个新增加的自增seq数字,为了缩短数字占用的字符数,我们可以把abc等字母的大小写用上。这样10个数字,26个 小写字母,26个大小字母就组成了一个62进制了。比如数字10000000000(100亿)转换后就是aUKYOA,只有6位了,这样就能缩短很多的 网址了。

下面是php的进制转换代码,来源于php手册(简单吧),当然其他语言实现也是很简单的,

<?php //十进制转到其他制 function dec2any( $num, $base=62, $index=false ) { if (! $base ) { $base = strlen( $index ); } else if (! $index ) { $index = substr( "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ,0 ,$base ); } $out = ""; for ( $t = floor( log10( $num ) / log10( $base ) ); $t >= 0; $t-- ) { $a = floor( $num / pow( $base, $t ) ); $out = $out . substr( $index, $a, 1 ); $num = $num - ( $a * pow( $base, $t ) ); } return $out; } function any2dec( $num, $base=62, $index=false ) { if (! $base ) { $base = strlen( $index ); } else if (! $index ) { $index = substr( "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 0, $base ); } $out = 0; $len = strlen( $num ) - 1; for ( $t = 0; $t <= $len; $t++ ) { $out = $out + strpos( $index, substr( $num, $t, 1 ) ) * pow( $base, $len - $t ); } return $out; } ?>

上面只是说了下实现的原理,如果要大规模的使用,后端可以抛弃数据,用key-value数据库存储,比如ttserver,将会有很高的性能提升。

如果改下ttserver的源代码,通过ttserver的http接口直接跳转,那么性能将会非常高,一台机器一天提供上10亿次的中专都不是问题。 用两台机器就可以实现高可用了,这种服务都不怎么耗费流量的。

[转载]Asp.net缓存简介

mikel阅读(1032)

[转载]Asp.net缓存简介 – CareySon – 博客园.

概述

缓存学术一些的解释是”将常用数据放入易于读取的地方以提高性能”。而对于ASP.NET来说,需要被缓存的对象多种多样,包括从数据库中提取出来的数 据,以及aspx页面生成的静态页,甚至是编译好的程序集。合理利用缓存能让ASP.NET的性能大幅提升,下面将对ASP.NET中的缓存机制进行简单 概述。

缓存的分类

在Asp.net中,大部分缓存机制是保存在cache对象中,也就是服务器内存的一部分。当用户请求数据时,如果数据已经被缓存,则用户所提取的数据直 接从服务端返回,而不是从数据库等底层数据库提取。这对性能的提升不得不说很有帮助。下面来看asp.net中几种缓存机制。

程序集缓存

简单的说,这种缓存是asp.net自带的,无需开发人员进行参与的缓存方式。即当第一次请求服务器时,Page类以及相关程序集被编译,当下次请求时, 访问缓存后的编译而不是重新编译。CLR会自动检测代码的改变,如果代码改变后,当下次访问时,相关代码会被重新编译。

数据源缓存

数据源缓存,顾名思义,也就是利用数据源控件对获取的数据进行缓存的方式。这些控件包括SQLDataSource,ObjectDataSource 等:

datasource

作为抽象类的DataSourceControl暴漏了如下属性用于缓存:

名称 说明
CacheDuration 获取或设置以秒为单位的一段时间,数据源控件就在这段时间内缓存 SelectMethod 属性检索到的数据。
CacheExpirationPolicy 获取或设置缓存的到期行为,该行为与持续时间组合在一起可以描述数据源控件 所用缓存的行为。
CacheKeyDependency 获取或设置一个用户定义的键依赖项,该键依赖项链接到数据源控件创建的所有 数据缓存对象。
EnableCaching 获取或设置一个值,该值指示 ObjectDataSource 控件是否启用数据缓存。

而使用起来就非常简单了,只需要将缓存的相关属性进行设置即可。比如我想要当前数据源缓存10秒,只需要设置EnableCaching属性和 CacheDuration属性如下:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
            ConnectionString="<%$ ConnectionStrings:AdventureWorksConnectionString %>" 
            SelectCommand="SELECT top 10 * FROM [Person].[Contact]" EnableCaching="true" CacheDuration="10"></asp:SqlDataSource>

这种方式的工作原理可以用下图表示:

3

关于ObjectDataSource我推荐阅读Caching Data with the ObjectDataSource

SQL Cache Dependency

大家应该注意到了上面的数据源控件还暴漏了CacheKeyDependency属 性,这是用于实现SQL Cache Dependency的方式,关于Dependency,其实就是在数据库表内容改变时,将相应的缓存进行更新,正如Dependency这个词的意思一 样,是缓存依赖底层数据库。下面就要说到两种实现SQL Cache Dependency的方法了。

方法一:使用轮流查询机制(polling-based):

这种方式实现机制是在sql server中插入以AspNet_SqlCacheNotification_Trigger开头的一个特殊的表和5个存储过程,当被监测的表数据发生 改变时,则一个名为AspNet_SqlCacheTablesForChangeNotification的表被更新,而Asp.net程序会根据用户 设置的间隔时间每隔一定时间检查一下数据库内容是否更新,如果更新,则将缓存中的数据进行跟新。

这种机制配置相对比较麻烦。具体做法网上有很多教程,这里我推荐阅读:Using SQL Cache Dependencies.

使用起来就很简单了,可以在页面头部的OutputCache指令中设置,会社DataSource空间中进行设置,设置格式为:“数据库名:表名”.里 面的表名即是需要监测是否改变的表名,示例如下:

<%@ OutputCache Duration="30" VaryByParam="none" SqlDependency="DatabaseName:tableName" %>

如果需要添加多个表,则用”;”进行分割

SqlDependency="database:table;database:table"

方法二:使用通知机制(notification-based)

使用通知机制配置起来要简便很多,但是sql server的版本需要9.0以上,也就是sql server 2005,使用这种方式需要将sql server的通知服务开启。

使用通知机制可以对页面进行缓存,也可以对datasouce控件进行缓存,对页面进行缓存代码如下:

<%@ OutputCache Duration="30" VaryByParam="none" SqlDependency="CommandNotification" %>

注意SqlDependency必须设置成CommandNotification。

对于datasource控件,也是同样:

        <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
            ConnectionString="<%$ ConnectionStrings:AdventureWorksConnectionString %>" 
            SelectCommand="SELECT top 10 * FROM [Person].[Contact]" EnableCaching="true" CacheDuration="10" SqlCacheDependency="CommandNotification"></asp:SqlDataSource>

输出缓存(output Caching)

输出缓存是页面级别的缓存,是将aspx页面内容在第一次请求后生成的静态页放入缓存,在不过期时间内每一次请求时从缓存中返回静态页,而不是重新走完 Asp.net的生命周期。可以将可以通过在页面头部加入OutputCache指令实现,也可以通过HttpCachePolicy类实现。

输出缓存可以缓存整个页面,也可以缓存部分页面,缓存页面的一部分是通过用户控件来实现。

下面来看通过OutputCache指令实现页面缓存:

前面已经看到,这种方式十分简单,下面说一下OutputCache的重点属性

Duration

页面过期的时间,单位为秒。超过过期时间后,则在下一次请求时页面会重新生成并缓存。

VaryByHeader

VaryByCustom

VaryByParam

VaryByControl

VaryByContentEncodings

这些属性都是为了保存页面的多个版本,比如说一个页面用于显示产品,则根据产品id的不同,缓存同一个页面的不同版本, 具体的意思请看MSDN

CacheProfile

这个选项有些像连接字符串,作用只是将具体的缓存选项变成对于选项的引用,比如我们在Web.Config放入如下代 码:

<caching>
        <outputCacheSettings>
          <outputCacheProfiles>
            <add name="CacheProfile"
                  enabled="true"
                  duration="60"
                   varyByParam="product:id"/>
          </outputCacheProfiles>
        </outputCacheSettings>
            
       
      </caching>

则在引用时只需要在页面头部设置如下:

<%@ OutputCache CacheProfile="CacheProfile" %>

而不是全部写入页面

DiskCacheable

因为服务器内存是有限的,所以通过将DiskCacheable属性设置为true,则可以将缓存页面放入硬盘中,这样即使服务器崩溃重启,缓存依然存 在.

缓存部分页面

缓存页面的一部分实现原理和缓存整个页面毫无二致,都是在页面头部加入OutputCache指令,唯一的不同是缓存部分页面是在用户控件中进行的。这部 分就不在多说了。

使用HttpCachePolicy缓存页面

前面已经说了通过OutputCache指令在页面头部设置缓存选项,另一种替代方法是使用HttpCachePolicy类,这个类的实例是 Response.Cache.如果使用HttpCachePolicy设置缓存,则需要在页面移除OutputCache指令。

比如:

<%@ OutputCache  Duration="30" VaryByParam="state;city" %>

和下面代码是等价的:

 	Response.Cache.SetExpires(DateTime.Now.AddSeconds(30));
      Response.Cache.VaryByParams["state"] = true;
      Response.Cache.VaryByParams["city"] = true;

更多关于HttpCachePolicy,请查看MSDN

对象缓存

对象缓存是将继承与System.Object的对象缓存在服务器的内存中,通过Page类的Cache属性可以访问到Cache集合。Cache内可以 放任何类型的对象,但是要小心使用Cache,因为Cache是占用服务器内存,如果使用不当,也许会拖累性能。

使用Cache的例子:

        //save object into Cache
        Cache["table"] = GridView1;

        //get object from Cache
        GridView gv = (GridView)Cache["table"];

要注意的是,再提取缓存中的对象时,一定别忘了强制转换。

总结

文中对Asp.net的缓存机制进行了简述,asp.net中的缓存极大的简化了开发人员的使用。如果使用得当,程序性能会有客观的提升。

[转载]Asp.net MVC2 使用经验,性能优化建议

mikel阅读(897)

[转载]Asp.net MVC2 使用经验,性能优化建议 – Otis’s Technology Space – 博客园.

这个月一直在用 ASP.NET MVC2 做http://www.86e0.com/t 这个网站,用的时候是 aps.net MVC2 RC2,然后现在ASP.NET MVC2正式版已经是发布了。 在MVC的使用上,有一些心得。下面作一下总结,希望对大家有用,也欢迎大家讨论。

1.关于 缓存

缓存上,数据层上的缓存是必须的,这点不必多说了。

另一个很重要的是:视图片段缓存。

我参考了老赵的写的三篇关于片段缓存的文章:

本想用老赵的了,但是我发现Asp.net MVC2 的有一个新功能: Html.Partial可以返回生成的HTML, 返回的类型是:MvcHtmlString. 虽然要利用Partial View才能生成Html片段,但是我想这个已经够我用的了, 所以我做了一个这样一个Helper,主要是将生成的HTML片段缓存到Memcached里。代码如下:

01 public static class MvcHtmlHelper
02 {
03 public static MvcHtmlString MemcacheHtmlPartial(this HtmlHelper htmlHelper,int duration, string partialViewName, object model, ViewDataDictionary viewData)
04 {
05 object obaear = htmlHelper.ViewContext.RouteData.DataTokens["area"];
06 string area=string.Empty;
07 if (obaear != null) area = obaear.ToString();
08 string key = string.Format("MemcacheHtmlPartial_{0}{1}", area, partialViewName);
09 object ob = DistCache.Get(key);
10 if (ob == null)
11 {
12 MvcHtmlString mstr = htmlHelper.Partial(partialViewName, model, viewData);
13 DistCache.Add(key, mstr.ToString(), TimeSpan.FromSeconds(duration));
14 return mstr;
15 }
16 else
17 {
18 return MvcHtmlString.Create((string)ob);
19 }
20 }
21 }

然后,我觉得,这样,在每次请求时,还是要在Controller 里把数据取出来,然后再传到 Partial View里。 既然已经缓存了,就应该不用每次请求都要在Controller里把数据取出来才对!虽然数据层会有缓存。

所以我,能不能再省下去Controller取数据的消耗,于是又有了以下代码,其功能是:缓存Action生成的HTML到Memcached 里。

01 public static MvcHtmlString MemcacheHtmlRenderAction(this HtmlHelper htmlHelper, int duration, string actionName,string controllerName, RouteValueDictionary routeValues)
02 {
03 object obaear = htmlHelper.ViewContext.RouteData.DataTokens["area"];
04 string area = string.Empty;
05 if (obaear != null) area = obaear.ToString();
06 string key = string.Format("MemcacheHtmlRenderAction_{0}{1}{2}", area, controllerName,actionName);
07 object ob = DistCache.Get(key);
08 if (ob == null)
09 {
10
11 // htmlHelper.RenderAction(actionName, controllerName, routeValues);
12 StringWriter writer = new StringWriter(CultureInfo.CurrentCulture);
13 ActionHelper(htmlHelper, actionName, controllerName, routeValues, writer);
14 string wStr = writer.ToString();
15 DistCache.Add(key, wStr,TimeSpan.FromSeconds(duration));
16 MvcHtmlString mstr = MvcHtmlString.Create(wStr);
17
18 return mstr;
19 }
20 else { return MvcHtmlString.Create((string)ob); }
21 }

说明一下,Actionhelper的方法是在MVC原代码里提取出来的。 因为MVC2里的 Html.RenderAction方法并没有返回 MvcHtmlString的重载版。那位有更好的方法?

其实,MVC里的Action有输出缓存,所以直接在View里用 Html.RenderAction都可以解决很多问题了。这个主要是可以用程序管理缓存。

2.关于静态内容的放置

习惯上,静态内容会放在 mvc程序所在的目录下,比如说js,css,上传的图片等。但是这样的话,所有的静态请求都要经过 aspnet_isapi 处理,这样是非常不合算的。所以静态内容一般都会放在另外的子域上。http://www.86e0.com/t 是放在 cdn.86e0.com上。

3.关于强类型ViewModel

我基本上看了老赵的ASP.NET MVC最佳实践。 其中有一点,就是强烈推荐使用强类型的ViewModel. 我试了一些页面,发现用强类型的ViewModel,现阶段并不适用于我。因为我是用NbearLite,从数据库抓出来的大多是DataTable. 我是觉得DataTable+NbearLite蛮方便的,虽然没有动态语言的数据访问来得方便,但是比用Entity,ViewModel, DTO,等等来说,还是可以省下很多代码。然后,最重要的是,由于我这种站经常会修改,所以数据库改变,加字段,减字段是很经常性的事。但是,用 NbearLite + DataSet,DataTable,却非常方便。

所以我觉得,做Asp.net MVC,如果你不是用DDD,DDT的话,用DataTable还是可以的。因为DDD,DDT学习起来还是要点成本的。

4.关于URL生成

URL生成, 老赵写了一系列文章:

  • 各 种URL生成方式的性能对比
  • 各 种URL生成方式的性能对比(结论及分析)
  • 为 URL生成设计流畅接口(Fluent Interface)
  • URL生成方式性能优化结果我直接选择

    Raw方式了, 速度最快的,才是适合我的。呵。 而不是强类型的才是适合我的。

    最后,分享一个很实用的ASP.NET MVC 分页Helper.

    这个Helper引自重典老大的blog:http://www.cnblogs.com/chsword/ . 我在之前做了少少修改,现已经在http://www.86e0.com/t 上使用了。

    效果如下:

    image

    请大家注意生成的 URL, 是用 ?参数=页码 的方式。代码如下:

    01 /// <summary>
    02 /// 分页Pager显示
    03 /// </summary>
    04 /// <param name="html"></param>
    05 /// <param name="currentPageStr">标识当前页码的QueryStringKey</param>
    06 /// <param name="pageSize">每页显示</param>
    07 /// <param name="totalCount">总数据量</param>
    08 /// <returns></returns>
    09 public static string Pager(this HtmlHelper html, string currentPageStr, int pageSize, int totalCount)
    10 {
    11 var queryString = html.ViewContext.HttpContext.Request.QueryString;
    12 int currentPage = 1; //当前页
    13 if(!int.TryParse(queryString[currentPageStr], out currentPage)) currentPage = 1; //与相应的QueryString绑定
    14 var totalPages = Math.Max((totalCount + pageSize - 1) / pageSize, 1); //总页数
    15 var dict = new RouteValueDictionary(html.ViewContext.RouteData.Values);
    16
    17 var output = new StringBuilder();
    18
    19 foreach (string key in queryString.Keys)
    20 if (queryString[key] != null && !string.IsNullOrEmpty(key))
    21 dict[key] = queryString[key];
    22 if (totalPages > 1)
    23 {
    24 if (currentPage != 1)
    25 {//处理首页连接
    26 dict[currentPageStr] = 1;
    27 output.AppendFormat("<span class=\"p_home\">{0}</span>", html.RouteLink(" 首页", dict));
    28 }
    29 if (currentPage > 1)
    30 {//处理上一页的连接
    31 dict[currentPageStr] = currentPage - 1;
    32 output.AppendFormat("<span class=\"p_up\">{0}</span>", html.RouteLink(" 上一页", dict));
    33 }
    34 else
    35 {
    36 output.AppendFormat("<span class=\"p_disable\">{0}</span>","上一页");
    37 }
    38 int currint = 5;
    39 for (int i = 0; i <= 10; i++)
    40 {//一共最多显示10个页 码,前面5个,后面5个
    41 if ((currentPage + i - currint) >= 1 && (currentPage + i - currint) <= totalPages)
    42 if (currint == i)
    43 {//当前页处理
    44 output.Append(string.Format("<span class=\"p_current\">{0}</span>", currentPage));
    45 }
    46 else
    47 {//一般页处理
    48 dict[currentPageStr] = currentPage + i - currint;
    49 output.AppendFormat("<span class=\"p_num\">{0}</span>",html.RouteLink((currentPage + i - currint).ToString(), dict));
    50 }
    51 }
    52 if (currentPage < totalPages)
    53 {//处理下一页的链接
    54 dict[currentPageStr] = currentPage + 1;
    55 output.AppendFormat("<span class=\"p_down\">{0}</span>", html.RouteLink(" 下一页", dict));
    56 }
    57 else
    58 {
    59 output.AppendFormat("<span class=\"p_disable\">{0}</span>", "下一页");
    60 }
    61 if (currentPage != totalPages)
    62 {
    63 dict[currentPageStr] = totalPages;
    64 output.AppendFormat("<span class=\"p_last\">{0}</span>",html.RouteLink(" 末页", dict));
    65 }
    66 }
    67 output.AppendFormat("<span class=\"p_count\">第{0}页/共{1}页</span>", currentPage, totalPages);//这个统计加不加都行
    68 return output.ToString();
    69 }

    另: http://www.86e0.com/t 是做淘宝客类应用的。园子里还有谁在做淘宝客类网站么? 有的话多交流。^_^