Query large external list with CAML
Asked Answered
H

6

7

I have a SharePoint external list that points to a 100,000 record SQL table. I have to set a filter on the Read List Operation otherwise the list doesnt work. It will timeout as it tries to return the full list. So I have added a Limit filter of size 200 onto the operation.

THe problem this causes is that when I query the external list using CAML it only searches the 200 entries returned, not the full list.

I would like it to search the entire list, but return only a maximum of 200 matching entries.

How can I best achieve this?

Helenahelene answered 25/8, 2011 at 4:37 Comment(2)
Do you have you BCS as the SQL connector or Assembly connector?Quintie
@MaksMatsveyeu I am interested in this question in the general sense, i.e. it should be connector-agnostic.Picket
P
2

Maybe this answer att SharePoint-Exchange can help you. https://sharepoint.stackexchange.com/questions/31566/caml-and-external-list-pass-parameter-to-readlist-finder-method

My idead is that you will probably need to modify your readlist-method to make sure that it reads the entire SQL-table but with the Where-parameter specified by the filter parameter in the readlist-method. Something like

Pseudo code:

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
    }
}

Good luck

Pelpel answered 3/4, 2012 at 7:11 Comment(5)
This is a reasonable solution, but not an optimal one. The consumer still needs to have decent knowledge of the external system's implementation to understand how to query it. Ideally, an external list could be queried simply as a list.Picket
No problem. Yes, I do in fact. Do you know of us? Do you live in Sweden?Edlyn
I see from your profile you work for Volvo, maybe our paths will cross then.. :) I understand that Volvo in Gothenburg are heavily invested in SharePoint.Edlyn
I just got back to the US from GOT on Saturday :) I have heard the name Avega from some colleagues.Picket
Thanks! I haven't been on stackoverflow for a bit and just saw all the good suggestions to help answer my question, including yours.Helenahelene
B
2

Based upon the structure of your query and the information presented here, reports indicate <RowLimit> implements the functionality you desire:

The RowLimit element sets the row limit for a view.

Syntax

Attributes

  • Paged: Optional Boolean. TRUE if the list supports displaying more items page by page. If FALSE or unspecified, then the row limit is absolute and there is no link to see more items.

Caveats: note the remarks in the documentation.

You've probably already inspected this for your purposes (citing your question: "So I have added a Limit filter of size 200 onto the operation."). So, on to the next issue:

The problem this causes is that when I query the external list using CAML it only searches the 200 entries returned, not the full list.

This seems strange. If you're indeed using <RowLimit> and the documentation is correct:

The RowLimit element sets the row limit for a view.

And:

The <RowLimit> tag is in the schema definition of a view (direct child of ) and therefore cannot be nested inside a <Query> tag.

Then it should hold true that your nested query executes before your View component to satisfy the guarantee of your limit statement. As a corollary to this, this should allow you to perform results paging across the rest of the set defined by your query.

Building on these principles, we might construct a paged query like this:

<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>

Noting, as mentioned in the documentation, that we must implement <PagedRowset>. If this isn't desired, we set Paged='FALSE' above.

I'm probably entirely off base here, because this seems like exactly what you've tried already. But, in the interests of exhaustively mapping out the space, it can't hurt to suggest it.

Bukovina answered 4/4, 2012 at 9:35 Comment(2)
For an external list, it is clear that under the "normal" implementation conditions, the query is not passed through to the connector for translation. So the behavior seems to be that the connector is asked to provide as many items as it can, and then the filter query and rowlimit conditions are applied to the result.Picket
@RexM Agreed. Pathing over to sharepoint.stackexchange.com, this answer discusses some idiosyncrasies of <ViewFields>/<RowLimit> precedence order in the SOAP API that appear pertinent to CAML as well. Of course, the best way to handle this would be to specify a query to be executed internally by Sharepoint instead of the web component, but I'm assuming the ability to do so is restricted for administrative reasons. I'm unsure what's causing the issue without more information.Bukovina
A
0

Unfortunately, this is a known issue with querying external list. However, in OOB web parts, paging is supported by means of XSLT. The RowLimit works only in XSLT and not in CAML query. In External List, there is no server side paging, rather the paging is client side meaning that SharePoint pulls all the data and then sets a Filter limit in the view.

Allyce answered 6/4, 2012 at 9:33 Comment(0)
C
0

This is one of the scenarios where no-code BCS on it's own doesn't really cut it. Either implement this as a stored procedure on the database server, or using a custom BDC connector built in Visual Studio.

Cleancut answered 8/4, 2012 at 14:13 Comment(1)
It doesn't work with a custom BCS connector, either. "No-code" has nothing to do with it. There's still no way to defer query execution to the connector. I am hoping to get some outside-the-box thinking for good alternatives.Picket
B
0

If you can't pull in the entire SQL table to your external list, then there's no way you're going to be able query that data set as you could a SharePoint List.

However, I can offer a solution that we've used for scenarios virtually identical to this that has worked extremely well for us. In our scenario we're querying an Oracle database which takes forever to return large data sets.

The approach we took was to use the Factory pattern to determine by what means the data source (SharePoint List, external database, etc) should be queried.

The examples below are a tad trite, but they illustrate the concept well.

So, start out with an interfaced which defines how the data set will be queried and what fields will be returned:

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

You'd have a custom object that represents a single record returned by the query

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

Then you'd have a data provider which implements this interface for the SQL data source

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
    }
}

You'd also have a data provider which implements the interface for a SharePoint data source

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
    }
}

With this implementation you've encapsulated the logic and the details of the query in your respective data providers.

Now you'd have a Factory that builds the appropriate data provider (based on the specified ListToQuery parameter):

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
        }
    }
}

Finally, you'd use your factory to initiate your query, passing in the name of the data source you want to query:

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

This pattern keeps your external implementation encapsulated into its own data provider and abstracts away the details of the query from the consumer. All the consumer needs to do is specify the name of the list they want to query and the Factory decides which implementation to initiate.

You can even make your IQueryData interface implement generics for further extensibility:

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

This would open the door for the consumer to also specify the type of object they'd expect to be returned.

Our query data interface actually has many more members that add even more extensibility points to our query providers, but I thought this example illustrates the point in a concise and easy-to-grasp manner.

Just wanted to offer this suggestion as it seems like virtually the same scenario we came across a year or so ago and this strategy has been working very well for us.

Billington answered 8/4, 2012 at 15:22 Comment(1)
I'm glad that works well for you. In a legacy (SP 2007) system, we also have a home-grown LINQ provider that hides the data source - is it SharePoint? Is it something else? The consumer doesn't need to know. But I put a bounty on this question because in my current scenario, which has radically different demands, I'd really like the query interface to be SharePoint itself, not some custom code.Picket
U
-1

Use property ListItemCollectionPosition from SPQuery and SPListItemCollection e.g.

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);
}
Uneven answered 25/8, 2011 at 13:34 Comment(2)
This doesn't work. After the first search (where it only searches the first 200 documents as specified by limit filter) the ListItemCollectionPosition is null, so it doesn't search the next 200 records...Helenahelene
Try add query.ViewAttributes = "Scope='RecursiveAll'"; and query.Folder = list.RootFolder;Uneven

© 2022 - 2024 — McMap. All rights reserved.