
2012年2月16日
asp.net 前台绑定后台变量方法总结<转帖>
经常会碰到在前台代码中要使用(或绑定)后台代码中变量值的问题。一般有<%= str%>和<%# str %>两种方式,这里简单总结一下。如有错误或异议之处,敬请各位指教。
一方面,这里所讲的前台即通常的.aspx文件,后台指的是与aspx相关联的CodeBehind,文件后缀名为.aspx.cs;另一方面,这里的绑定是指用户发出访问某一页面指令后,服务器端在执行过程中就已经将前台代码进行了赋值,而后生成html格式回传客户端显示,而并非已经显示到客户端后,然后通过其他方法(如ajax)去服务器端获取相应变量。
备注:上面说的两个文件是常见的代码隐藏(code-behind)模式,还有一种是代码嵌入(code-beside, inline)模式,那就是只存在aspx一个文件,而后台代码则写入此文件的<script type="text/javascript" runat="server"></script>之中(还有一些语法上区别),这对于本文讨论的问题略有影响,因为代码嵌入是声明性代码与C#/VB.NET代码都一起编译到一个类里面,而代码隐藏则将声明性代码与C#/VB.NET代码分开几次进行翻译/编译,因此前者是局部与局部(partial)的关系后者基类与派生类的关系,但这仅仅影响所能绑定变量的范围(与修饰符有关),下面会提到。以下均以代码隐藏模式为例。
一般来说,在前台代码的三种位置可能会用到(绑定)后台变量:
- 服务器端控件属性或HTML标签属性
- JavaScript代码中
- Html显示内容的位置(也就是开始标签与结束标签之间的内容,如<div>这里</div>(Html标签)或者<asp:Label ID="Label2" runat="server" Text="Label">这里</asp:Label>(服务器端控件),它作为占位符把变量显示于符号出现的位置)
对于第一种位置,有一些约束条件:
(1)一般的属性要求是字符串型或数值型(下面会提到有些服务器端属性支持属性为数据集合);
(2)并不是所有的属性都可以绑定变量,有些属性例如runat属性必须是"server"常量,即使绑定的字符串是server,也会导致分析器分析时出错;
(3)有一种属性,他要求属性值有约束(类型约束,比如服务器端控件要求TabIndex属性是short类型,或者字符串内容有约束),也应该在绑定时满足,否则依然可能编译时报错;
(4)还一种属性,虽然属性本身有约束,但即使绑定的变量不满足约束,也可以编译通过,比如input的checked属性,它只有checked字符串是合法的,但如果通过绑定获取到的字符串不是checked,那么这些属性将有自己内部处理机制,来保证可以正常使用;
(5)还要注意,即使对于同一类属性,服务器端和HTML的属性的处理机制也不同,同样是TabIndex(tabIndex),前者如果不满足,则分析器错误,后者则忽略这一问题。
对于第二种位置,一般只要绑定的后台变量和JavaScript中数据类型兼容即可。
对于第三种位置,如果绑定出现的位置不在服务器端控件内部,则没有约束条件,只要是常量字符串可以出现的位置,均可以绑定。但是对于置于服务器端控件内部,也就是上面那种<asp:Label ID="Label2" runat="server" Text="Label">这里</asp:Label>的方式,则有约束条件。通过总结,归纳为四类服务器端控件,如果绑定的代码出现在这些控件的开始和结束标签之间(这里所说的控件,是指如果绑定代码外有多层的嵌套控件包围,则是指包围绑定代码的最内层控件),有不同的显示结果:
(1)约束型控件:这类控件要求它的开始标签和结束标签中只能包含指定的子控件,因此如果在这里出现代码块,将编译错误。例如:
<asp:DataList runat="server"></asp:DataList>,在它之间,要求必须嵌套<ItemTemplate></ItemTemplate>。
(2)非嵌套类控件:这类控件,不允许在内部嵌套其他控件或标签,只能是常量字符串,它会将开始标签和结束标签中常量字符串内容作为他的属性。例如上面提到的TextBox,它会将标签间内容作为它的Text属性值。
(3)嵌套类控件:这类控件,可以嵌套其他任意控件,也可以包含字符串,因此可以正常显示绑定代码块所表示的字符串内容。例如Label控件、Panel等。
(4)数据绑定类控件:这类控件是ASP.NET提供的服务器端控件,除了可以绑定普通的变量类型,也可以绑定一个数据集合(只能采取下面的第二种方式实现)。
关于是否加引号:在以上三个位置使用时,是否应该将<%= str%>或<%# str %>置于单引号或双引号中呢?对于在不同位置,处理的方式是不同的:(具体请在下面两种方式的具体介绍时,加以体会)
(1)对于第一种位置,由于JavaScript是弱类型的,如果绑定时加引号,显然就认为就当做字符串来处理,这始终是正确的;如果绑定时不加引号,它将认为这是个数值型的,那么如果获取的真是数值,当然可以,如果是非数值型,则将产生脚本错误,这即使对于JavaScript赋值常量时,也是同样的:
var test1 = 123b;//运行时报错
var test2=123;//正确,是数值型
var test3="123b";//正确,字符串型
(2)对于第二种位置,经过测试,无论是对于服务器端控件属性还是HTML标签属性,加引号总是正确的;如果不加引号,则两种属性的处理方式不同:
- 对于服务器端控件属性,如果绑定的代码块不加引号,则编译时会提示“验证(ASP.NET):特性值前后必须加引号”的警告信息,但是生成为HTML后,对应生成的HTML属性已经被加上引号并获取了正确的绑定结果,因此加不加引号不会影响使用,但是建议对于规范的代码,还是加上为好;
- 对于HTML标签属性,如果不加引号,则编译时会提示“验证(XHTML 1.0 Transitional): 特性值前后必须加引号”的警告信息,并且生成为HTML属性也确实没有加上引号,那么虽然属性后面确实是没有加上引号的正确的绑定值,但是不一定能展示出想要看到的结果。比如对于input标签的value属性,如果绑定的字符串是" hello world from variable”,则在客户端的input显示出的内容实际上只是"hello”字符串,生效的属性值是一个被截断的字符串,它从属性后的一串字符串(若未加引号)的第一个非空字符开始,截止到下一个空字符的前一个字符为止(比如对于" hello world”,结果将是"hello”),因此,加上引号是必须的。
(3)对于第三种位置,加与不加引号,获取的值及其显示均不受影响。
因此建议,所有绑定表达式都加上引号,作为字符串获取,然后根据实际需求,用相应函数进行转换,得到所需要的类型。
另外,这里所说的后台变量是泛指的,包括如下:
- 成员变量
- 方法或属性的返回值
- 表达式,也就是所有后台能够执行的代码,运行后所得到的值(也就是直接将后台代码写在前台代码中,记得使用完全限定名或在后台中using相关namespace)
-
数据集合
后台变量有一些约束条件,需要满足:
(1)变量修饰符要求。变量是静态或者实例字段均可。对于代码隐藏模式的ASP.NET,以上的所述的变量必须为public或protected类型(因为是基类与派生类的关系),private或者internal都不行,而代码嵌入模式则任何修饰符的变量均可访问(一个类内部的关系)。
(2)变量类型要求。由于前台属性一般是字符串类型,而JavaScript基本类型也就是字符串型、数字型、布尔型,因此对应的变量应该也是这几种方式,其余类型如果不被支持(如复杂类型、数组、引用类型等),前台获取的就是调用了变量的ToString()方法所得到的字符串。因此,在绑定时,要根据情况看是否能进行隐式类型转换,必要时还要用相关函数来强制转换,以保证前台可以获得正确的值。当然,对于数据绑定类控件,它的有些属性可以为数据集合,但这时的绑定只能通过下面第二种方式才被支持。
以上是一些概念和基本约束,这些都是两种方式都应该满足的,下面具体介绍两种方式,来实现前台代码中(以下称为代码块)绑定后台变量的功能。
一. <%= str%>
此种方式其实是ASP 时代就支持的,ASP 通过包含在 < % 和 %>中的表达式将执行结果输出到客户浏览器 , 如:< % =test %>就是将变量test的值发送到客户浏览器中。在ASP.NET中,这个表达式依然可以使用,并可以出现在前台代码的上述三个位置,但是要注意,除了上述的一般性约束外,对于控件属性,还必须是绑定到非服务器端控件的属性。另外,它只能绑定上面讲的前三种变量类型,不支持绑定数据集合。例子如下:
后台代码:
public partial class WebForm2 : System.Web.UI.Page
{
public string GetVariableStr;//注意变量的修饰符
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GetVariableStr = "hello world from variable";
}
}
protected string GetFunctionStr()//注意返回值的修饰符
{
return "hello world from Function";
}
}
前台代码:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
function fun() {
var str = '<%= DateTime.Now %>';
//前台位置1,绑定的是第三种变量类型(也是第二种方式,?因为Now是个属性)
alert(str);
}
</script>
</head>
<body onload="fun()">
<form id="form1" runat="server">
<div>
<input type="text" value="<%= GetVariableStr %>" />
<%--前台位置2,绑定的是成员变量--%>
"<%= GetFunctionStr() %>"
<%--前台位置3,绑定的是一个方法的返回值>--%>
</div>
</form>
</body>
</html>
一些错误的使用:
之所以说第一种绑定方式要用于非服务器端控件的属性,是因为如果应用于这些服务器端属性时,这些代码实际上不被解析。 比如:
<asp:Label ID="Label1" runat="server" Text="<%= GetVariableStr %>"></asp:Label>
<asp:TextBox ID="TextBox1" runat="server" Text="<%= GetVariableStr %>"></asp:TextBox>
则显示出来的Label1的文本是空,而TextBox中文本是"<%= GetVariableStr %>”,所以记住,对服务器端控件的属性加这样的代码块,将不被解析,而是将这一字符串直接作为属性值了,所以不是想要的结果。如果引号也不加上,将会编译错误,提示“服务器标记不能包含 <% ... %> 构造。”。
这里结合开篇提到的关于将绑定代码快置于“Html显示内容的位置”时,如果在服务器端控件内,那四类控件如何显示的问题。如下:
<asp:Label ID="Label1" runat="server" >"<%= GetVariableStr %>"</asp:Label>
<asp:TextBox ID="TextBox1" runat="server" >"<%= GetVariableStr %>"</asp:TextBox>
其中,Label1属于嵌套类控件,Label1确实显示了正确的结果,TextBox属于非嵌套类控件,TextBox如果用这种方式,将会产生编译错误,提示“此上下文中不支持代码块。”
二. <%# str %>
ASP.NET 引入了一种新的声明语法 <%# %>。该语法是在 .aspx 页中使用数据绑定的基础,所有数据绑定表达式都必须包含在这些字符中。这里从用法和适用范围等方面与第一种绑定方式进行区分。
从出现的位置来看,除了能出现在第一种代码块出现的所有位置外,他还可以出现在服务器端控件的属性中。
从绑定的变量类型上看,他还可以配合ASP.NET的数据绑定类控件,来绑定上述的第四种“变量”类型,就是数据集合(DropDownList,DataList,DataGrid,ListBox这些是数据绑定类控件,数据集合包括ArrayList(数组),Hashtable(哈稀表,DataView(数据视图),DataReader等)。
从用法上看,在前台代码中除了在相应位置写上<%# %>外,在后台代码中,还需要使用DataBind()方法。以下是实例:
前台代码:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
function fun() {
var str = '<%# DateTime.Now %>';
alert(str);
}
</script>
</head>
<body onload="fun()">
<form id="form1" runat="server">
<div>
<input type="text" value="<%# GetVariableStr %>" /><br />
"<%# GetVariableStr %>"
<asp:Label ID="Label1" runat="server" Text="<%# GetVariableStr %>"></asp:Label>
<%--此种方式可以绑定服务器端控件的属性--%>
<asp:DropDownList ID="DropDownList1" runat="server" DataSource='<%# arraylist %>'>
<%-- 将集合绑定到数据绑定类控件,通过DataSource属性来实现,从而在下拉框看到集合中的内容--%>
</asp:DropDownList>
<asp:DataList ID="DataList1" runat="server" DataSource='<%# dt %>'>
<%-- 同上,绑定了DataTable数据集合?--%>
<ItemTemplate>
<table border="1" cellpadding="0" cellspacing="0">
<tr>
<td>
<asp:Label ID="Label2" runat="server" Text='<%# Bind("row0")%>'></asp:Label>
<%--由于绑定的数据集合具有多列,并且此数据绑定类控件支持模板,
因此需要在模板中指定需要绑定的列以及格式--%>
</td>
<td>
<%# Eval("row1")%>
</td>
</tr>
</table>
</ItemTemplate>
</asp:DataList>
</div>
</form>
</body>
</html>
可以看出,这种方式在使用时,不但可以实现(取代)<%=... %>所满足的功能,还可以绑定服务器控件属性(如上面的Label1),也可以将集合类型绑定到支持的数据绑定类控件。在用法上,前台代码除了对数据绑定类控件绑定数据集合外有所差别,其他的使用上与第一种没区别。在绑定类控件的模板中,如何使用Eval、Bind、DataBinder.Eval等,不在此文讨论中,可以参考下面链接的参考文章。
后台代码:
public partial class WebForm2 : System.Web.UI.Page
{
public string GetVariableStr;
public ArrayList arraylist;
public DataTable dt;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GetVariableStr = "hello world from variable";
arraylist = new ArrayList();
arraylist.Add("选?项?1");
arraylist.Add("选?项?2");
dt = new DataTable();
dt.Columns.Add("row0");
dt.Columns.Add("row1");
DataRow dr1 = dt.NewRow();
dr1[0] = "1.1";
dr1[1] = "1.2";
DataRow dr2 = dt.NewRow();
dr2[0] = "2.1";
dr2[1] = "2.2";
dt.Rows.Add(dr1);
dt.Rows.Add(dr2);
Page.DataBind();
//DropDownList1.DataBind();
//DataList1.DataBind();
}
}
}
在后台代码中,与第一种方式唯一不同的,就是需要调用DataBind方法。只有执行了相应控件的DataBind方法,前台代码中这些控件中使用<%# %>的绑定才会发生(并且控件内部的所有绑定也会发生,比如又嵌套了一个绑定后台数据的控件),否则得话将不会被赋值,而是默认空值。上面我们用的是Page的DataBind方法,那么整个页面所有绑定都会执行。当然,如果我们只执行DataList1或者DropDownList1的DataBind方法,那么只有相应控件的绑定才会发生。需要注意的是,这里说的需要执行DataBind包括了显示和隐式执行,有些数据绑定类控件,当它们通过 DataSourceID 属性绑定到数据源控件时,会通过隐式调用 DataBind 方法来执行绑定。这时就不必显示的再次调用了。
两者区别:
两种绑定方式上,他们的约束基本相同,都要求与属性匹配,出现在他们可以出现的位置。后者的使用位置更广泛,尤其是支持服务器端控件和绑定数据集合。后台代码方面,后者需要调用DataBind才能完成绑定,前者则没有这方面要求。这里主要区别一下两者在执行机制上的区别:<%=...%>是在程序执行时调用(应该是在页面的RenderControl事件过程中完成的,也就是通常情况下我们可以看到的后台代码都执行完毕后再去到前台代码中进行赋值绑定),而<%#... %>是在DataBind()方法之后被调用,一旦调用了DataBind(),则它对应的控件将绑定变量,因此,请注意:如果在DataBind()后再修改变量,那么绑定的就不是最新值了,这就需要在完成变量的赋值后,再去DataBind()。其实这两种方式,它的运行过程可以在VS中通过设置断点来看看,看两者的绑定赋值分别是在什么时候发生的。
尚存的疑问:
1.不知道为什么不能获取到internal修饰的变量?
答:此问题已经在下一篇文章《ASP.NET前台无法访问后台internal类型变量的问题》中分析并解决。(2010-10-25日更新)
补充1:回答关于9楼的提到的ASP.NET 4.0中的<%: xxxx%>(2010-11-4)
答:这种方式是ASP.NET4.0中新加入的绑定方式,常用于MVC中,但普通webform中也可使用。他的功能其实就是对绑定的值进行一下编码,因此,一下两者是等同的:
<%= Server.HtmlEncode("<b>test</b>") %>
<%: "<b>test</b>" %>;
补充2:关于各种百分号的功能区分(2010-11-4)
答:本文上面讲了两种百分号的用法,在加上补充1中的,一共讲了三种,其实还有一种百分号用法就是<% func(xx)%>,也就是百分号后不带任何符号,然后执行一段代码,一句话概括:
在百分号內 , 如果百分号后面不带任何符号(冒号、等号、井号) , 即表示要执行一段代码而已,此处不包含任何输出信息;若带符号,即表示执行此处的代码,并且将执行后返回的值绑定(或者显示)在此处。
posted @ 2012-02-16 19:39 阿杜008 阅读(219) 评论(0)
编辑

