SSIS Script Component Source - Read from SharePoint 365 List via Odata - sharepoint

I apologize if my question is not adequately described. I am a .Net / C# / SSIS newbie. Also apologize if already answered, I've tried searching here and Google for a few hours without luck.
Background: I need to pull data rows from a SharePoint 365 list and unpivot certain columns into a format ready for import to a SQL Server table. I realize that SSIS has an Odata Source and built-in Unpivot component, and I've successfully used those for proof of concept.
However, I believe I need a custom script component because the nummber of columns to unpivot from the source SharePoint list is variable. Each month or so, a new column will be added (it relates to a financial forecasting "tool" in SharePoint, and the latest forecasted month changes). My understanding is that source columns must be defined in SSIS at design time, so if my source columns are changing, the only way I can think to address this without manually changing the SSIS data flow each month is to programatically combine the Odata source and unpivot functions into a custom script component.
I understand, or can figure out, the unpivot logic. The part I am struggling with is how to actually connect to and expose a given list and it's data rows / columns as lists that I can loop through and perform my mapping to the output columns.
My "starting point" for requested guidance is this:
1) Created and successfully connected to the SharePoint site in question using standard SSIS Odata Connection Manager.
2) Created standard "Script Component", type = source, on the visual designer.
3) From script component properties, associated the Odata connection manager with the name of "myConnection".
4) Need help -> within the script component, open a connection to a specific list, read it's contents, and perform unpivot logic.
For illustration purposes, assume the source is a SharePoint list with two "fixed" string columns titled Study and Site, and a variable number of columns with names matching month-end dates (e.g. 9/30/2016, 10/31/2016, etc.) that contain integer values. I want to map the study and site source columns to destination columns of the same name and unpivot the month columns where column name is mapped to ProjectionMonth and the integer value is mapped to ProjectionValue.
Here's the basic algorithm I have in mind (I realize this isn't compilable - that's where I need your help!):
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using System.Data.SqlClient;
[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
public class ScriptMain : UserComponent
{
IDTSConnectionManager100 connMgr;
SqlConnection sqlConn; // from MSDN tutorial, but I don't know how to adapt to Odata/SharePoint 365 list
SqlDataReader sqlReader; // not sure how to adapt
public override void AcquireConnections(object Transaction)
{
connMgr = this.Connections.myConnection;
sqlConn = (SqlConnection)connMgr.AcquireConnection(null); // This is from MSDN tutorial, but I don't know how to adapt to Odata
}
public override void PreExecute()
{
//Not sure how to adapt to odata to read specific SharePoint list
SqlCommand cmd = new SqlCommand("SELECT * FROM <some sharepoint list>", sqlConn);
sqlReader = cmd.ExecuteReader();
}
public override void PostExecute()
{
sqlReader.Close(); // Not sure how to adapt.
}
public override void CreateNewOutputRows()
{
string myStudy;
string mySite;
string myProjectionMonth;
string myProjectionValue;
// This is a rough representation of the logic needed.
// I realize that the actual code to access column values / names depends on the class(es) I need to use, but not sure what those classes are / how to access
foreach (myListRow in sqlConn.rows)
{
myStudy = myListRow.Columns["Study"].value;
mySite = myListRow.Columns["Site"].value;
foreach (myColumn in myListRow.Columns)
if (DateTime.TryParse(myColumn.Name, out dateValue))
{
myProjectionMonth = myColumn.Name;
myProjectionValue = myColumn.Value;
Output0Buffer.AddRow();
Output0Buffer.Study = myStudy;
Output0Buffer.Site = mySite;
Output0Buffer.ProjectionMonth = myProjectionMonth;
Output0Buffer.ProjectionValue = myProjectionValue;
}
}
}
}
Edit: as an example, assume the source SharePoint list has the following:
Study Site 9/30/2016 10/31/2016
123 ABC 5 10
I want to the script component to connect to the list, read it's contents, and return the following unpivoted data set for eventual load into SQL Server:
Study Site ProjectionMonth ProjectionValue
123 ABC 9/30/2016 5
123 ABC 10/31/2016 10

So this is probably not an ideal way of doing it, and it doesn't leverage the standard SSIS Odata Connection Manager that I wanted... but it does technically get the job done and it's good enough for me, for now.
Would be interested on any suggested feedback / improvements / etc., if you have any.
#region Namespaces
using System;
using Microsoft.SharePoint.Client;
using System.Security;
using System.Collections.Generic;
#endregion
[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
public class ScriptMain : UserComponent
{
public override void CreateNewOutputRows()
{
// Connect to SharePoint
ClientContext context = new ClientContext("https://<redacted>.sharepoint.com/Development");
SecureString passWord = new SecureString();
foreach (char c in Variables.sharepointPassword.ToCharArray()) passWord.AppendChar(c);
context.Credentials = new SharePointOnlineCredentials("<redacted>#<redacted>.onmicrosoft.com", passWord);
// Define the target list
List EnrollmentList = context.Web.Lists.GetByTitle("Enrollment Projections");
// Find all fields in the target list
FieldCollection myFields = EnrollmentList.Fields;
context.Load(myFields);
context.ExecuteQuery();
// Load all rows from the target list
CamlQuery query = CamlQuery.CreateAllItemsQuery(100);
ListItemCollection items = EnrollmentList.GetItems(query);
context.Load(items);
context.ExecuteQuery();
//Store valid date fields
List<Field> myDateFields = new List<Field>();
foreach (Field tempField in myFields)
{
DateTime tempDate;
if (DateTime.TryParse(tempField.Title, out tempDate))
{
myDateFields.Add(tempField);
}
}
string myStudy;
string mySite;
string myMonth;
string myValue;
foreach (ListItem listItem in items)
{
myStudy = listItem["Study"].ToString();
mySite = listItem["Site"].ToString();
foreach (Field tempField in myDateFields)
{
myMonth = tempField.Title;
myValue = listItem[tempField.InternalName.ToString()].ToString();
Output0Buffer.AddRow();
Output0Buffer.Study = myStudy;
Output0Buffer.Site = mySite;
Output0Buffer.ProjectedMonth = myMonth;
Output0Buffer.ProjectedValue = Convert.ToInt32(myValue);
}
}
}
}

Related

Parse SubAccount to get individual segments

I would like to access the individual segments of a subaccount programmatically. Assuming I have a particular sub account set as ABC-123, I would like to be able to access ABC and 123 separately in code so that I can implement a particular business requirement.
I know that SubAccounts are saved in the Sub table as one string example ABC123. The sub account fields which link to this table would then link based on the ID (integer - PK of the Sub table). I can of course read from this table and then split accordingly (by taking the first 3 characters and the second 3 characters). However, I would like this to be dynamic so that the customization will work for different clients and clients may have different lengths for the segment. Therefore I cannot hard-code the value 3. I can make use of the SegmentValues table to retrieve the lengths of each Segment accordingly.
However, since Acumatica is already somehow carrying out this parsing (example in the UI), is there an API where Acumatica handles this logic and can provide the Sub-account as an array of strings. I tried to look into the SubAccountAttribute, PXDimensionSelectorAttribute, and SubAccountProvider but could not find anything which delivers this functionality.
Does Acumatica provide a way to split the sub-account into an array of strings or should I do this manually by identifying lengths from the Segment Values?
I believe some of the logic used to separate the segment are in the protected Definition class. The separated segments are in the Dimensions collections of the Definition class. You can access it in attributes that derive from PXDimensionAttribute class but since Definition class is protected you can't access it in a graph because PXGraph/PXGraphExtension don't derive from it.
Not much can be extracted from Dimension because most properties are protected:
You can roll your own by reading the segments of the segmented key:
Here is an example that write the segment values of the transactions subaccount in the trace for the Invoices and Memos screen:
using PX.Data;
using PX.Objects.AR;
using PX.Objects.CS;
using PX.Objects.GL;
namespace PX.Objects.SO
{
public class ARInvoiceEntry_Extension : PXGraphExtension<ARInvoiceEntry>
{
public void ARTran_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
ARTran tran = e.Row as ARTran;
if (tran != null && tran.SubID.HasValue)
{
Sub sub = SubAccountAttribute.GetSubaccount(Base, tran.SubID.Value);
if (sub != null && sub.SubCD != null)
{
short segmentStartIndex = 0;
foreach (Segment segment in PXSelect<Segment,
Where<Segment.dimensionID, Equal<Required<Segment.dimensionID>>>,
OrderBy<Asc<Segment.segmentID>>>.Select(Base, "SUBACCOUNT"))
{
if (segment.SegmentID.HasValue && segment.Length.HasValue)
{
PXTrace.WriteInformation(string.Format("Segment {0}: {1}",
segment.SegmentID,
sub.SubCD.Substring(segmentStartIndex, segment.Length.Value)));
segmentStartIndex += segment.Length.Value;
}
}
}
}
}
}
}
Trace results:

Creating Data Table from object array

I am not sure if I am going about this the correct way but I have a c# method which loads an excel sheet into a 2 dimentional object array. In this array item 1,1 - 1,16 contain headers, then 2-1 - 2-16 contain data that match up with those headers as do x-1 - x-16 from there on in. I would like to turn this array into a data table so ultimately I can have it in a format I will then import into an access or SQL server db depending on a clients needs. I have tried using the following code to no avail, but I have a feeling I am way off. Any help on this would be very much appreciated.
private void ProcessObjects(object[,] valueArray)
{
DataTable holdingTable = new DataTable();
DataRow holdingRow;
holdingTable.BeginLoadData();
foreach(int row in valueArray)
{
holdingRow = holdingTable.LoadDataRow(valueArray[row], true);
}
}
Any chance you're using a repository pattern (like subsonic or EF) or using LinqToSql?
You could do this (LinqToSql for simplicity):
List<SomeType> myList = valueArray.ToList().Skip([your header rows]).ConvertAll(f => Property1 = f[0] [the rest of your convert statement])
DataContext dc = new DataContext();
dc.SomeType.InsertAllOnSubmit(myList);
dc.SubmitChanges();

Dynamic data structures in C#

I have data in a database, and my code is accessing it using LINQ to Entities.
I am writing some software where I need to be able to create a dynamic script. Clients may write the scripts, but it is more likely that they will just modify them. The script will specify stuff like this,
Dataset data = GetDataset("table_name", "field = '1'");
if (data.Read())
{
string field = data["field"];
while (cway.Read())
{
// do some other stuff
}
}
So that script above is going to read data from the database table called 'table_name' in the database into a list of some kind based on the filter I have specified 'field='1''. It is going to be reading particular fields and performing normal comparisons and calculations.
The most important thing is that this has to be dynamic. I can specify any table in our database, any filter and I then must be able to access any field.
I am using a script engine that means the script I am writing has to be written in C#. Datasets are outdated and I would rather keep away from them.
Just to re-iterate I am not really wanting to keep with the above format, and I can define any method I want to behind the scenes for my C# script to call. The above could end up like this for instance,
var data = GetData("table_name", "field = '1'");
while (data.ReadNext())
{
var value = data.DynamicField;
}
Can I use reflection for instance, but perhaps that would be too slow? Any ideas?
If you want to read dynamically a DataReader context, it's a pretty easy step:
ArrayList al = new ArrayList();
SqlDataReader dataReader = myCommand.ExecuteReader();
if (dataReader.HasRows)
{
while (dataReader.Read())
{
string[] fields = new string[datareader.FieldCount];
for (int i =0; i < datareader.FieldCount; ++i)
{
fields[i] = dataReader[i].ToString() ;
}
al.Add(fields);
}
}
This will return an array list composed by a dynamic object based on the number of field the reader has.

SubSonic 3 Simple Query Tool

I want to use the Simple Query tool in SubSonic 3(.0.0.2) and the docs page (http://subsonicproject.com/docs/Simple_Query_Tool) implies there's a way to easily get hold of table column names (e.g. Product.ProductNameColumn):
int records = new Select(Product.ProductIDColumn, Product.ProductNameColumn).
From<Product>().GetRecordCount();
The ActiveRecord generated class doesn't appear to expose this info - there is no ProductIDColumn property. Is this a hang-up from version 2?
There's no way to get the column names in SubSonic 3 at the moment. You can still use the simple query tool with strings or if you modify the Structs.tt template you can get them generated for you.
Find this section of code (I think it's line 45):
<# foreach(var col in tbl.Columns){ #>
public IColumn <#=col.CleanName#>{
get{
return this.GetColumn("<#=col.Name#>");
}
}
<# }#>
and modify it so it looks like this:
<# foreach(var col in tbl.Columns){ #>
public IColumn <#=col.CleanName#>{
get{
return this.GetColumn("<#=col.Name#>");
}
}
public static string <#= col.CleanName #>Column{
get{
return "<#= col.Name #>";
}
}
<# }#>
Then you should get all your column names automatically generated as static properties.

