看好你的代码之 - CAML In 查询语句

SharePoint 中 Lookup 字段主要用于列表之间关联关系的建立(单值查阅),可以理解为关系数据库中的主子表关系。例如有一个主列表LookupMaster,另外有一个子列表LookupSub......

看好你的代码之 - CAML In 查询语句

也许只有遇到了下面的问题,才能够让你静下心来把一下内容阅读完:

  • 程序自动拼装了 In 查询语句,查询偶尔会得到System.ArgumentException异常。
  • 针对 Lookup 字段使用 In 查询语句,同一个查询语句,同一个列表,有时候能查询出结果,有时候却查不出结果。

应用场景

SharePoint 中 Lookup 字段主要用于列表之间关联关系的建立(单值查阅),可以理解为关系数据库中的主子表关系。例如有一个主列表LookupMaster,另外有一个子列表LookupSub,在子列表上有一个 Lookup 字段关联到主列表。在子列表新建列表项时就可以关联到主列表上存在的某个列表项。在一些业务场景下,需要根据主列表下的一些列表项找到子列表上于此关联的所有列表项数据,熟悉 CAML 的同学首先想到可以用 In 查询。比如主列表是报销单列表,子列表是报销单明细列表,现在知道某个时间段的的1000张报销单,需要找出这1000个报销单关联的所有报销明细,这个时候CAML In 查询就可以派上用场了。

<Where>
    <In>
        <FieldRef Name='Field1' LookupId='True'></FieldRef>
        <Values>
            <Value Type='Lookup'>1</Value>
            <!-...->
            <Value Type='Lookup'>1000</Value>
        </Values>
    </In>
</Where>

然而,这一切并不像 SQL 中的 In 那么简单,使用不当就会出现上面列出的两条问题。下面通过测试过程来说明问题所在以及解决办法。

重现问题

测试基础环境说明:

  • 一个网站。
  • 一个主列表:LookupMaster。
  • 一个子列表:LookupSub,上面创建一个名字叫做 Field1的 Lookup 字段关联到主列表。
  • 全部过程使用 powershell 进行,可以复制代码在 SharePoint Powershell 命令行窗口运行。

测试准备

先创建测试需要的列表及字段

## 获取网站实例
$web = Get-SPWeb http://n-yl-spf2013:60 ##替换为自己的网站地址
## 创建主列表
$web.Lists.Add("LookupMaster", "", [Microsoft.SharePoint.SPListTemplateType]::GenericList)
## 获取主列表
$masterList = $web.GetList("Lists/LookupMaster")

## 创建子列表
$web.Lists.Add("LookupSub", "", [Microsoft.SharePoint.SPListTemplateType]::GenericList)
## 获取子列表
$subList = $web.GetList("Lists/LookupSub")
## 在子列表上创建与主列表关联的Lookup字段
$subList.Fields.AddLookup("Field1", $masterList.ID, $false)
$lookupField = [Microsoft.SharePoint.SPFieldLookup]$subList.Fields.GetField("Field1")
$lookupField.LookupField = "Title"
$LookupField.Update()
$subList.Update()

CAML In 500 Value items 限制测试

  • 先用下面的代码在主列表上创建500条数据
## 创建主列表数据500条
for($i = 1; $i -le 500; $i++){
    $item = $masterList.AddItem()
    $item["Title"] = $i
    $item.Update()
    $i
}
  • 运行包含500个 Items 的 CAML In 查询语句
## 主表数据CAML In 拼装查询
$queryStringTemp = "<Where><In><FieldRef Name='Title'></FieldRef><Values>{0}</Values></In></Where>"
$valueStringTemp = "<Value Type='Text'>{0}</Value>"
$valuesString = ""
$masterList.Items | foreach{
    $valuesString += ($valueStringTemp -f $_.ID)
}
$queryString = $queryStringTemp -f $valuesString
$query = New-Object Microsoft.SharePoint.SPQuery
$query.Query = $queryString
$items = $masterList.GetItems($query)
$items.Count

