ASP.NET中的数据绑定:哪个更快?
写这个随笔的起因是JGTM'2003.NET Blog的 这个同名随笔 http://blog.joycode.com/jgtm2000/posts/9089.aspx,该文中例举了5种asp.net数据绑定的写法
<%# DataBinder.Eval(Container.DataItem, "ColumnName") %>
<%# DataBinder.Eval(Container.DataItem, "ColumnName", null) %>
<%# DataBinder.Eval(Container, "DataItem.ColumnName", null) %>
<%# ((DataRowView)Container.DataItem)["ColumnName"] %>
<%# ((DataRowView)Container.DataItem).Row["ColumnName"] %>
由于对其结论:
实际上根据我们的测试,第4种写法的效率甚至比不上最普遍的第1种写法,也就是说这样进行优化适得其反!
颇为怀疑,所以做了一个粗粗的测试,再后来又做了一个详细的测试。
重要说明
repeater1.DataSource=ds.Tables[0].DefaultView; 和
repeater1.DataSource=ds.Tables[0];
两者有区别吗?dino esposito都犯了错误
在 构建web解决方案—应用asp.net 和ado.net 中,第12页和第13页中写了,如果DataSource是DataTable,则数据绑定语法写作
<%# ((DataRow)Container.DataItem)["field"] %>
当然,在实际中这是行不通的
如果我们了解asp.net 服务器控件数据绑定过程和DataTable的实现,就会理解为什么是上面的看法是错误的
对于实现IListSource接口的对象,像DataTable,服务器控件总是会读取其IListSource的GetList返回的数据作为数据源
DataTable的GetList实现是
IList IListSource.GetList()
{
return DefaultView;
}
因此,上面两行代码其实是一样的
所有,只有写成
<%# ((DataRowView)Container.DataItem)["field"] %>
编译才能通过
测试方法
这里说一下我的简单测试方法,大家可以看看这个测试方法是否有问题。对于机器和具体数字大家不必关系, 因为我们主要是看对比数据。由于机器上装的是.net framework 1.2,而且1.2又引入了Eval,所以我把Eval(ColumName)这种写法作为第六种测试
测试页面Default2.aspx
定义一个私有变量
private DateTime startDate;
在Page_Load中,我们读取记录并进行数据绑定
void Page_Load(object sender, System.EventArgs e)
{
SqlConnection con = new SqlConnection ("server=(local);user id=sa;pwd=;database=repair");
con.Open ();
SqlDataAdapter sda = new SqlDataAdapter ("select name from m_item", con);
DataSet ds = new DataSet ("item");
sda.Fill (ds, "item");
repeater1.DataSource = ds.Tables[0].DefaultView;
startDate = DateTime.Now;
repeater1.DataBind ();
sda.Dispose ();
con.Close ();
con.Dispose ();
}
在Page_LoadComplete时进行记录
void Page_LoadComplete(object sender, System.EventArgs e)
{
TimeSpan span=DateTime.Now.Subtract(startDate);
double speed=span.TotalMilliseconds;
SqlConnection con = new SqlConnection ("server=(local);user id=sa;pwd=;database=recording");
con.Open();
SqlCommand cmd = con.CreateCommand ();
cmd.CommandText = "insert into databinding (typeid,speed) values (@typeid,@speed)";
cmd.CommandType = CommandType.Text;
//@typeid的值根据每次测试不同改变
cmd.Parameters.AddWithValue ("@typeid", 6);
cmd.Parameters.AddWithValue("@speed",speed);
cmd.ExecuteNonQuery ();
cmd.Dispose ();
con.Close ();
con.Dispose ();
}
测试页面由default3.aspx完成
void Page_Load (object sender, EventArgs e)
{
for (int i = 1; i <= 100; i++)
{
Server.Execute ("default2.aspx");
}
}
这里我们对每种方法执行100次
绑定是通过一个Repeater控件来进行的
<asp:repeater id="repeater1" runat=server>
<itemtemplate>
<%# Eval("name") %>
</itemtemplate>
</asp:repeater>
其中Itemtemplate部分每次使用不同的方法
对测试后的数据我们通过查询进行分析
SELECT Typeid, AVG(Speed) AS Expr1
FROM databinding
GROUP BY Typeid
ORDER BY expr1
结果(单位:毫秒)
4 182.03125
5 182.65625
1 216.09375
6 221.5625
2 226.5625
3 302.34375
关于4和5 区别
因为4实际上调用的是
1、DataRow.Item(property, !dataView.IsOriginalVersion(index) ? DataRowVersion.Default : DataRowVersion.Original);
2、DataRow.Item(DataColumn,DataRowVersion)
而5首先调用过程是
1、DataRow.Item(string)
2、DataRow.Item(DataColumn)
3、DataRow.Item(DataColumn,DataRowVersion)
所以有一些细微的差别
说明:关于asp.net 2.0中的Eval实现
Eval在Page类中实现,其本质如下
protected object Eval(string expression)
{
return DataBinder.Eval(GetDataItem(), expression);
}
protected object Eval(string expression, string format)
{
return DataBinder.Eval(GetDataItem(), expression, format);
}
所以大家对它的效率应该能够估计到
补充
JGTM'2003发布了他的测试代码
我以为DataBind应该在asp.net环境下测试,并且应该调用DataBind方法,否则就变成一些方法调用性能测试了。
另外关于4和1的差别,我测试了32个字段的结果,还是是4远快于1
至于两者的差别,我还是说不出所以然来
关于DataBinder.Eval的内部实现,实际上类似如下代码
PropertyDescriptor propertyDescriptor = TypeDescriptor.GetProperties(Container.DataItem).Find(columName, true);
return propertyDescriptor.GetValue(Container.DataItem);
这里我同意JGTM'2003的说法,虽然这是通过看来是通过反射调用,但实际上对于实现ICustomTypeDescriptor接口的对象, 调用速度还是不能用平常眼光来看。DataRowView实现了ICustomTypeDescriptor方法,调用的是其对应Datatable的 GetPropertyDescriptorCollection方法,正如JGTM'2003所说,DataTable有 UpdatePropertyDescriptorCollectionCache方法,在第一次获取时组装数组
但即使如此,TypeDescriptor的调用并不能说是超过了对DataColumnCollection的检索
我构造了一个1000列和1000行的datatable
DataTable t = TestDataProvider.GetDataTable (1000, 1000);
DataView dv = new DataView (t);
DataRowView drv=dv[0];
DateTime startTime=DateTime.Now;
for (int i = 0; i < 10000; i++)
{
PropertyDescriptor pd = TypeDescriptor.GetProperties (drv).Find ("column1",true);
pd.GetValue (drv).ToString();
}
TimeSpan span=DateTime.Now.Subtract(startTime);
Response.Write ("<br/>" + span.TotalMilliseconds.ToString ());
startTime=DateTime.Now;
for (int i = 0; i < 10000; i++)
{
drv.Row["column739"].ToString();
}
span=DateTime.Now.Subtract(startTime);
Response.Write ("<br/>" + span.TotalMilliseconds.ToString ());
大家可以对比结果,DataColumnCollection的检索还是快了许多倍
结论
JGTM'2003的进一步测试终于找出了我们的分歧点在何处,即DataColumnCollection的Item(name)方法在列名大小 写全匹配时和不匹配时的处理不同。当大小写不匹配时,还会调用IndexOfCaseInsensitive(name)方法以获取正确的 DataColumn
我的测试总是字段名总是大小写匹配的,所以导致了一些细节差异。但我们还是可以总结出一些结论来
1、第4,5种在使用columnIndex中无疑是最快的
2、第4,5种在使用列名大小写匹配时无疑也是最快的
3、当4,5种在使用列名大小写不匹配时,同第一种相比结果很微妙,可以看JGTM'2003的测试结果
4、 但是结论3 这种比较是基于DataRowView对DataBinder.Eval调用的优化(即实现了ICustomTypeDescriptor) 接口而言的,对于没有实现该接口的数据集合绑定,如一个自定义的对象集合,无疑,4,5两种的调用的模式还是推荐之列的
我认为
原则上大家应该使用第4,5种,在大量字段时考虑字段名的匹配问题,如果是自己创建的商业组件,在一定时候可以考虑实现ICustomTypeDescriptor接口来对DataBinder.Eval进行一些优化