Запросить большой внешний список с помощью CAML

У меня есть внешний список SharePoint, который указывает на таблицу SQL из 100 000 записей. Мне нужно установить фильтр для операции со списком чтения, иначе список не будет работать. Время ожидания истекает, поскольку он пытается вернуть полный список. Поэтому я добавил к операции фильтр Limit размером 200.

Проблема, связанная с этим, заключается в том, что когда я запрашиваю внешний список с помощью CAML, он ищет только 200 возвращенных записей, а не полный список.

Я хотел бы, чтобы он выполнял поиск по всему списку, но возвращал не более 200 совпадающих записей.

Как мне лучше всего этого добиться?


person Mort    schedule 25.08.2011    source источник
comment
У вас есть BCS как коннектор SQL или коннектор Assembly?   -  person Maks Matsveyeu    schedule 03.04.2012
comment
@MaksMatsveyeu Меня интересует этот вопрос в общем смысле, т.е. он должен быть независимым от коннекторов.   -  person Rex M    schedule 04.04.2012


Ответы (6)


Возможно, этот ответ на SharePoint-Exchange может вам помочь. https://sharepoint.stackexchange.com/questions/31566/caml-and-external-list-pass-parameter-to-readlist-finder-method

Я считаю, что вам, вероятно, потребуется изменить метод списка чтения, чтобы он читал всю таблицу SQL, но с параметром Where, заданным параметром фильтра в методе списка чтения. Что-то вроде

Псевдокод:

public static IEnumerable<YourEntity> ReadList(string param)
{
    if(string.IsNullOrEmpty(param) == true)
    {
        //Your original code thata only fetches the first 200 items
    }
    else
    {
        //Read your SQL-table with a Where ParamName = 'param' - clause
    }
}

Удачи

person Rikard Uppström    schedule 03.04.2012
comment
Это разумное решение, но не оптимальное. Потребитель по-прежнему должен хорошо разбираться в реализации внешней системы, чтобы понимать, как ее запрашивать. В идеале внешний список можно было бы запросить просто как список. - person Rex M; 04.04.2012
comment
Без проблем. Да, действительно. Ты о нас знаешь? Вы живете в Швеции? - person Rikard Uppström; 10.04.2012
comment
Из вашего профиля я вижу, что вы работаете на Volvo, может быть, тогда наши пути пересекутся ... :) Я понимаю, что Volvo в Гетеборге сильно инвестирует в SharePoint. - person Rikard Uppström; 10.04.2012
comment
Я только что вернулся в США из GOT в субботу :) Я слышал имя Avega от некоторых коллег. - person Rex M; 11.04.2012
comment
Спасибо! Я немного не был в stackoverflow и просто увидел все хорошие предложения, которые помогут ответить на мой вопрос, включая ваш. - person Mort; 16.04.2012

Основываясь на структуре вашего запроса и представленной здесь информации, в отчетах указано _ 1_ реализует желаемую функциональность:

Элемент RowLimit устанавливает ограничение на количество строк для представления.

Синтаксис

Атрибуты

  • Paged: необязательное логическое значение. ИСТИНА, если список поддерживает отображение большего количества элементов постранично. Если FALSE или не указано, то ограничение на количество строк является абсолютным и нет ссылки для просмотра других элементов.

Предостережения: обратите внимание на примечания в документации .

Вероятно, вы уже проверяли это для своих целей (цитируя ваш вопрос: «Итак, я добавил к операции фильтр ограничения размера 200».). Итак, перейдем к следующему вопросу:

Проблема, связанная с этим, заключается в том, что когда я запрашиваю внешний список с помощью CAML, он ищет только 200 возвращенных записей, а не полный список.

Это кажется странным. Если вы действительно используете <RowLimit> и документация верна:

Элемент RowLimit устанавливает ограничение строки для представления.

И:

Тег <RowLimit> находится в определении схемы представления (прямой дочерний элемент) и поэтому не может быть вложен в тег <Query>.

Тогда должно быть верно, что ваш вложенный запрос выполняется перед вашим компонентом View, чтобы удовлетворить гарантии вашего оператора limit. Как следствие этого, это должно позволить вам выполнять разбиение на страницы результатов по остальной части набора, определенного вашим запросом.