返回结果500

2015120801

  • 再增加第501条数据
## 主表上创建第501条数据
$item = $masterList.AddItem()
$item["Title"] = "501"
$item.Update()
  • 在运行包含501个 Items 的查询语句
$queryStringTemp = "<Where><In><FieldRef Name='Title'></FieldRef><Values>{0}</Values></In></Where>"
$valueStringTemp = "<Value Type='Text'>{0}</Value>"
$valuesString = ""
$masterList.Items | foreach{
    $valuesString += ($valueStringTemp -f $_.ID)
}
$queryString = $queryStringTemp -f $valuesString
$query = New-Object Microsoft.SharePoint.SPQuery
$query.Query = $queryString
$items = $masterList.GetItems($query)
$items.Count

结果查询结果数字返回空,如果是在服务器代码中模拟,你会得到一个System.ArgumentException异常

2015120802

这两个查询的差异仅仅是CAML 的<Values />节点里多了一个501行<Value>子节点,所以,使用 CAML In 查询,<Values />节点里最多包含500个<Value>子节点。这点从 SharePoint 本身 UI 上也能看出端倪,在列表视图页面上做字段的过滤,如果当前字段数据唯一值少于500个,显示字段多选过滤,如果大于等于500个,则只能显示字段唯一过滤选择:

  • 少于500个唯一值,显示多选过滤

2015120803

  • 大于等于500个唯一值,显示单选过滤

2015120804

CAML In 查询针对 Lookup 字段的问题

针对 Lookup 字段的 CAML In 查询问题主要体现在时而可以查到数据,时而又不行,这个问题隐藏得更深,不过还是可以简单重现。

  • 第一步 在子列表上创建499条数据
## 创建子列表数据499条
for($i = 1; $i -le 499; $i++){
    $item = $subList.AddItem()
    $item["Title"] = $i
    $item["Field1"] = "{0};#{0}" -f $i
    $item.Update()
    $item.Title
}

  • 第二步 运行下面的查询语句,可以得到一条记录
## 子表Lookup字段使用Lookup类型进行In查询
$queryString = "<Where><In><FieldRef Name='Field1' LookupId='True'></FieldRef><Values><Value Type='Lookup'>20</Value></Values></In></Where>"
$query = New-Object Microsoft.SharePoint.SPQuery
$query.Query = $queryString
$items = $subList.GetItems($query)
$items.Count
  • 第三步 在增加一条数据,第500条子表数据
$item = $subList.AddItem()
$item["Title"] = "500"
$item["Field1"] = "{0};#{0}" -f 500
$item.Update()

此时运行第二步的查询语句,依然可以得到数字1的返回结果。

  • 第四步 在子表 Lookup字段Field1上设置索引
## 子表Field1字段创建索引
$lookupField.Indexed = $true
$lookupField.Update()
$subList.Update()

这个时候再次运行第二步骤的查询语句,发现返回数字为0。

2015120806

  • 第五步 在第四步基础上,变化查询值类型

把查询节点Value的类型从Lookup换为Integer再查询:

## 子表Lookup字段使用Integer类型进行In查询
$queryString = "<Where><In><FieldRef Name='Field1' LookupId='True'></FieldRef><Values><Value Type='Integer'>20</Value></Values></In></Where>"
$query = New-Object Microsoft.SharePoint.SPQuery
$query.Query = $queryString
$items = $subList.GetItems($query)
$items.Count

返回查询结果为1,正常。

问题就出在查询类型和字段索引上面,如果Lookup 字段创建了索引,且唯一值大于等于500,且查询语句里Value的类型使用Lookup,则查询返回空;在任何情况下,查询语句里Value的类型使用Integer,查询返回正确结果。

结论

  • CAML In 查询语句里的Value节点不能多余500条。
  • 无论 Lookup 字段是否有索引,查询语句里的Value的类型使用Integer