Bulk Operations - Adding multiple rows to sheet - smartsheet-api

I'm attempting to write data from a data table to a sheet via the smartsheet API (using c# SDK). I have looked at the documentation and I see that it supports bulk operations but I'm struggling with finding an example for that functionality.
I've attempted to do a work around and just loop through each record from my source and post that data.
//Get column properties (column Id ) for existing smartsheet and add them to List for AddRows parameter
//Compare to existing Column names in Data table for capture of related column id
var columnArray = getSheet.Columns;
foreach (var column in columnArray)
{
foreach (DataColumn columnPdiExtract in pdiExtractDataTable.Columns)
{
//Console.WriteLine(columnPdiExtract.ColumnName);
if(column.Title == columnPdiExtract.ColumnName)
{
long columnIdValue = column.Id ?? 0;
//addColumnArrayIdList.Add(columnIdValue);
addColumnArrayIdList.Add(new KeyValuePair<string, long>(column.Title,columnIdValue));
}
}
}
foreach(var columnTitleIdPair in addColumnArrayIdList)
{
Console.WriteLine(columnTitleIdPair.Key);
var results = from row in pdiExtractDataTable.AsEnumerable() select row.Field<Double?>(columnTitleIdPair.Key);
foreach (var record in results)
{
Cell[] cells = new Cell[]
{
new Cell
{
ColumnId = columnTitleIdPair.Value,
Value = record
}
};
cellRecords = cells.ToList();
cellRecordsInsert.Add(cellRecords);
}
Row rows = new Row
{
ToTop = true,
Cells = cellRecords
};
IList<Row> newRows = smartsheet.SheetResources.RowResources.AddRows(sheetId, new Row[] { rows });
}
I expected to generate a value for each cell, append that to the list and then post it through the Row Object. However, my loop is appending the column values as such: A1: 1, B2: 2, C3: 3 instead of A1: 1, B1: 2, C3: 3
The preference would be to use bulk operations, but without an example I'm a bit at a loss. However, the loop isn't working out either so if anyone has any suggestions I would be very grateful!
Thank you,
Channing

Have you seen the Smartsheet C# sample read / write sheet? That may be a useful reference. It contains an example use of bulk operations that updates multiple rows with a single call.

Taylor,
Thank you for your help. You lead me in the right direction and I figured my way through a solution.
I grouped by my column value list and built records for the final bulk operation. I used a For loop but the elements in each grouping of columns is cleaned and assigned a 0 prior to this method so that they retain the same count of values per grouping.
// Pair column and cell values for row building - match
// Data source column title names with Smartsheet column title names
List<Cell> pairedColumnCells = new List<Cell>();
//Accumulate cells
List<Cell> cellsToImport = new List<Cell>();
//Accumulate rows for additions here
List<Row> rowsToInsert = new List<Row>();
var groupByCells = PairDataSourceAndSmartsheetColumnToGenerateCells(
sheet,
dataSourceDataTable).GroupBy(
c => c.ColumnId,
c => c.Value,
(key, g) => new {
ColumnId = key, Value = g.ToList<object>()
});
var countGroupOfCells = groupByCells.FirstOrDefault().Value.Count();
for (int i = 0; i <= countGroupOfCells - 1; i++)
{
foreach (var groupOfCells in groupByCells)
{
var cellListEelement = groupOfCells.Value.ElementAt(i);
var cellToAdd = new Cell
{
ColumnId = groupOfCells.ColumnId,
Value = cellListEelement
};
cellsToImport.Add(cellToAdd);
}
Row rows = new Row
{
ToTop = true,
Cells = cellsToImport
};
rowsToInsert.Add(rows);
cellsToImport = new List<Cell>();
}
return rowsToInsert;

Related

How to compare if two tables are the same with Office-js