2012年2月15日
Linq 学习笔记<转帖>
转自 http://blog.sina.com.cn/s/blog_5df2629a0100lr3u.html
一·前言
Linq 英文全称Language Integrated Query,它提供了C#编程语言中的查询语法,可以使用相同的语法访问不同的数据源。并且Linq还提供了不同数据源的抽象层,所以可以使用相同的语法。本次笔记的主要内容如下:
● 用List<T>在对象上执行传统查询
● 扩展方法
● λ表达式
● LINQ 查询
● 标准查询操作符
● 表达式树
● LINQ 提供程序
一。用List<T>在对象上执行传统查询
首先我们来看一个最简单的例子,请看下面的代码:
List<string> strList = new List<string>();
strList.Add("AAA");
strList.Add("BABB");
strList.Add("ACCC");
strList.Add("BBB");
strList.Add("CCC");
var res = strList.FindAll(delegate(string s) { return s.Contains("A"); });
res.Sort(delegate(string a, string b) { return a.CompareTo(b); });
this.GridView1.DataSource = res;
this.GridView1.DataBind();
这段代码是从strList中查找所有包含字母A的记录,并且将得到的结果进行进行从小到大的排序(如果将return a.CompareTo(b); 换成return b.CompareTo(a);结果则是从大到小的顺序进行排列了)。相信很多人用过这样的方式来对结果集进行过滤和排序操作。但是如果你想在任何集合都可以使用这两个方法,那你就可能会使用到扩展方法了。扩展方法是C#3.0的新增特性,这也是上述例子迈向LINQ 的第一个变化。下面我们就来介绍一下扩展方法。
二。扩展方法
请看下面关于扩展方法的例子
public static class helper
{
public static string MD5Hash(this string s)
{
return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(s, "MD5");
}
public static bool In(this object o, IEnumerable b)
{
foreach (object obj in b)
{
if (obj == o)
return true;
}
return false;
}
}
// 调用扩展方法
Response.Write("123456".MD5Hash());
Response.Write("1".In(new[]{"1","2","3"}));
上面的例子是对string进行了扩展,这样的话只要在当前命名空间下任何string对象都可以利用该扩展方法。这样会给代码带来很大的方便。
既然有了扩展方法,我们的Linq自然就很容易就出现了。就如同下面的Linq例子一样
MethodInfo[] methods = typeof(string).GetMethods();
var result = from m in methods
where m.IsStatic != true
select m.Name;
foreach (var r in result)
{
Response.Write(r.ToString()+"<br/>");
}
这就是Linq最典型的样子。是不是比较像我们平时用的SQL呢?呵呵虽然还有不小的差别,但是Linq的引入实在是C#的一种革命性的变化。他不仅好用,更给枯燥的C#代码注入了一股鲜活的思想。
三。λ表达式
C# 3.0 给匿名方法提供了一个新的语法——λ表达式(也叫做Lambda表达式)。除了把匿名方法传送给Where()、OrderbyDescen
ding()和Select()方法之外,还可以使用λ表达式。
λ 表达式参见第7 章。λ表达式在LINQ 中非常重要,所以下面复习一下该语法。详细信息可参见第7
章。
比较λ表达式和匿名委托,会发现许多类似之处。λ运算符=>的左边是参数,不需要添加参数类型,因
为它们是由编译器解析的。λ运算符的右边定义了执行代码。在匿名方法中,需要花括号和return 语句。在
λ表达式中,不需要这些语法元素,因为它们是由编译器处理的。如果λ运算符右边有多个语句,也可以使
用花括号和return 语句。
例如下面的例子
var list = new[] { "aa", "bb", "ca" };
var resultList = Array.FindAll(list, s => (s.IndexOf("a") > -1));
foreach (var v in resultList)
Response.Write(v+"<br/>");
s就像是我们在第一个例子中使用的delegate(string s)里面的s一样只是这种使用方法更加的灵活和方便。
四。LINQ 查询
我们就以NorthWind数据库为例,添加一个Linq to Sql Classes命名为Customer.dbml,然后打开Server Explorer设置好连接参数,选择Customers表直接拖拽到Customer.dbml,打开Customer.design.cs文件我们可以看到Visual Studio为我们生成了很多代码。这些代码我们会在后面的内容里详细的讲解。现在主要来讲一下Linq查询以及后面的内容。
LINQ 查询是C#语言中的一个简化查询记号。编译器编译查询表达式,调用扩展方法。查询表达式只是C#中的一个语法,但不需要修改底层的IL 代码。查询表达式必须以from子句开头,以select或group子句结束。在这两个子句之间,可以使用where、orderby、join、let 和其他from子句。注意,变量query只指定了LINQ查询。该查询不是通过这个赋值语句执行的,只要使用foreach循环访问查询,该查询就会执行。
推迟查询的执行
在运行期间定义查询表达式时,查询就不会运行。查询会在迭代数据项时运行。再看看扩展方法Where()。它使用yield return语句返回谓词为true的元素。因为使用了yield return语句,所以编译器会创建一个枚举器,在访问枚举中的项后,就返回它们。
public static IEnumerable<T> Where<T>(this IEnumerable<T> source,Func<T, bool> predicate)
{
foreach (T item in source)
if (predicate(item))
yield return item;
}
请看一下下面的代码
var names = new List<string>{"Nino","Alberto", "Juan", "Mike", "Phil"};
var namesWirhJ = (from name in names where name.StartsWith("J") orderby name select name);
Response.Write("First Iteration<br/>");
foreach (var name in namesWirhJ)
Response.Write(name + "<br/>");
names.Add("John");
names.Add("Jim");
names.Add("Jack");
names.Add("Denny");
Response.Write("Second iteration<br/>");
foreach (var name in namesWirhJ)
Response.Write(name + "<br/>");
其返回结果为
First iteration
Juan
Second iteration
Jack
Jim
John
Juan
但是当我们修改一下代码将 var namesWirhJ = (from name in names where name.StartsWith("J") orderby name select name);换做var namesWirhJ = (from name in names where name.StartsWith("J") orderby name select name).ToList();那么结果就成了
First iteration
Juan
Second iteration
Juan
这就是我们需要注意的地方了,每次在迭代中使用查询时,都会调用扩展方法。在大多数情况下,这是非常有效的,因为我们可以检测出源数据中的变化。但是在一些情况下,这是不可行的。调用扩展方法ToArray()、ToEnu
merable()、ToList()等可以改变这个操作。这也就是为什么我们需要演示ToList()方法了。ToList 迭代集合,返回一个实现了List<string>的集合。之后对返回的列表迭代两次,在这个过程中,数据源被修改了,但是原来的集合并没有改变。这样可以用来实现一些比较复杂的业务逻辑。
五。标准查询操作符
Where、OrderByDescending 和Select 只是LINQ 的几个查询操作符。LINQ 查询为最常用的操作符定
义了一个声明语法。还有许多标准查询操作符。下表列出了Linq的标准查询操作符。
|
Where
OfType<TResult>
|
过滤操作符定义了返回元素的条件。在Where 查询操作符中,可以使用谓词,
例如λ表达式定义的谓词,来返回布尔值。OfType<TResult>根据类型过滤元素,只
返回TResult 类型的元素
|
|
Select和SelectMany
|
投射操作符用于把对象转换为另一个类型的对象。Select 和SelectMany 定义
了根据选择器函数选择结果值的投射
|
|
OrderBy,ThenBy
OrderByDescending
ThenByDescending
Reverse
|
排序操作符改变所返回的元素的顺序。OrderBy 按升序排序,
OrderByDescending 按降序排序。如果第一次排序的结果很类似,就可以使用
ThenBy 和ThenBy Descending 操作符进行第二次排序。Reverse 反转集合中元
素的顺序
|
|
Join,GroupJoin
|
连接运算符用于合并不直接相关的集合。使用Join 操作符,可以根据键选择
器函数连接两个集合,这类似于SQL 中的JOIN。GroupJoin 操作符连接两个集
合,组合其结果
|
|
GroupBy
|
组合运算符把数据放在组中。GroupBy 操作符组合有公共键的元素
|
|
Any,All,Contains
|
如果元素序列满足指定的条件,量词操作符就返回布尔值。Any,All 和Contains
都是量词操作符。Any 确定集合中是否有满足谓词函数的元素;All 确定集合
中的所有元素是否都满足谓词函数;Contains 检查某个元素是否在集合中。这些操作
符都返回一个布尔值
|
|
Take,Skip,
TakeWhile
SkipWhile
|
分区操作符返回集合的一个子集。Take、Skip、TakeWhile 和SkipWhile 都是
分区操作符。使用它们可以得到部分结果。使用Take 必须指定要从集合中提
取的元素个数;Skip 跳过指定的元素个数,提取其他元素,TakeWhile 提取条件为真的
元素
|
|
Distinct,Union
Intersect,Except
|
Set 操作符返回一个集合。Distinct 从集合中删除重复的元素。除了Distinct
之外,其他Set 操作符都需要两个集合。Union 返回出现在其中一个集合中的
元素。Intersect 返回两个集合中都有的元素。Except 返回只出现在一个集合
中的元素
|
|
First
FirstOrDefault
Last
LastOrDefault
ElementAt
ElementAtOrDefault
Single
SingleOrDefault
|
这些元素操作符仅返回一个元素。First 返回第一个满足条件的元素。
FirstOrDefault 类似于First,但如果没有找到满足条件的元素,就返回类型
的默认值。Last 返回最后一个满足条件的元素。ElementAt 指定了要返回的元
素的位置。Single 只返回一个满足条件的元素。如果有多个元素都满足条件,
就抛出一个异常
|
|
Count,Sum,Min,
Max,Average,
Aggregate
|
合计操作符计算集合的一个值。利用这些合计操作符,可以计算所有值的总和、
元素的个数、值最大和最小的元素,平均值等
|
|
ToArray
ToEnumerable
ToList
ToDictionary
toType<T>
|
这些转换操作符将集合转换为数组、IEnumerable、IList、IDictionary 等
|
|
Empty,Range,
Repeat
|
这些生成操作符返回一个新集合。使用Empty,集合是空的,Range 返回一系列数
字,Repeat 返回一个始终重复一个值的集合
|
下面就来介绍一些查询的示例:
1。Linq查询
var racers = from r in Formula1.GetChampions()
where r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria") select r;
foreach (var r in racers)
{
Responose.Write("{0:A}", r);
}
使用扩展方法的查询
并不是所有的查询都可以用LINQ查询完成。也不是所有的扩展方法都映射到LINQ查询子句上。高级查询需要使用扩展方法。为了更好地理解带扩展方法的复杂查询,最好看看简单的查询是如何映射的。使用扩展方法Where()和Select(),会生成与前面LINQ 查询非常类似的结果:
var racers = Formula1.GetChampions().Where(r => r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria")).Select(r = > r);
2。用索引来过滤
不能使用LINQ 查询的一个例子是Where()方法的重载。在Where()方法的重载中,可以传送第二个参数——索引。索引是过滤器返回的每个结果的计数器。可以在表达式中使用这个索引,执行基于索引的计算。下面的代码由Where()扩展方法调用,它使用索引返回姓氏以A开头、索引为偶数的赛手:
var racers = Formula1.GetChampions().Where((r, index) => r.LastName.StartsWith("A") &&
index % 2 != 0);
foreach (var r in racers)
{
Responose.Write("{0:A}", r);
}
3。类型过滤
为了进行基于类型的过滤,可以使用OfType()扩展方法。这里数组数据包含string和int对象。使用OfType()扩展方法,把string类传送给泛型参数,就从集合中返回字符串。
object[] data = { "one", 2, 3, "four", "five",6 };
var query = data.OfType<string>();
foreach (var s in query)
{
Console.WriteLine(s);
}
4。复合的from子句
var ferrariDrivers = from r in Formula1.GetChampions() from c in r.Cars where c == "Ferrari"
orderby r.LastName select r.FirstName + " " + r.LastName;
C#编译器把复合的from 子句和LINQ 查询转换为SelectMany()扩展方法。SelectMany()可用于迭代序列的序列。示例中SelectMany()方法的重载版本如下所示:
public static IEnumerable<TResult> SelectMany<TSource,TCollection,TResult>(this IEnumerable<TSource> source,Func<TSource,IEnumerable<TCollection>> collectionSelector,Func<TSource,TCollection,TResult>resultSelector);
第一个参数是隐式参数,从GetChampions()方法中接收Racer对象序列。第二个参数是collectionSelector委托,它定义了内部序列。在λ表达式r=>r.Cars中,应返回赛车集合。第三个参数是一个委托,现在为每个赛车调用该委托,接收Racer和Car对象。λ表达式创建了一个匿名类型,它带Racer和Car属性。这个SelectMany()方法的结果是摊平了赛手和赛车的层次结构,为每辆赛车返回匿名类型的一个新对象集合。
这个新集合传送给Where()方法,过滤出驾驶Ferrari 的赛手。最后,调用OrderBy()和Select()方法:
var ferrariDrivers = Formula1.GetChampions().SelectMany(r => r.Cars,
(r, c) => new { Racer = r, Car = c }).Where(r => r.Car == "Ferrari").OrderBy(r =>r.Racer.LastName).Select(r => r.Racer.FirstName + " " +r.Racer.LastName);
把SelectMany()泛型方法解析为这里使用的类型,所解析的类型如下所示。在这个例子中,数据源是
Racer类型,所过滤的集合是一个string数组,当然所返回的匿名类型的名称是未知的,这里显示为TResult:
public static IEnumerable<TResult> SelectMany<Racer, string, TResult>(
this IEnumerable<Racer> source,Func<Racer,IEnumerable<string>> collectionSelector,
Func<Racer,string,TResult> resultSelector);
查询仅从LINQ 查询转换为扩展方法,所以结果与前面的相同。
5。排序
var racers = from r in Formula1.GetChampions() where r.Country == "Brazil" orderby r.Wins descending select r;
orderby子句解析为OrderBy()方法,orderby descending子句解析为OrderBy Descending()方法:
var racers = Formula1.GetChampions().Where(r => r.Country == "Brazil").OrderByDescending(r => r.Wins).Select(r => r);
OrderBy()和OrderByDescending()方法返回IOrderEnumerable<TSource>。这个接口派生于接口IEnumerable<TSource>,但包含一个额外的方法CreateOrderedEnumerable-<TSource>()。这个方法用于进一步给序列排序。如果根据关键字选择器来排序,两项的顺序相同,就可以使用ThenBy()和ThenByDescending()方法继续排序。这两个方法需要IOrderEnumerable<TSource>才能工作,但也返回这个接口。所以,可以添加任意多个ThenBy()和ThenByDescending()方法,对集合排序。
使用LINQ 查询时,只需把所有用于排序的不同关键字(用逗号分隔开)添加到orderby 子句中例如
var racers = (from r in Formula1.GetChampions() orderby r.Country, r.LastName, r.FirstName select r).Take(10);
6。分组
var countries = from r in Formula1.GetChampions() group r by r.Country into g orderby g.Count() descending, g.Key where g.Count() >= 2 select new { Country = g.Key, Count = g.Count() };
foreach (var item in countries)
{
Response.Write("{0, -10} {1}",item.Country, item.Count);
}
扩展方法的分组表示:
var countries = Formula1.GetChampions().GroupBy(r => r.Country).OrderByDescending(g => g.Count()).
ThenBy(g => g.Key).Where(g => g.Count() >= 2).Select(g => new { Country = g.Key,Count = g.Count()});
7。对嵌套的对象分组
如果分组的对象应包含嵌套的对象,就可以改变select 子句创建的匿名类型。
var countries = from r in Formula1.GetChampions() group r by r.Country into g orderby g.Count() descending, g.Key where g.Count() >= 2 select new
{
Country = g.Key, Count = g.Count(),
Racers = from r1 in g orderby r1.LastName select r1.FirstName + " " + r1.LastName
};
foreach (var item in countries)
{
Response.Write("{0, -10} {1}", item.Country, item.Count);
foreach (var name in item.Racers)
{
Response.Write("{0}; ", name);
}
Response.Write("<br/>");
}
8。连接
使用join 子句可以根据特定的条件合并两个数据源,但之前要获得两个要连接的列表。
var racers = from r in Formula1.GetChampions() from y in r.Years where y > 2003 select new
{
Year = y,
Name = r.FirstName + " " + r.LastName
};
var teams = from t in Formula1.GetContructorChampions() from y in t.Years where y > 2003
select new { Year = y, Name = t.Name };
有了这两个查询,再通过子句join t in teams on r.Year equals t.Year就可以得到结果集了。
var racersAndTeams = from r in racers join t in teams on r.Year equals t.Year select new
{
Year = r.Year,
Racer = r.Name,
Team = t.Name
};
Response.Write("Year Champion " + "Constructor Title");
foreach (var item in racersAndTeams)
{
Response.Write("{0}: {1,-20} {2}",item.Year, item.Racer, item.Team);
}
9。设置操作
扩展方法Distinct()、Union()、Intersect()和Except()都是设置操作。
var ferrariDrivers = from r in Formula1.GetChampions() from c in r.Cars where c == "Ferrari"
orderby r.LastName select r;
现在建立另一个相同的查询,但where 子句的参数不同,以获得所有驾驶McLaren 的冠军。最好不要再次编写相同的查询。而可以创建一个方法,给它传送参数car:
private static IEnumerable<Racer> GetRacersByCar(string car)
{
return from r in Formula1.GetChampions() from c in r.Cars where c == car orderby r.LastName select r;
}
但是,因为该方法不需要在其他地方使用,所以应定义一个委托类型的变量来保存LINQ 查询。变量racerByCar 必须是一个委托类型,它需要一个字符串参数,返回IEnumerable <Racer>,类似于前面实现的方法。为此,定义了几个泛型委托Func<>,所以不需要声明自己的委托。把一个λ表达式赋予变量racerByCar。λ表达式的左边定义了一个car 变量,其类型是Func 委托的第一个泛型参数(字符串)。右边定义了LINQ 查询,它使用该参数和where 子句:
Func<string, IEnumerable<Racer>> racersByCar = Car => from r in Formula1.GetChampions() from c in r.Cars where c == car orderby r.LastName select r;
现在可以使用Intersect()扩展方法,获得驾驶Ferrari 和McLaren 的所有冠军:
Response.Write("World champion with " + "Ferrari and McLaren");
foreach (var racer in racersByCar("Ferrari").
Intersect(racersByCar("McLaren")))
{
Response.Write(racer);
}
10。分区
扩展方法Take()和Skip()等的分区操作可用于分页,例如显示5×5 个赛手。在下面的LINQ 查询中,扩展方法Take()和Skip()添加到查询的最后。Skip()方法先忽略根据页面的大小和实际的页数计算出的项数,再使用方法Take()根据页面的大小提取一定数量的项:
int pageSize = 5;
int numberPages = (int)Math.Ceiling(Formula1.GetChampions().Count()/(double)pageSize);
for (int page = 0; page < numberPages; page++)
{
Response.Write("Page {0}", page);
var racers = (from r in Formula1.GetChampions() orderby r.LastName select r.FirstName + " " + r.LastName).Skip(page * pageSize).Take(pageSize);
foreach (var name in racers)
{
Response.Write(name);
}
Response.Write();
}
11。合计操作符
合计操作符如Count()、Sum()、Min()、Max()、Average()和Aggregate(),不返回一个序列,而返
回一个值。
var query = from r in Formula1.GetChampions() where r.Years.Count() > 3 orderby r.Years.Count() descending select new
{
Name = r.FirstName + " " +r.LastName,
TimesChampion = r.Years.Count()
};
foreach (var r in query)
{
Response.Write("{0} {1}", r.Name, r.TimesChampion);
}
Sum()方法汇总序列中的所有数字,返回这些数字的和。下面的Sum()用于计算一个国家赢得比赛的
总次数。首先根据国家对赛手分组,再在新创建的匿名类型中,给Wins 属性赋予某个国家赢得比赛的总
次数。
var countries = (from c in from r in Formula1.GetChampions() group r by r.Country into c
select new
{
Country = c.Key,
Wins = (from r1 in c select r1.Wins).Sum()
}
orderby c.Wins descending, c.Country select c).Take(5);
foreach (var country in countries)
{
Response.Write("{0} {1}",country.Country, country.Wins);
}
方法Min()、Max()、Average()和Aggregate()的使用方式与Count()和Sum()相同。Min()返回集合
中的最小值,Max()返回集合中的最大值,Average()计算集合中的平均值。对于Aggregate()方法,可
以传送一个λ表达式,对所有的值进行汇总。
12。转换
查询可以推迟到访问数据项时再执行。在迭代中使用查询,查询会执行。而使用转换操作符会立即执行查询,把结果放在数组、列表或字典中。在下面的例子中,调用ToList()扩展方法,立即执行查询,把结果放在List<T>中:
List < Racer > racers = (from r in Formula1.GetChampions()
where r.Starts > 150 orderby r.Starts descending select r).ToList();
foreach (var racer in racers)
{
Response.Write("{0} {0:S}", racer);
}
把返回的对象放在列表中并没有这么简单。例如,对于集合中从赛车到赛手的快速访问,可以使用新类Lookup<TKey, TElement>。
提示:
Dictionary<TKey, TValue>只支持一个键对应一个值。在System.Linq 命名空间的类Lookup<TKey,
TElement>中,一个键可以对应多个值。这些类详见第10 章。
使用复合的from 查询,可以摊平赛手和赛车序列,创建带有Car 和Racer 属性的匿名类型。在返回的Lookup 对象中,键的类型应是表示汽车的string,值的类型应是Racer。为了进行这个选择,可以给ToLookup()方法的一个重载版本传送一个键和一个元素选择器。键选择器表示Car 属性,元素选择器表示Racer 属性。
ILookup<string, Racer> racers = (from r in Formula1.GetChampions() from c in r.Cars select new
{ Car = c, Racer = r }).ToLookup(cr = > cr.Car, cr = > cr.Racer);
if (racers.Contains("Williams"))
{
foreach (var williamsRacer in racers["Williams"])
{
Response.Write(williamsRacer);
}
}
如果需要在未类型化的集合上使用LINQ查询,例如ArrayList,就可以使用Cast()方法。在下面的例子中,基于Object 类型的ArrayList 集合用Racer 对象填充。为了定义强类型化的查询,可以使用Cast()方法。
System.Collections.ArrayList list = new System.Collections.ArrayList(Formula1.GetChampions() as
System.Collections.ICollection);
var query = from r in list.Cast <Racer>() where r.Country == "USA" orderby r.Wins descending select r;
foreach (var racer in query)
{
Response.Write("{0:A}", racer);
}
13。生成操作符
生成操作符Range()、Empty()和Repear()不是扩展方法,而是返回序列的正常静态方法。在LINQto Objects 中,这些方法可用于Enumerable 类。有时需要填充一个范围的数字,此时就应使用Range()方法。这个方法把第一个参数作为起始值,把第二个参数作为要填充的项数。
var values = Enumerable.Range(1, 20);
foreach (var item in values)
{
Response.Write("{0} ", item);
}
Range()方法不返回填充了所定义值的集合,这个方法与其他方法一样,也推迟执行查询,返回一个RangeEnumerator,其中只有一个yield return 语句,来递增值。可以把该结果与其他扩展方法合并起来,获得另一个结果,例如使用Select()扩展方法:
var values = Enumerable.Range(1, 20).Select(n = > n * 3);
Empty()方法返回一个不返回值的迭代器,它可以用于参数需要一个集合,且可以给参数传送空集
合的情形。Repeat()方法返回一个迭代器,该迭代器把同一个值重复特定的次数。
posted @ 2012-02-15 21:37 阿杜008 阅读(128) 评论(0)
编辑