3.1 使用CAML查询列表
3.1.1 概述
CAML是Collaboration Application Markup Language,协作应用程序标记语言的缩写。SharePoint使用此语言描述对列表的查询,同时也描述字段类型定义、站点模板定义等。
查询列表数据可以使用Microsoft.SharePoint.SPQuery对象,我们先看一个简单的例子:
SPQuery query=new SPQuery(); query.ViewFields=@"<FieldRef Name='Title' /><FieldRef Name='Status'/>"; query.Query= @"<Where> <Eq> <FieldRef Name='Title' /> <Value Type='Text'>Test01</Value> </Eq> </Where>"; try{ using (SPSite site=new SPSite("http://WIN-I53T3JI4M13")){ SPWeb web=site.RootWeb; SPList list=web.Lists.TryGetList("Tasks"); SPListItemCollection items=list.GetItems(query); foreach (SPListItem item in items){ Console.WriteLine(item.Title); } } } catch (Exception ex){ Console.WriteLine(ex.StackTrace); }
上述例子通过SPQuery的ViewFields属性指定了需要返回的列名,通过Query属性指定了要查询数据的条件,再调用SPList.GetItems(SPQuery query)的重载方法返回查询的数据。本例中需要注意的是,在最后调用item.Title输入列表项标题的时候一定要确保ViewFields里包含了Title,否则会报ArgumentException错误。
3.1.2 CAML语法介绍
在上面的例子中大家看到了CAML的用法,首先我们可以看看ViewFields里指定返回列的语法,只需要通过<FieldRef Name='ColumnName'/>进行指定即可。但需要注意的是,如果字段名中有空格等字符的时候需要编码后才能使用,例如字段“Test Space”需要指定为<FieldRef Name='Test_x0020_Space' />才能调用,否则通过item["Test Space"]或者item["Test_x0020_Space"]进行列表项属性获取时也会报ArgumentException错误,当然如果不在结果中使用该字段值则不会触发该运行时的错误。此外,也可以使用ID属性指定字段的GUID获取属性值。
需要使用的字段必须在ViewFields里进行指定,但是如果指定了SPQuery.Include MandatoryColumns属性为True,则即便没有在ViewFields里进行指定,查询也会返回设置为必填的字段。另外,如果需要限制返回的条数,可以通过SPQuery.RowLimit指定返回条数。
进行查询的CAML语法主要有Where、OrderBy和GroupBy三个元素,三个元素分别指定了查询的条件、排序条件以及分组条件。
Where指定查询条件,Where的语法比较简单,但具体到实际的使用也可能会写得非常复杂。下面是一个非常简单的例子:
<Where> <Eq> <FieldRef Name='LastName'> <Value Type='Text'>Wong</Value> </Eq> </Where>
首先第一个子节点Eq(Equal)操作符说明了要进行等于判断,Eq的第一个子节点说明了比较的字段是“LastName”,比较的值是“Wong”,翻译过来也即要返回某个列表里“LastName”字段等于“Wong”的列表项。通常使用到的操作符如表3-1所示。
表3-1 操作符一览
但涉及若干个条件的时候要使用<And>或者<Or>进行条件的组合,组合的方式是在<And>或者<Or>里分别包含两个条件。
<Where> <And> <Eq> <FieldRef Name='LastName' /> <Value Type='Text'>Wong</Value> </Eq> <Geq> <FieldRef Name='Age' /> <Value Type='Number'>21</Value> </Geq> </And> </Where>
以上CAML文指定返回“LastName”为“Wong”,并且“Age”为21岁的所有列表项。如果满足三个条件及三个以上的时候,记述方式会有点奇怪,如下:
<Where> <And> <And> <Eq> <FieldRef Name='LastName' /> <Value Type='Text'>Wang</Value> </Eq> <Geq> <FieldRef Name='Age' /> <Value Type='Number'>21</Value> </Geq> </And> <Lt> <FieldFef Name='Age' /> <Value Type='Number>60</Value> </Lt> </And> </Where>
以上CAML文指定返回“LastName”为“Wong”并且“Age”在21岁到60岁之间的所有列表项。可以简单地理解成<And>或者<Or>之间只允许有两个并级的子节点存在。
<GroupBy>和<OrderBy>的使用相对简单,此处不赘述,使用例子参照如下:
<GroupBy> <FieldRef Name='Title' /> </GroupBy> <OrderBy> <FieldRef Name='Age' /> </OrderBy>
CAML的使用上也有许多需要注意的地方,以下是一些经常会遇到的问题总结。
对文件夹的查询
如果在列表或者文档库等里有子文件夹的时候,子文件夹及孙子文件夹等的数据并不会被默认查询,需要通过指定SPQuery.ViewAttributes="Scope='Recursive'"强制查询所有子文件夹内容。
如果想针对某个特定的文件夹进行查询,可以通过SPQuery.Folder=list.RootFolder. SubFolders["SubFolderName"]进行查询。如果希望针对孙子文件夹查询则可以使用SPQuery.Folder=list.RootFolder.SubFolders["SubFolderName"].SubFolders["SubsubFolderNam e"]进行查询,如果结构更复杂,则依次类推。另外,结果过于复杂后如果不喜欢一直通过SubFolder取数据,也可以使用SPWeb.GetFolder("ServerRelativeURL")直接获取SPFolder文件夹对象,例如,上述的孙子文件夹位于Tasks列表的情况下:
query.Folder=list.ParentWeb.GetFolder("/Lists/Tasks/SubFolderName/SubsubFolderName");
对DateTime类型字段的查询
真实场景中经常会出现对DateTime字段类型的查询,通常使用例子如下:
<Where> <Eq> <FieldRef Name='DueDate'/> <Value Type='DateTime'>2011-04-24T00:00:00Z</Value> </Eq> </Where>
上面的例子查询Tasks列表内DueDate等于2011年4月24日零点的记录,而在实际查询中SharePoint默认将忽略时间部分的值,也即在上例中只要DueDate是2011年4月24日的记录都会被返回。如果需要使用时间部分,需要显示在FieldRef里指明IncludeTimeValue="True",具体如下:
<Where> <Eq> <FieldRef Name='DueDate' IncludeTimeValue='True'/> <Value Type='DateTime'>2011-04-24T00:00:00Z</Value> </Eq> </Where>
当然,上述日期是硬编码进去的,如果需要更加灵活的方式还可以使用Today函数Today函数返回当前的日期,不过奇怪的是微软一直没有提供Now函数来获取当前的时间。
<Where> <Eq> <FieldRef Name='DueDate'/> <Value Type='DateTime'><Today /></Value> </Eq> </Where>
如果在当前日期的基础上加减一些天数,可以通过Today节的Offset属性设置,正值代表往后加天数,负值代表往后减天数。
<Where> <Eq> <FieldRef Name='DueDate'/> <Value Type='DateTime'><Today OffsetDays=10 /></Value> </Eq> </Where>
对LookUp字段类型的查询
LookUp类型字段是在早期SharePoint里就已经存在的一种字段类型,简单地说,如果我们的员工列表里包含员工姓名、部门等信息,然后有一个维护员工薪资的列表,这个列表里的员工姓名可以设置成一个对员工姓名的引用,即我们可以在薪资列表里增加一个LookUp类型的字段,将其设置为对员工列表的引用。对LookUp类型的字段也可以当做普通字段进行处理,例如这个例子中的员工姓名通常是单行文本,我们可以进行如下查询:
<Where> <Eq> <FieldRef Name='EmployeeName' /> <Value Type='Text'>XiaoFeng</Value> </Eq> </Where>
也可以指定Type为Lookup进行同样的查询:
<Where> <Eq> <FieldRef Name='EmployeeName' /> <Value Type='Lookup'>XiaoFeng</Value> </Eq> </Where>
进行LookUp字段查询的时候还可以指定FieldRef的LookUpId属性为True,譬如上例中XiaoFeng对应的员工列表的ID是1,如果XiaoFeng改名为DuanYu,我们仍然可以通过查询ID查询到正确的信息:
<Where> <Eq> <FieldRef Name='EmployeeName' LookupId='True' /> <Value Type='Lookup'>1</Value> </Eq> </Where>
使用CAML进行列表项查询还有两个点:一个是查询日历列表里的重复性事件等,另一个是通过Web服务访问列表数据。前者使用频率不是很高,后者和调用SPQuery访问比较类似,这里不做赘述,感兴趣的朋友可以访问笔者的博客:http://www.cnblogs. com/johnsonwong/ archive/2011/02/27/1966008.html。
3.1.3 关联列表查询
在介绍双列表查询之前先要介绍一下SharePoint Foundation对Lookup字段的改善,其实从严格意义来说相关的改善点在SharePoint2007里面已经有了,通过开发自定义字段类型也可以做到类似的功能,不过SharePoint Foundation默认提供了这个功能倒是省了很多事情。让我们先来看看这些增强点到底在哪里。
1.SharePoint2010新特性:LookUp增强
如图3-1所示,设想HR部门管理员工数据的场景,经过简化后,先建好一个员工数据表,里面有三个字段:员工ID、员工全名和员工部门。
图3-1 员工列表
建一个薪资表,这个表只包含两个字段:薪资金额和员工ID。薪资金额使用货币类型字段,为了确保数据的完整性,员工ID字段使用对员工表的引用,如图3-2所示当选择好Lookup类型后,字段配置画面会允许我们去选择参考的列表类型。而在SharePoint Foundation这个新版本的增强功能是在我们选择一个EmployeeID作为在薪资列表里的使用字段之外,还可以额外地选择其他字段显示在新的薪资列表里,这种字段称之为“Projected Fields”。
图3-2 Projected Fields
上图中选中员工全名(EmployeeFullName)、员工部门(Employee Department)后单击保存。接下来往薪资列表里插入数据,会发现尽管只在新建项目画面选择了员工ID信息00001、00002、00003,但在如图3-3所示的查看画面却能看到包含员工全名以及员工部门的相关信息。
图3-3 Projected Fields效果
2.查询关联列表
SPQuery也支持对LookUp字段这种额外Projected Fields信息的查询,通过SPQuery.Joins指定要关联的表的相关信息,通过SPQuery.ProjectedFields指定在参照的表里需要被使用到的字段就可以参照表的额外字段信息查询出来了。下面的代码中我们将针对上述薪资表进行查询,并连接员工信息表查询员工全名的信息。
SPQuery query=new SPQuery(); query.Query= @"<Where> <Gt> <FieldRef Name='Salary'/> <Value Type='Text'>60000</Value> </Gt> </Where>"; query.Joins= @"<Join Type='INNER' ListAlias='EmployeeList'> <Eq> <FieldRef Name='Employee' RefType='Id'/> <FieldRef List='EmployeeList' Name='Id' /> </Eq> </Join>"; query.ProjectedFields= @"<Field Name='FullName' Type='Lookup' List='EmployeeList' ShowField='EmployeeFullName' />"; query.ViewFields=@"<FieldRef Name='Employee' /><FieldRef Name='Salary'/><FieldRef Name= 'FullName'/>"; try{ using (SPSite site=new SPSite("http://WIN-I53T3JI4M13")){ SPWeb web=site.RootWeb; SPList list=web.Lists.TryGetList("Salary"); SPListItemCollection items=list.GetItems(query); foreach (SPListItem item in items){ Console.WriteLine("---------------------------------"); Console.WriteLine(item["Employee"]+" Full Name: "+item["FullName"]); } } } catch (Exception ex){ Console.WriteLine(ex.StackTrace); }
上述代码中首先使用了一个简单的Where条件指定返回薪资大于60 000的记录。接着通过SPQuery.Joins指定了关联列表的信息。Join的属性说明如表3-2所示。
<Join Type="LEFT" | "INNER" ListAlias="Text"></Join>
表3-2 Join属性说明
上例的Join里<Eq>节中第一个FieldRef元素指定了在薪资列表里这个引用字段的名称,RefType属性的值永远都是“Id”,第二个FieldRef指定了关联列表的同名词,Name属性的值也永远都是“Id”。
SPQuery.ProjectedFields通过Field节指定需要被引用到的关联列表的字段,注意ShowField的值是关联员工列表的员工全名信息字段“EmployeeFullName”,而Name则指定了它的引用名称“FullName”,将此“FullName”包含在SPQuery.ViewFields里,我们在调用SPList.GetItems(SPQuery query)后就可以在返回的结果中使用该字段值了。最后Field节的Type属性值通常为“Lookup”。
通过关联列表查询不仅能够查询两个列表,当薪资列表使用到了多个Lookup字段的时候可以跨多个表进行连接,另外如果迭代更多的情况,比如薪资列表引用了员工列表的信息,而员工列表又引用到了部门信息列表的信息,也可以通过Join查询的方式进行多表联合查询。
3.1.4 多列表查询
尽管SPQuery能够针对单列表进行查询,也能够对有引用关系的多列表进行查询,但是如果要进行更大范围内数据查询,比如站点集内所有日历列表数据的查询时SPQuery就爱莫能助了,不过SharePoint Foundation提供了一个查询更大范围列表数据的对象Microsoft.SharePoint.SPSiteDataQuery。这个对象和SPQuery很相似,也有Query和ViewFields属性,此外还有Lists和Webs两个属性。Lists属性指定了查询应该包含的列表,Webs属性则指定了查询的范围是在网站集范围还是在网站范围。
SPSiteDataQuery query=new SPSiteDataQuery(); query.Query=@"<Where />"; query.Lists="<Lists ServerTemplate='106' />"; query.ViewFields=@"<FieldRef Name='Title'/><FieldRef Name='EventDate'/><FieldRef Name= 'EndDate' />"; query.Webs="<Webs Scope='Recursive' />"; try{ using (SPSite site=new SPSite("http://WIN-I53T3JI4M13")){ DataTable dt=site.RootWeb.GetSiteData(query); foreach (DataRow row in dt.Rows){ foreach (DataColumn column in dt.Columns){ Console.WriteLine("dt["+column.ColumnName+"] : "+row[column]); } } } } catch (Exception ex){ Console.WriteLine(ex.StackTrace); }
上面的例子通过Lists属性指定了106,也即查询网站集http://WIN-I53T3JI4M13根站点下所有站点内的日历列表的数据并输出。再详细地说一下SPSiteDataQuery.Web属性,这个属性有两个可选值:
SPSiteDataQuery.Webs="<Webs Scope='Recursive' />
SPSiteDataQuery.Webs="<Webs Scope='SiteCollection' />
“Recursive”:返回当前网站(请注意使用SPSiteDataQuery的是SPWeb对象)下所有子站点内符合条件的数据。
“SiteCollection”:返回当前网站所在的网站集内所有符合条件的数据。
SPSiteDataQuery.Lists指定了两种设置方式:
SPSiteDataQuery.Lists="<Lists ServerTemplate='TemplateID'>";
SPSiteDataQuery.Lists="<Lists BaseType='BaseTypeID'>";
设置为ServerTemplate,将限定查询范围为特定的服务器模板列表,设置为BaseType,将限定查询范围为特定的基类类型列表,表3-3列举了两者的选值范围。
表3-3 服务器模板说明
表3-4是基类类型列表的选值范围。
表3-4 列表基类类型范围
3.1.5 Throttling查询
Throttling是SharePoint Foundation引入的一个新功能“Resources Throttling”,它可以针对一些瓶颈的服务器资源,例如大数据查询以及Web请求设置限制以降低对服务器的性能负担。本节主要介绍的是和列表查询相关内容的细节。
1.SharePoint Foundation新特性:列表Throttling
列表Throttling是SharePoint Foundation引入的一个新特性,它的核心含义在于通过设置限定从列表或文档库查询返回的数据行数。当超过限制的时候用户试图查询数据,SharePiont会提示一个友好的信息告知查询的数据量超过了服务器的设置。
但需要注意的是,不仅仅是单纯的查询数据会触发此限制,一些其他的操作,比如从一个Throttling限制为5000但实际列表数据为10 000的列表里查询100条数据,但却针对一个没有被索引的字段做了排序这样的操作也会触发到超过查询的限制;再比如删除网站的时候,如果网站里有一个大数据列表超过了服务器设置的限制也会诱发此类问题。
设置列表Throttling可以到管理中心->Web应用程序管理画面,选中需要设置的Web应用程序,单击“普通设置”后选择“Resources Throttling”就可以看到弹出的画面,如图3-4所示。
图3-4 列表Throttling设置
其中“列表视图Throttling”指定了查询返回的最大行数,默认是5000条,最小可设置值是2000条。Object Model Override设置为指定有权限的用户可以超越“列表视图Throttling”的设置,“Daily Time Window for Large Queries”指定了每日大数据的可查询时间窗口,我们可以设置一个服务器负载比较小的时间段,允许这段时间范围内进行大数据量的查询。需要注意的是,如果我们的查询开始之后,即使窗口已经关闭,此查询仍然会继续运行直到结束。最后说一下“List View Looup Threshold”这个设置,它指定了一次查询能够包含的最大的Lookup、Person/Group,或者工作流状态字段的数目。
2.Throttling列表查询的三种用户角色
要真正理解 Throttling列表查询,需要明白它的设置根据具体的用户角色不同分别会起到不同的效果。以下三种角色分别会受到Throttling列表查询设置的不同影响,如表3-5所示。
表3-5 列表Throttling用户角色说明
对服务器管理员角色来说,一切设置并不都是“浮云”,这个角色虽然拥有很大的权限,但在一定条件下仍然会受到访问数据的限制。
对超级用户或者普通用户来说,如果根据不同的设置组合会有不同的影响,我们首先说明各种设置单独改变下的影响,然后在下一节通过不同的例子说明组合设置下的影响。
QueryThrottleMode设置
SPQuery和SPSiteDataQuery对象都有一个叫做QueryThrottleMode的属性,这个属性有三个选项,分别是Default、Override和Strict。
设置为Default的情况下,超级用户和普通用户的查询都会遵从管理中心的最大返回数据行数的限制(List View Threshold),服务器管理员不受限制。
设置为Override的情况下,超级用户的查询将不受管理中心最大返回数据行数的限制,但普通用户仍然受此限制,前提是管理中心的“Object Model Override”要设置为“Yes”。服务器管理员不受限制。
设置为Strict的情况下,不管是什么样的权限或者角色的用户都会受到管理中心最大返回数据行数的限制。
大数据查询时间窗口(Daily Time Window for Large Queries)设置
在这个时间段内开始的查询将不受QueryThrottleMode的限制。
EnableThrottling属性设置
如果将SPList的属性设置为False,对该列表的相关查询将不受管理中心最大返回数据行数的限制。只有具有服务器场管理员身份的人才能通过代码进行设置,或者使用PowerShell进行设置,以下是通过PowerShell进行设置的代码:
$site=Get-SPWeb -Identity "http://localhost" $list=$site.Lists["Tasks"] $list.EnableThrottling=$false
3.Throttling列表查询案例分析
下面我们通过一些组合设置巩固对Throttling的理解。
场景一:
数据量:5000条
EnableThrottling属性:True
默认视图:显示4000条数据
List View Threshold属性设置:3000条
Object Model Override:Yes
QueryThrottleMode:Override,查询所有数据
表3-6 场景一结果
场景二:
数据量:5000条
EnableThrottling属性:False
默认视图:显示4000条数据
List View Threshold属性设置:3000条
Object Model Override:No
QueryThrottleMode:Override,查询所有数据
表3-7 场景二结果
场景三:
数据量:5000条
默认视图:显示3000条数据
List View Threshold属性设置:3000条
Object Model Override:Yes
QueryThrottleMode:Override,查询返回所有数据
表3-8 场景三结果
本节介绍了使用CAML查询列表数据的一些方法,并围绕这个话题介绍了SharePoint Foundation在列表管理方面功能的增强和改善。但是实际使用过的朋友一定会注意到CAML的调试非常麻烦,如果语法出错只有在运行时才能发现,而且纠错也非常困难。下一节将要介绍的LINQ技术将会提高我们开发列表查询的效率。