I'm iterating through the whole document, paragraph by paragraph. When a paragraph is in a table I want to store it in an array. But I don't want to store duplicates.
I tried to simply use indexOf, with == or with === but without success:
let tables = [];
// Iterate over the document
for (let i = 0; i < paragraphs.items.length; ++i)
{
// if this is in a table
if (paragraphs.items[i].tableNestingLevel > 0)
{
let table = paragraphs.items[i].parentTable;
table.load();
await context.sync();
// Try to avoid duplicates in the array (doesn't work)
if(tables.indexOf(table) == -1)
{
tables.push(table);
}
}
...
}
I know that document.body.tables exists, but I iterate over paragraphs to parse other structures than tables in the order they are in the document.

Group By with Entity Framework

enter image description hereI have a code. And there you need to make a grouping by name.
//<date,<partid,amount>>
Dictionary<string, Dictionary<int, double>> emSpending = new Dictionary<string, Dictionary<int, double>>();
foreach (Orders order in db.Orders.ToList())
{
foreach (OrderItems orderitem in order.OrderItems.ToList())
{
if (!emSpending.ContainsKey(order.Date.ToString("yyyy-MM"))) emSpending.Add(order.Date.ToString("yyyy-MM"), new Dictionary<int, double>());
if (!emSpending[order.Date.ToString("yyyy-MM")].ContainsKey(Convert.ToInt32(orderitem.PartID))) emSpending[order.Date.ToString("yyyy-MM")].Add(Convert.ToInt32(orderitem.PartID), 0);
emSpending[order.Date.ToString("yyyy-MM")][Convert.ToInt32(orderitem.PartID)] += Convert.ToDouble(orderitem.Amount);
}
}
DataGridViewColumn col1 = new DataGridViewColumn();
col1.CellTemplate = new DataGridViewTextBoxCell();
col1.Name = "Department";
col1.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
col1.HeaderText = "Department";
dgvEMSpending.Columns.Add(col1);
foreach (string date in emSpending.Keys)
{
DataGridViewColumn col = new DataGridViewColumn();
col.Name = date;
col.HeaderText = date;
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
col.CellTemplate = new DataGridViewTextBoxCell();
dgvEMSpending.Columns.Add(col);
}
List<string> allKey = emSpending.Keys.ToList();
foreach (string date in allKey)
if (date == "Department") continue;
else
{
dgvEMSpending.Rows.Add();
foreach (int partid in emSpending[date].Keys)
{
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = db.Parts.Where(x => x.ID == partid).SingleOrDefault().Name.GroupBy(Name);
for (int i = 1; i < dgvEMSpending.Columns.Count; i++)
{
if (!emSpending.ContainsKey(dgvEMSpending.Columns[i].Name)) emSpending.Add(dgvEMSpending.Columns[i].Name, new Dictionary<int, double>());
if (!emSpending[dgvEMSpending.Columns[i].Name].ContainsKey(partid)) emSpending[dgvEMSpending.Columns[i].Name].Add(partid, 0);
double val = emSpending[dgvEMSpending.Columns[i].Name][partid];
dgvEMSpending.Rows[dgvEMSpending.RowCount - 1].Cells[i].Value = val;
}
}
}
I tried to use group by myself, but something doesn't work. It just outputs the same names, and I want to group them so that there is a grouping. Pls helped to me.
Ok, a few issues to help you out first. This code:
foreach (Orders order in db.Orders.ToList())
{
foreach (OrderItems orderitem in order.OrderItems.ToList())
{
if (!emSpending.ContainsKey(order.Date.ToString("yyyy-MM"))) emSpending.Add(order.Date.ToString("yyyy-MM"), new Dictionary<int, double>());
if (!emSpending[order.Date.ToString("yyyy-MM")].ContainsKey(Convert.ToInt32(orderitem.PartID))) emSpending[order.Date.ToString("yyyy-MM")].Add(Convert.ToInt32(orderitem.PartID), 0);
emSpending[order.Date.ToString("yyyy-MM")][Convert.ToInt32(orderitem.PartID)] += Convert.ToDouble(orderitem.Amount);
}
}
Right off the bat this is going to trip lazy loading on OrderItems. If you have 10 orders 1-10 you're going to be running 11 queries against the database:
SELECT * FROM Orders;
SELECT * FROM OrderItems WHERE OrderId = 1;
SELECT * FROM OrderItems WHERE OrderId = 2;
// ...
SELECT * FROM OrderItems WHERE OrderId = 10;
Now if you have 100 orders or 1000 orders, you should see the problem. At a minimum ensure that if you are touching a collection or reference on entities you are loading, eager load it with Include:
foreach (Orders order in db.Orders.Include(x => x.OrderItems).ToList())
This will run a single query that fetches the Orders and their OrderItems. However, if you have a LOT of rows this is going to take a while and consume a LOT of memory.
The next tip is "only load what you need". You need 1 field from Order and 2 fields from OrderItem. So why load everything from both tables??
var orderItemDetails = db.Orders
.SelectMany(o => o.OrderItems.Select(oi => new { o.Date, oi.PartId, oi.Amount })
.ToList();
This would give us just the Order date, and each Part ID and Amount. Now that this data is in memory we can group it to populate your desired dictionary structure without having to iterate over it row by row.
var emSpending = orderItemDetails.GroupBy(x => x.Date.ToString("yyyy-MM"))
.ToDictionary(g => g.Key,
g => g.GroupBy(y => y.PartId)
.ToDictionary(g2 => g2.Key, g2 => g2.Sum(z => z.Amount)));
Depending on the Types in your entities you may need to insert casts. This first groups the outer dictionary of the yyyy-MM of the order dates, then it groups the remaining data for each date by part ID, and sums the Amount.
Now relating to your question, from your code example I'm guessing the problem area you are facing is this line:
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = db.Parts
.Where(x => x.ID == partid)
.SingleOrDefault().Name.GroupBy(Name);
Now the question would be to explain what exactly you are expecting from this? You are fetching a single Part by ID. How would you expect this to be "grouped"?
If you want to display the Part name instead of the PartId then I believe you would just want to Select the Part Name:
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = db.Parts
.Where(x => x.ID == partid)
.Select(x => x.Name)
.SingleOrDefault();
We can go one better to fetch the Part names for each used product in one hit using our loaded order details:
var partIds = orderItemDetails
.Select(x=> x.PartId)
.Distinct()
.ToList();
var partDetails = db.Parts
.Where(x => partIds.Contains(x.ID))
.ToDictionary(x => x.ID, x => x.Name);
This fetches us a dictionary set indexed by ID for the part names, it would be done outside of the loop after we had loaded the orderItemDetails. Now we don't have to go to the DB with every row:
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = partDetails[partId];

Defining data type of a Google Charts DataTable column after it has been created

I am a newbie of Google charts. I am trying to change data type of a google chart data table column after this has been created. Searching for a solution over the internet I have bumped into this solution for a datetype column. May you generalize it in order to change a chosen data type column from number to string? I would like to change it so that I can visualize some strings in a number column, as you can see in the attached screenshot.
My trial is:
<script type="text/javascript">
function drawVisualization() {
$.get("delivery_pl_daily_test.csv", function(csvString) {
// transform the CSV string into a 2-dimensional array
var arrayData = $.csv.toArrays(csvString, {onParseValue: $.csv.hooks.castToScalar});
// this new DataTable object holds all the data
var data = new google.visualization.arrayToDataTable(arrayData);
var columns = [];
for (var i = 0; i < data.getNumberOfColumns(); i++) {
columns.push(i);
}
var view = new google.visualization.DataView(data);
columns[0] = {
calc: row,
label: arrayData[0][0],
type: 'string'
};
view.setColumns(columns);
Thanks in advance.
Drigo
what you have looks close,
but calc should be a function,
that returns the value for the column.
the calc function receives the data table and current row as arguments.
here, I simply return the formatted value of the column.
columns[0] = {
calc: function (dt, row) {
return dt.getFormattedValue(row, 0);
},
label: arrayData[0][0],
type: 'string'
};

SapUi5 Table Multiple Filter

I'm developing a sap ui5 application using sap.ui.table.Table.
I need to apply a filter based on multiple strings. For example, if the user input is an array like:
["Stack", "Overflow"]
I need:
Filter all table fields by "Stack";
Filter the result of point 1 by "Overflow";
the result will be all rows that have "Stack" and "Overflow", no matter the field.
Does anyone have a solution?
As per the sap.ui.model.Filter documentation, you can create a filter either based on a filter info object, or from an array of previously created filters. This allows us to do the following:
Create a filter for the first value (eg "Stack")
Create a filter for the second value (eg "Overflow")
Create a filter which contains both of these values, and use it to filter the table.
Let's have a look at some code.
// We will only display rows where ProductName contains
// "Stack" AND CustomerName equals "Overflow"
var oFilterForProductName,
oFilterForCustomerName,
aArrayWhichContainsBothPreviousFilters = [],
oFilterToSetOnTheTable;
var sValueToFilterTheProductNameOn = "Stack",
sValueToFilterTheCustomerNameOn = "Overflow";
var sKeyForProductNameInTheTableModel = "ProductName",
sKeyForCustomerNameInTheTableModel = "CustomerName";
var oTableToFilter = this.byId("myTableId");
// Step 1: create two filters
oFilterForProductName = new sap.ui.model.Filter(
sKeyForProductNameInTheTableModel,
sap.ui.model.FilterOperator.Contains,
sValueToFilterTheProductNameOn);
oFilterForCustomerName = new sap.ui.model.Filter(
sKeyForCustomerNameInTheTableModel,
sap.ui.model.FilterOperator.EQ,
sValueToFilterTheCustomerNameOn);
// Step 2: add these two filters to an array
aArrayWhichContainsBothPreviousFilters.push(oFilterForProductName);
aArrayWhichContainsBothPreviousFilters.push(oFilterForCustomerName);
// Step 3: create a filter based on the array of filters
oFilterToSetOnTheTable = new sap.ui.model.Filter({
filters: aArrayWhichContainsBothPreviousFilters,
and: true
});
oTableToFilter.getBinding("items").filter(oFilterToSetOnTheTable , sap.ui.model.FilterType.Application);
Hope this helps. Let me know if you have any questions.
Chris
Please pass that array in for loop and pass filters like,
var tableId = this.byId("oTable");
for(var i=0;i < array.length ; i++)
{
oTable.getBinding().filter(new sap.ui.model.Filter("", sap.ui.model.FilterOperator.Contains, array[0]));
}
it may be helpful for you.

map reduce function return id instead of count

I m applying map reduce function but facing an issue. In case of one record it returns the id instead of count = 1.
map_func = """function () {
emit(this.school_id, this.student_id);
}"""
reduce_func = """
function (k, values) {
values.length;
}
"""
if school 100 has only one student then it should return school id 100 , value =1 but in this scenario it return
schoolid = 100 , value = 12 ( 12 is its student id in db ). for other records it works fine.
map_func = """function () {
emit({this.school_id, this.student_id},{count:1});
}"""
reduce_func = """
function (k, values) {
var count =0 ;
values.forEach(function(v)
{
count += v['count'];
});
return {count:count};
}
"""
map_func2 = """
function() {
emit(this['_id']['school_id'], {count: 1});
}
"""
http://cookbook.mongodb.org/patterns/unique_items_map_reduce/
i used this example but it uses two maps-reduce function so it took much more time.
It looks like you may be misunderstanding some of the mechanics of mapReduce.
The emit will get called on every document, but reduce will only be called on keys which have more than one value emitted (because the purpose of the reduce function is to merge or reduce an array of results into one).
You map function is wrong - it needs to emit a key and then a value you want - in this case a count.
Your reduce function needs to reduce these counts (add them) but it has to work correctly even if it gets called multiple times (to re-reduce previously reduced results).
I recommend reading here for more details.
if you are trying to count number of students per school :
map = """emit(this.school_id, 1)"""
reduce = """function (key, values) {var total = 0; for (var i = 0; i < values.length; i++) { total += values[i]; } return total;} """