Tag: SharePoint 2010

  • SharePoint List Item Loading Page — Query from Multiple Lists

    SharePoint List Item Loading Page — Query from Multiple Lists

    This blog post covers how to implement a simple, yet devilishly effective mutiple-list query redirect page using SPServices. If you have not yet explored how SPServices spservices_logocan expand your current SharePoint solutions, you should definitely check the project out on CodePlex!

    A client recently required a single landing page (a “loading” page, if you will) from which a list item will open based an a URL parameter, where the parameter matches a column value in the list item. The caveat though, is that the list item could exist across any of six individual (and very large) custom lists, which do not necessarily have the same structure. The lists are all closely related and represent the movement of work items through a tightly-controlled business process… so the item could change locations throughout the day.

    For the sake of clarity, let’s call the work items “documents” to represent the business use-case. Documents have a unique identifying number that generates when an initial request arrives in one department. This DocID then represents the easiest way for an end-user to check on the status of the the request, the resulting document, or both, at any point in the business process.

    List A List B
    Initial Requests Archived Requests
    DocID (Internal list item ID) DocID (Number column)
    Unique Unique
    List C List D List E List F
    Submitted Documents In Review Documents Approved Documents Obsolete Documents
    DocID (Number column) DocID (Number column) DocID (Number column) DocID (Number column)
    Unique Unique Unique Duplicates Possible

    This “landing” page needs to query these lists in a particular order, taking into account the current user’s permissions (skipping over lists to which he/she does not have read rights), and loading the document or request as soon as the query finds a match.

    So, how best to approach the issue of querying for the location of the document based on this DocID?

    Out-of-the-box, SharePoint offers the standard list view web part that can be connected to URL filter parameters, but I needed to display the list item’s view form directly (cutting out that extra click). Theoretically, you could connect a view form web part to the URL parameter, but in this case it would be very sloppy to repeat this six times on the same page to cover all the lists.

    Third-party, the challenge reduces to a trivial implementation using SPServices JQuery library for SharePoint 2010; fortunately; this was already in use elsewhere in the site collection.

    The .aspx loading page itself contains a call to the SPServices SPGetQueryString function, to parse the URL parameter named “doc“…

    var queryStringValues = $() .SPServices.SPGetQueryString();
    var strDocId = queryStringValues["DOC"];
    //double-check for lowercase parameter name:
    if(!strDocId) strDocId = queryStringValues["doc"]

    …the SPServices getListItems calls and logic to query the six lists…

    	var cQueryOptions = "<QueryOptions><ExpandUserField>FALSE</ExpandUserField></QueryOptions>" ;
    	var url;
    	var foundFlag = false;
    
    	function queryListA() {
    		//call GetListItems
    		//set url=<URL OF LIST A> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListB() {
    		//call GetListItems
    		//set url=<URL OF LIST B> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListC() {
    		//call GetListItems
    		//set url=<URL OF LIST C> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListD() {
    		//call GetListItems
    		//set url=<URL OF LIST D> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListE() {
    		//call GetListItems
    		//set url=<URL OF LIST E> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListF() {
    		//call GetListItems
    		//set url=<URL OF LIST F> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	$(document).ready(function() {
    		if(strDocId) {
    			if(strDocId=='0') {
    				alert("The document ID provided (\"0\") is not valid. Please try again."
    				foundFlag = true;
    				window.location = URL TO HOMEPAGE;
    			}
    
    			if(!foundFlag) queryListE();
    			if(!foundFlag) queryListD();
    			if(!foundFlag) queryListC();
    			if(!foundFlag) queryListF();
    			if(!foundFlag) queryListA();
    			if(!foundFlag) queryListB();
    		} else {
    			alert("The link is invalid or you have accessed this page in an unsupported manner. Please check the link and try your request again.");
    			window.location = URL TO HOMEPAGE;
    		}
    	});
    

    GEARS_AN

    …and a simple “loading” message that displays to the user until the query completes (making clever use of the native SharePoint loading graphic gears_an.gif… which everyday users of SharePoint will readily recognize as a loading signal). I also styled the container divs using CSS3PIE so I could be sure this loading page has a professional finish regardless of the user’s browser version.

     

    <div class="LoadingMsgMain">
    <div class="LoadingMsgTitle"> YOUR SITE TITLE HERE</div>
    <div style="text-align:center; font-size:large; font-face:Calibri; padding:20px;">Please wait the system prepares your document...
    <br /><br />
    <img src='/_layouts/images/gears_an.gif' style="border:none;" /></div>
    </div>

    The page then redirects to the display form of the matching list item, or displays an error message if no match found or an invalid URL parameter is provided.

    Full page source below.

    <html>
    <head>
    <title>YOUR PAGE TITLE HERE</title>
    
    <!-- Include the SPServices and Jquery libraries from a local site library (or just reference them from a CDN of your choice) -->
    <script language="javascript" type="text/javascript" src="/mySiteCodeLibrary/jquery-1.11.0.min.js"></script>
    <script language="javascript" type="text/javascript" src="/mySiteCodeLibrary/jquery.SPServices-2014.01.min.js"></script>
    
    <script type="text/javascript">
    	var queryStringValues = $() .SPServices.SPGetQueryString();
    	var strDocId = queryStringValues["DOC"];
    	//double-check for lowercase parameter name:
    	if(!strDocId) strDocId = queryStringValues["doc"]
    	var cQueryOptions = "<QueryOptions><ExpandUserField>FALSE</ExpandUserField></QueryOptions>" ;
    	var url;
    	var foundFlag = false;
    
    	function queryListA() {
    		//call GetListItems
    		//set url=<URL OF LIST A> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListB() {
    		//call GetListItems
    		//set url=<URL OF LIST B> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListC() {
    		//call GetListItems
    		//set url=<URL OF LIST C> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListD() {
    		//call GetListItems
    		//set url=<URL OF LIST D> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListE() {
    		//call GetListItems
    		//set url=<URL OF LIST E> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	function queryListF() {
    		//call GetListItems
    		//set url=<URL OF LIST F> + "/DispForm.aspx?ID=" + MATCHING ITEM ID
    		//inside completefunc, set foundFlag=true and window.location=url;
    	}
    
    	$(document).ready(function() {
    		if(strDocId) {
    			if(strDocId=='0') {
    				alert("The document ID provided (\"0\") is not valid. Please try again."
    				foundFlag = true;
    				window.location = URL TO HOMEPAGE;
    			}
    
    			if(!foundFlag) queryListE();
    			if(!foundFlag) queryListD();
    			if(!foundFlag) queryListC();
    			if(!foundFlag) queryListF();
    			if(!foundFlag) queryListA();
    			if(!foundFlag) queryListB();
    		} else {
    			alert("The link is invalid or you have accessed this page in an unsupported manner. Please check the link and try your request again.");
    			window.location = URL TO HOMEPAGE;
    		}
    	});
    </script>
    </head>
    
    <body>
    <div class="LoadingMsgMain">
    <div class="LoadingMsgTitle"> YOUR SITE TITLE HERE</div>
    <div style="text-align:center; font-size:large; font-face:Calibri; padding:20px;">Please wait the system prepares your document...
    <br /><br />
    <img src='/_layouts/images/gears_an.gif' style="border:none;" /></div>
    </div>
    </body>
    </html>
    

     

     

  • SharePoint Single-Value Lookup Column Index (Strange Behavior)

    Today’s post continues my previous discussion of column index issues, but this time we focus solely on the indexing of a single-value lookup column and some of the strange behavior that indexing can produce when the row contains a blank value.

    Keep in mind that lookup columns set to allow multiple selections are not supported by Microsoft in SharePoint 2010.

    Diagnosis

    I had a production list of “requests” with a single-value lookup column into another production list of department employees. The lookup column was thus used to assign a request to an owner. Now, you might be wondering why I would simply not just use a column of type person, and I would say to you that you’ve asked a perfectly valid question. The lookup table, if you will, allows me to track historical owners that have moved on from the department, without worrying about data loss as requests are updated. The lookup table also enables me to store additional information on the owners that can easily be retrieved from within workflows.

    So with this production list approaching the dreaded 5,000 item list view threshold, I decided it was time to properly address column indexing. One of my changes included indexing this single-value lookup “Owner” column. Now, in most cases this would be perfectly fine, but keep in mind (as I initially did not), that new, incoming requests do not have an owner until assigned. This means that the now indexed lookup column has (albeit very few) blank values.

    SharePoint will accept the column index and go about daily operations as if everything is fine, but you may notice something strange when using column filters in your list views. When you select (blanks) as the filter on this “Owner” column, you would normally expect to see all list items that do not have an owner assigned. But instead, the list view shows you nothing (as if to say there are no items with a NULL owner).

    Solution

    This behavior persists until you remove the column index. Instantly, the column filter (blanks) will work as you would expect. Add the column index back, and the issue is reproducible.

    Strange, but nonetheless manageable, when you remember that column indexing simply does not play well with blank values. Lesson learned!

  • Exception from HRESULT: 0x80131904 (Column Index Issue)

    A frequently-accessed production list suddenly started exhibiting strange behaviors as users (and automated workflows!) attempted to edit existing items. Users could add new items and delete existing items from the list without issue, using both datasheet views and the list forms. However, if users tried to update existing items (evenly a newly created item), SharePoint would exhibit the following errors:

    When editing in a datasheet view:

    "An unexpected error has occurred. Changes to your data cannot be saved. For this error, you can retry or discard your changes."
    “An unexpected error has occurred. Changes to your data cannot be saved. For this error, you can retry or discard your changes.”

    When editing in the standard (non-customized) list form OR customized InfoPath edit form:

    "The form cannot be submitted. Exception from HRESULT: 0x80131904 An entry has been added to the Windows event log of the server."
    “The form cannot be submitted. Exception from HRESULT: 0x80131904 An entry has been added to the Windows event log of the server.”

    This behavior did not replicate onto any other list in the site collection, but remained localized to this single, critical list (naturally).

    The Investigation

    Since the errors occurred in both datasheet views and the list edit forms, the root cause could not be limited to InfoPath forms services (since the list in question uses custom InfoPath display, edit, and new forms).

    Strangely, the error message in datasheet view does not provide a correlation ID. Fortunately, the more detailed error message in form mode does provide a correlation ID. Using those correlation IDs, our troubleshooting research uncovered several frontend server log entries that harken to SQL server exceptions. At this point, the server logging level was set to HIGH.

    High Batchmgr Method error. Errorcode: 0x564aa500. Error message: The operation failed because an unexpected error occurred. (Result Code: 0x80131904)

    Okay, we’ve found the matching error code, now let’s look a bit deeper…

    High SqlError: 'The variable name '@CmpIndexValue1' has already been declared. Variable names must be unique within a query batch or stored procedure.' Source: '.Net SqlClient Data Provider' Number: 134 State: 1 Class: 15 Procedure: '' LineNumber: 3 Server: 'XXXXXXXXXXX-REDACTED-XXXXXXXXXXXXX'&amp;lt;em&amp;gt;
    Critical Unknown SQL Exception 134 occurred. Additional error information from SQL Server is included below.The variable name '@CmpIndexValue1' has already been declared. Variable names must be unique within a query batch or stored procedure.
    System.Data.SqlClient.SqlException: The variable name '@CmpIndexValue1' has already been declared. Variable names must be unique within a query batch or stored procedure. at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean
    System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj) at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) at System.Data.SqlClient.SqlDataReader.ConsumeMetaData() at System.Data.SqlClient.SqlDataReader.get_MetaData() at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString) at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async) at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result) at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method) at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior) at Microsoft.SharePoint.Utilities.SqlSession.ExecuteReader(SqlCommand command, CommandBehavior behavior, SqlQueryData monitoringData, Boolean retryForDeadLock)

    Bingo! So in the heat of the moment, we surmised that this SQL exception might be caused by a database error, or worse, database corruption (a number of forum posts across the internet seem to suggest this idea). A colleague ran some queries on the SQL server to try to locate this “CmpIndexValue1” thinking that it might be part of stored procedure, but to no avail. It is here that we engaged Microsoft support to investigate further.

    After reproducing the issue with Microsoft support, we sent off a VERBOSE log dump for analysis. A short while later, the technician narrowed the possible culprits down to one (or more) of the list column indices, which are defined in the list settings.

    Root Cause

    System.Data.SqlClient.SqlException: The variable name '@CmpIndexValue1' has already been declared. Variable names must be unique within a query batch or stored procedure.

    List Indices

    We then proceeded to examine each of the six indexes I had configured when the list was originally created. From the six, only one was a compound index of a number column (primary) and date column (secondary). Based on the log entries that reference “@CmpIndexValue1” this compound list index must be the source of our troubles.

    First, we verified that this type of compound index is officially supported per the SharePoint documentation on TechNet. Second, we considered the data values stored in each column. The number column contained values ranging from 0 to 99,999 (integers only), and the date column contained only date values (no time per the column settings). The latter detail was less important, as Microsoft supports both date and time values when indexing the column. Values in either column were not unique, and duplicates were definitely present in the list.

    Final Resolution

    With the suspected index in our cross-hairs, I deleted the compound index and tried to edit some list items. Both avenues worked flawlessly, via datasheet and via the list forms. I then recreated the compound index, and the edit issue reappeared on cue. Since the compound index was created to meet a future business requirement, the support technician suggested I recreate the index, but switch the primary and secondary columns. Unfortunately, selecting the date column as primary then disables the selection of secondary column. As a workaround, I created two simple indexes, one for the number column and one for the date column. This did not cause the edit issue to return, so we considered the case resolved.

    At best, we concluded that the compound index is fully supported, but somewhere among the thousands of data values in these two columns, SharePoint did not tolerate one or more of them while building the compound index. At the end of the day, I was relieved to have a business-critical list fully functional again, and only mildly disappointed to have to adjust some list views in preparation for the inevitable march above and beyond 5,000 items.