Read/write Person metadata from a Word doc stored in SharePoint using VBA or VSTO?

Scenario: Document library in SharePoint with column x of "Person or Group" type. From within a VBA macro (or VSTO add-in) we're trying to access the MetaProperty on the document to set/get the user name. Any attempt to access the value via the ContentTypeProperties collection throws a
Type MisMatch error (13).
The Type property of the MetaProperty object says it's msoMetaPropertyTypeUser. I cannot find any examples of how to work with MetaProperties of this type. Anyone have any experience with this?
Thanks!
You should be able to just do something like this:
using (SPSite site = new SPSite("http://yoursite/subsite"))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists["DocLibraryName"];
SPListItemCollection items = list.GetItems(list.Views["All Documents"]);
foreach (SPListItem item in items)
{
item["Modified By"] = "Updated Value";
}
}
}
Any metadata for a document should be available by indexing the column name of the SPListItem.
I did it.
The trick here is actually to know that if you put a string corresponding to the user index in MOSS users in the custom property of the Word document, MOSS will recognize it and find the corresponding user to map the field.
so you just need to call http:///_vti_bin/usergroup.asmx
use the function GetUserInfo and retrieve the user index (ID) from it.
MOSSusergroup.UserGroup userGroupService = new MOSSusergroup.UserGroup();
userGroupService.Credentials = System.Net.CredentialCache.DefaultCredentials;
System.Xml.XmlNode node = userGroupService.GetUserInfo(userLogin);
string index = node.FirstChild.Attributes["ID"].Value;

Resources