I came across this while working with a customer and quickly found that the error triggers a known issue in my Bulk Edit app as well which was causing some unexpected errors using the app for some people, however this is a broader issue as it causes basic OData feeds to fail completely with the following message: Error message accessing /_api/ProjectData/Projects
<?xml version="1.0" encoding="UTF-8"?>
<m:message xml:lang="en-US">An error occurred while processing this request.</m:message>
The problem can happen both on-prem or online but if you have access to the ULS logs you will see the following log entries (showing only the interesting bits):
Project Server Database agxfb Exception [bucketHash:7C60C52B] SqlException occurred in DAL ([email protected]): Class 16, state 1, line 1, number 8156, procedure , error code -2146232060, exception: System.Data.SqlClient.SqlException (0x80131904): The column ‘Campaignstatus’ was specified multiple times for ‘OdataSelect’. Project Server OData abljj High [Forced due to logging gap, Original Level: Verbose] PWA:http://nb/PWA, ServiceApp:Project Server Service Application, User:i:0#.w|blah\ml, PSI: Exception encountered when trying to instantiante the OdataResultItemCollection
As error logs go that is a pretty helpful one!
Cause – Duplicate Custom Fields
The issue is caused by the Enterprise Custom Field configuration of the Project Server, specifically to looks like there are some duplicate named fields (Campaignstatus in my error above), now of course that is of course not possible! However when you look at the OData feed you can see that a few things happen:
- Spaces are removed from field names; so e.g. Campaign Status becomes CampaignStatus, etc.
- Duplicate named fields (after space removal) are numbered, so for example you may see CampaignStatus1.
However there is a bug, OData is case-sensitive while SQL is not. So in my example above it seems that there are two fields the same name but with different cases, specifically I have the following two fields configured: Campaign Status and Campaignstatus. In my case if the second field was named CampaignStatus then this issue would not occur as it would have a number appended in the feed! (While this might sound a little esoteric actually I’ve already seen this a couple of times in German PWA instances where words are typically conjugated, etc)
- Rename the aforementioned field to something completely different (e.g.; Campaignstatus2), or
- Recreate the field with different Case, e.g. in my example if I recreated the field Campaignstatus as CampaignStatus.
Note; renaming a field to a different case does not work(!), you need to recreate the field in order to regenerate the correct ‘cased’ Odata name.
Hopefully this bug will be fixed in a future service pack, but for now in Bulk Edit at least I have implemented a workaround anyway.
I like surprises! So today I was happy to find a brand new surprise shiny feature in ProjectOnline!
Behold the New Project Wizard:
And when you click Finish:
Neat, although seems to be currently un-configurable apart from the standard ‘New Project’ PDP changes that can be made, however I can see more ‘tabs’ being added up the top (where currently the numbers 1, 2 are) assumedly perhaps linked to other PDP’s?
More hints of what to expect in PS2015 I guess.
I’ve been looking forward to using the new SSIS OData Connector for SQL 2012 since first hearing about it at Project Conf last month, Paul Mather wrote up a great step-by-step guide on getting it all up and running here so have a look at that if you haven’t yet.
However the devil is always in the details! Creating a simple report combining Project data and SharePoint site data is not as straight-forward as you might hope.
Reporting custom project site list data from ProjectOnline
Possibly the most common customer request I get when it comes to reporting is the need to report against non-standard Project site list data, something that was simple using SSRS 2008 R2 but made impossible in ProjectOnline, until now that is.
Following on from Paul’s post, in order to do this you’ll need an SSIS package that does the following:
- Retrieve Project Data from the ProjectOnline OData endpoint: /_api/ProjectData
- Get a list of Project Site URLs from the data and save it in a variable
- Loop through each Project Site and retrieve data from the SharePoint endpoint: /[ProjectURL]/_vti_bin/listdata.svc after dynamically updating the [ProjectURL]
Also you’ll notice there in step 3 I use the listdata.svc endpoint rather than the REST endpoint (e.g. /_api/Web/Lists/getbytitle(‘Risks’)/Items), if you followed Paul’s steps above and tried to retrieve the site list data then you probably are seeing the following error when building the connection:
Test connection failed while parsing the XML document because it is not a valid OData service document.
If you’re seeing that message it is because the SSIS connector is expecting a ‘service document’ and not the actual OData feed. Not sure why the SharePoint REST endpoints don’t also have a service document at the root but we can work around it by using the listdata.svc.
Finally once we have the list of all of the Project site URLs then we need to update the Data Connection URL before we retrieve the site list data. I’ll cover all of these steps below.
Requirements for building the SSIS package
Paul Mather’s blog covers the basic setup, so I won’t cover that in detail but in summary to create / use the solution below you will need:
- SQL Server Integration Services 2012
- SQL Data Tools 2012 (the BIDS replacement on the SQL DVD)
- Microsoft® OData Source for Microsoft SQL Server® 2012
- SharePoint Server 2013 Client Components SDK
- An empty SQL database to write to, I’m going to use one called ProjectOnline_OData
You can run Data Tools on your client but the rest must be installed on the SSIS server.
Creating a SSIS package to retrieve site custom list for all projects
Create a new SSIS package and to start by adding the following Connection Managers:
- OLE DB Connection which points to your SQL database: e.g. ProjectOnline_OData
- An OData Connection pointing to the ProjectData OData service: e.g.
- An OData Connection pointing to the SharePoint ListData.svc OData service of an existing project site: e.g. Test%20Project/_vti_bin/listdata.svc
Make sure that each connection test’s successfully, and give them descriptive names which will be used later. I’m using OData_ProjectData and OData_SharePointListData for the last two and note that both names will be referenced below.
A note on security
The account used to authenticate with our OData connections must be a ProjectOnline user, and specifically it must have access to all projects and project sites. In order to do this easily I have added the users ([email protected]) to the following:
- Project Web App Administrators
- Site Collection Administrators
Adjust as you see fit.
Retrieve project data from OData
Now that we have our data connections we need to first get our Project data (and optionally any other data such as tasks), then prepare to get our project site data.
- As per Paul’s blog create a Data Flow task with an OData Source (OData_ProjectData) which points to the Projects collection.
- Ensure that you select only your required Columns, however you must include the following as a minimum: ProjectId & ProjectWorkspaceInternalUrl.
Projects collection OData source with preview
- Use the Destination Assistant to create the destination database
- Create a new table to store the data in (I’m using the name Projects) and configure your mappings
- Optionally you could add other Data Flow tasks in there to get Tasks or other data into additional database tables, but only using the OData_ProjectData connection at this point.
So now you should have a solution that runs and looks something like this when debugging:
Also if you started with Paul’s blog, then this is where those steps end.
Prepare to loop through all project sites
In order to get the site list data for each project in PWA we need to first save a temporary variable with a list of all ProjectWorkspaceInternalUrl‘s.
- In your solution on the Control Flow tab add another Data Flow Task. Link the green arrow from the previous task to this one (so runs 2nd)
- Add an OLE DB Source to the Data Flow using the Source Assistant
- This source will be configured to get project url’s saved into our database in the last step using the following SQL command:
SELECT ProjectId, ProjectWorkspaceInternalUrl
WHERE (ProjectWorkspaceInternalUrl IS NOT NULL)
So it looks like;
Create a temporary variable to store URLs
- Open the Variables window in Visual Studio (it’s one of the icons top right above the package area.
- Add a variable called ProjectList and set the Type to Object
- Now add a Recordset Destination to the data flow and link the blue arrow from our OLE Source to it
- In the advanced properties specify the VariableName just created.
- On the Input Columns tab select all columns
All other settings can be left with defaults. The Data Flow task should now look like this;
Create a Foreach loop to iterate through sites
Now we’re getting to the tricky part, SSIS enables this kind of iteration nicely, however we need to do a couple of special things to work with our OData list data, specifically update the datasource URL to the next project and extend the returned data by adding the ProjectId field of the project (for our FK).
- Firstly add two new Variables to the package; ProjectId and ProjectUrl, both as type String
- Next drag in a Foreach Loop Container and connect the green arrow from the previous item to it
- In the Collection properties of the Foreach Loop select Foreach ADO Enumerator and specify the User::ProjectList variable created earlier
- Next under Variable Mappings specify our other variables in the following index order: Index 0 – User:ProjectId, Index 1 – User::ProjectURL, (Note this order is important and used in the Foreach below!)
Update the SharePoint Listdata datasource URL
The first action inside our Foreach loop must be to update the connection string property of our SharePoint listdata dataconnection created at the beginning. Don’t run away but we’ll do this using a Script Task and a few lines of code.
- Drop a Script Task inside the Foreach loop container
- In the properties make sure the ScriptLanguage is Visual C# 2010
- Then add a ReadOnlyVariable pointing to User::ProjectUrl
- Click Edit Script and we’ll be replacing the Main() block with the following code:
public void Main()
// TODO: Add your code here
String ProjectURL = (String)Dts.Variables.Value;
Dts.TaskResult = (int)ScriptResults.Success;
IMPORTANT NOTE: In that code we reference the OData_SharePointListData connection by name which we created way back in the first step! So if you used another name make sure you update both references to that name in the code.
Save and close that script window and we can move on to the last couple of steps.
Getting project site data from each site
- Add a Data Flow Task after the script task inside the Foreach loop container, and link the green arrow from the script task to the data flow task
- In the Data Flow tab for this item, first add an OData Source
- Configure the source using the OData_SharePointListData source created and specify the required collection (I’m using Risks for the sake of this demo)
- Select the desired columns and preview to test
Note here that I’m able to see the values in the test project site that I specified right back at the beginning, however when the solution executes the URL will have been updated before this step. Whenever you edit this solution in the future this site must be valid and accessible else you will get errors here.
Next we need to add our project server ProjectId to the data coming from OData so we can use it is a foreign key in the destination database table.
- Drag a Derived Column to the canvas link it 2nd and edit the properties
- Add a Derived Column Name as ProjectId
- From the Variables and Parameters drag the User::ProjectId into the Expression box
- Lastly (importantly) from the Type Casts list drag the (DT_GUID) item to the start of the expression so it is exactly: (DT_GUID) @[User::ProjectId]
- Now finally use the Destination Assistant to create and configure our Risks database table where the data will be saved
- Configure the mappings as required, and double check that ProjectId has automatically been mapped
We’re just about done, that Data Flow should now look like this:
The solution should now run and work as expected, with only one catch, each time you run it it will append all of the data again, so for a quick (hackish) solution to that the final step here is to add a step to the very beginning to delete existing data in our report database.
- On the Control Flow tab add one more Execute SQL Task to the top of the canvas and make our first GetProjectData follow it.
- Edit the properties and select our temp database ProjectOnline_OData as the Connection
- Then set the following as the SQL Query (Note: Don’t use Build Query as that won’t let you cut and paste, just paste it into the SQLStatement field
DELETE FROM Projects WHERE 1=1
DELETE FROM Tasks WHERE 1=1
DELETE FROM Risks WHERE 1=1
- No other options need be changed
Done, test it out and it should run and look something like this when run:
That was easy, wasn’t it? Aren’t we all glad to not need the SSRS SharePoint list datasource anymore! *ahem* [sarcasm]
Well now that you have your data-warehouse of project data and all artifacts, that should make your SSRS report writing not only simpler but actually much faster performing.
Download this full solution here and any post any questions below.
To use this you will need to update all your data-connections and recreate the destination Tables (as per steps above).
7/02 Updated this post to become an index of my session posts:
Here I’ll break this full write-up down into three parts so make sure to check back here over the coming week(s) as they are posted:
For those of you who will be at Project Conf next week, add #PC403 to your calendar on Wednesday (http://www.msprojectconference.com/SessionDetail.aspx?id=12821) and make sure to come say hello!
This is an issue I have come across deploying Apps in non-English PWA site collections, while the App page loads clearly something is wrong, see a screenshot:
If you press F12 in IE or Chrome and open the console you will see errors like the following:
Failed to load resource: the server responded with a status of 404 (NOT FOUND) https://*****-1edc3a4f15d104.sharepoint.com/sites/testfi/_catalogs/theme/Themed/634341C6/corev15app-DDE41C8D.themedcss?ctag=0
From my testing this issue only came up in the last few months but fortunately there is a fix.
The problem appears to be caused by the provisioning job not deploying certain resources, fortunately this can be fixed by modifying the Alternate Language settings of the site collection. Simply selecting English as an alternate language will install the required components, and better yet when you then proceed to un-select English from the alternate language options the required resources remain and the issue is fixed for good.
Steps to correct issue
- Open Site Settings in your PWA or other site collection where the App is deployed.
- Open Alternate Languages, and select English as an alternate language.
- Save the settings and wait for the changes to apply.
- Now opening the App should work without display issues.
- Finally if required repeat step 1 and 2 and de-select English to restore the previous setup (while keeping the fix).