Основываясь на этих принципах, мы могли бы построить запрос с разбивкой на страницы следующим образом:

<View>
    <RowLimit Paged='True'>200</RowLimit>
    <Method Name='ReadList'/>
    <Query>
        <Where>
            <Contains>
                    <FieldRef Name='Name'/>
                    <Value Type='Text'>{0}</Value>
            </Contains>
        </Where>
    </Query>
    <ViewFields>
    <FieldRef Name='Name'/>
    <FieldRef Name='Id'/>
    <FieldRef Name='BdcIdentity'/>                        
    </ViewFields>
</View>

Отмечая, как упоминалось в документации, мы должны реализовать <PagedRowset>. Если это нежелательно, мы устанавливаем Paged='FALSE' выше.

Я, вероятно, здесь совершенно не в своей тарелке, потому что это похоже на то, что вы уже пробовали. Но в интересах исчерпывающего планирования пространства не помешает предложить это.

person MrGomez    schedule 04.04.2012
comment
Для внешнего списка ясно, что в обычных условиях реализации запрос не передается в соединитель для перевода. Таким образом, поведение выглядит так, что соединителю предлагается предоставить как можно больше элементов, а затем к результату применяются условия запроса фильтра и ограничения строки. - person Rex M; 04.04.2012
comment
@RexM Согласен. Переход к sharepoint.stackexchange.com, этому answer обсуждает некоторые особенности _1 _ / _ 2_ порядка приоритета в SOAP API, которые также имеют отношение к CAML. Конечно, лучший способ справиться с этим - указать запрос, который будет выполняться внутри Sharepoint вместо веб-компонента, но я предполагаю, что возможность сделать это ограничена по административным причинам. Я не понимаю, в чем проблема, без дополнительной информации. - person MrGomez; 05.04.2012

К сожалению, это известная проблема с запросом внешнего списка. Однако в веб-частях OOB подкачка поддерживается с помощью XSLT. RowLimit работает только в XSLT, но не в запросе CAML. Во внешнем списке нет подкачки на стороне сервера, скорее подкачка выполняется на стороне клиента, что означает, что SharePoint извлекает все данные, а затем устанавливает ограничение фильтра в представлении.

person ShurikEv    schedule 06.04.2012

Это один из сценариев, в которых сама по себе BCS без кода не помогает. Либо реализуйте это как хранимую процедуру на сервере базы данных, либо с помощью настраиваемого коннектора BDC, встроенного в Visual Studio.

person Tobias    schedule 08.04.2012
comment
Он также не работает с настраиваемым соединителем BCS. Отсутствие кода тут ни при чем. По-прежнему нет возможности отложить выполнение запроса на соединитель. Я надеюсь научиться нестандартному мышлению в поисках хороших альтернатив. - person Rex M; 08.04.2012

Если вы не можете включить всю таблицу SQL в свой внешний список, тогда вы не сможете запрашивать этот набор данных, как список SharePoint.

Тем не менее, я могу предложить решение, которое мы использовали для сценариев, практически идентичных этому, и которое сработало для нас очень хорошо. В нашем сценарии мы запрашиваем базу данных Oracle, и для возврата больших наборов данных требуется вечность.

Подход, который мы выбрали, заключался в использовании шаблона Factory, чтобы определить, какими средствами следует запрашивать источник данных (список SharePoint, внешнюю базу данных и т. Д.).

Приведенные ниже примеры несколько банальны, но они хорошо иллюстрируют концепцию.

Итак, начнем с интерфейса, который определяет, как будет запрашиваться набор данных и какие поля будут возвращены:

public interface IQueryData
{
    string ListToQuery { get; set; }
    List<MyResultObject> ExecuteQuery();
}

У вас был бы настраиваемый объект, представляющий одну запись, возвращаемую запросом.

public class MyResultObject
{
    public string FileRef { get; }
    public string Title { get; set; }
    // any other fields you'd like to see potentially returned...
}

Тогда у вас будет поставщик данных, который реализует этот интерфейс для источника данных SQL.

public class SqlDataProvider : IQueryData
{
    public string ListToQuery { get { return "BigSqlTable"; } }
    public List<MyResultObject> ExecuteQuery()
    {
        // query your external data source here...
        // populate a list of MyResultObject's from the result set and return it to the consumer
    }
}

У вас также будет поставщик данных, который реализует интерфейс для источника данных SharePoint.

public class SharePointDataProvider : IQueryData
{
    public string ListToQuery { get { return "MySharePointList"; } }
    public List<MyResultObject> ExecuteQuery()
    {
        // query your SharePoint list here, using CAML, SharePoint object model, etc...
        // populate a list of MyResultObject's from the result set and return it to the consumer
    }
}

С помощью этой реализации вы инкапсулировали логику и детали запроса в соответствующих поставщиках данных.

Теперь у вас будет Factory, который создает соответствующий поставщик данных (на основе указанного параметра ListToQuery):

public static class QueryDataProviderFactory
{
    public static IQueryData Build(string listToQuery)
    {
        switch(listToQuery)
        {
            case "BigSqlTable": return new SqlDataProvider(); break;
            case "MySharePointList": return new SharePointDataProvider(); break;
            // you can have many other implementations here that query your data sources in different manners
        }
    }
}

Наконец, вы должны использовать свою фабрику, чтобы инициировать запрос, передавая имя источника данных, который вы хотите запросить:

public List<MyResultObject> RunQuery()
{
    return QueryDataProviderFactory.Build("BigSqlTable").ExecuteQuery();
}

Этот шаблон сохраняет вашу внешнюю реализацию инкапсулированной в ее собственный поставщик данных и абстрагирует детали запроса от потребителя. Все, что нужно сделать потребителю, - это указать имя списка, который он хочет запросить, и Factory решает, какую реализацию инициировать.

Вы даже можете заставить свой интерфейс IQueryData реализовывать универсальные шаблоны для дальнейшей расширяемости:

public interface IQueryData<T>
{
    string ListToQuery { get; set; }
    List<T> ExecuteQuery();
}

Это откроет дверь для потребителя, чтобы также указать тип объекта, который они ожидают вернуть.

В нашем интерфейсе данных запроса на самом деле гораздо больше членов, которые добавляют еще больше точек расширяемости нашим поставщикам запросов, но я подумал, что этот пример иллюстрирует эту точку в краткой и простой для понимания манере.

Просто хотел предложить это предложение, поскольку похоже, что это практически тот же сценарий, с которым мы столкнулись год или около того назад, и эта стратегия очень хорошо работает для нас.

person KodeKreachor    schedule 08.04.2012
comment
Я рада, что это хорошо сработало для вас. В устаревшей системе (SP 2007) у нас также есть собственный поставщик LINQ, который скрывает источник данных - это SharePoint? Это что-то еще? Потребителю не нужно знать. Но я назначил награду за этот вопрос, потому что в моем текущем сценарии, который предъявляет радикально другие требования, я бы действительно хотел, чтобы интерфейс запроса был самим SharePoint, а не каким-то настраиваемым кодом. - person Rex M; 09.04.2012

Используйте свойство ListItemCollectionPosition из SPQuery и SPListItemCollection например

using (var web = site.OpenWeb("bla-bla"))
{
  var list = web.Lists["your_list"];
  var query = new SPQuery();
  query.Query = "your query";
  do
  {
    var items = list.GetItems(query);
    foreach(SPListItem item in items)
    {
      //your code
    }
    query.ListItemCollectionPosition = items.ListItemCollectionPosition;
  } while(query.ListItemCollectionPosition != null);
}
person MishaU    schedule 25.08.2011
comment
Это не работает. После первого поиска (когда он выполняет поиск только первых 200 документов, как указано ограничивающим фильтром) ListItemCollectionPosition имеет значение null, поэтому он не выполняет поиск следующих 200 записей ... - person Mort; 20.09.2011
comment
Попробуйте добавить query.ViewAttributes = Scope = 'RecursiveAll'; и query.Folder = list.RootFolder; - person MishaU; 09.10.2011