Home » excel » vsto – Modify an Excel PivotTable with a Recordset and Refresh: Exception

vsto – Modify an Excel PivotTable with a Recordset and Refresh: Exception

Posted by: admin April 23, 2020 Leave a comment

Questions:

I’m at the edge of my wits here and have lost an entire day trying to do something that should not be so complicated.

I have a Recordset that was returned from a Sybase query. This recordset was used to build a PivotTable in Excel. So far, so good. I want to change a value in the PivotTable and to do so I use the new value to update certain records in the recordset. I can do the update in the RS without any problems and the values are saved in the RS next time that I iterate through it.

The problem is that the values are not reflected in the pivot table. I have tried:

  • pivotTable.Refresh();
    • COMException: RefreshTable method of PivotTable class failed
  • pivotTable.PivotCache().Refresh();
    • ComException: Exception from HRESULT: 0x800A03EC
  • pivotTable.Update();
    • No exception but the changes are not reflected in the pivot table

I have also tried cloning the recordset and creating an entirely new pivot table from it but although the Recordset has data in it, the PivotCache.RecordCount is 0

Code:

var app = ExcelAppHelper.GetExcelApp();
if (app.ActiveCell == null || app.ActiveCell.PivotTable == null)
    return;

PivotTable pivotTable = app.ActiveCell.PivotTable;
var rs = (Recordset)pivotTable.PivotCache().Recordset;
rs.MoveFirst();

s_lastSelectedPivotTree = new PivotFilterTree();
RecalculateSelectedValues(vmMain);

while (!rs.EOF)
{
    if (s_lastSelectedPivotTree.Contains(rs.Fields))
    {
        foreach (var dataFieldName in s_lastSelectedDataFields)
        {
            // update the values in the RS
            rs.Fields[dataFieldName].Value = newValue;
        }

        // commit the modifications into the RS
        rs.Update(Type.Missing, Type.Missing);
    }
    rs.MoveNext();
}
rs.MoveFirst();

// here is the magic line that will show me the updated pivot table
pivotTable.Update();

Anybody know how to do this? Modify a recordset then “refresh” the pivot table to recalculate the pivot table based on the recordset.

Thanks
Sean

How to&Answers:

Well, I solved it. It seems that once a Recordset has been “consumed” by a PivotTable, you can modify it all you want, it just won’t update in Excel. Despite the Recordset containing data, a refresh will empty the PivotTable and cause it to lose its data.

The workaround? Create a deep copy (a Recordset.Clone() won’t work) of the Recordset before giving it to the PivotTable and then each time you want to modify a value in it, modify the “clean” copy, make a new copy of it and pass the copy to the PivotTable to consume it. Then refresh the PivotTable.

        var newRS = RecordsetDeepCopy(oldRS);

        newRS.MoveFirst();
        oldRS.MoveFirst();
        while (!newRS.EOF)
        {
            if (s_lastSelectedPivotTree.Contains(newRS.Fields))
            {
                // set the new value in the selected data fields
                foreach (var dataFieldName in s_lastSelectedDataFields)
                {
                    oldRS.Fields[dataFieldName].Value = val;
                    newRS.Fields[dataFieldName].Value = val;
                }

                newRS.Update(Type.Missing, Type.Missing);
                oldRS.Update(Type.Missing, Type.Missing);
            }
            newRS.MoveNext();
            oldRS.MoveNext();
        }

        newRS.MoveFirst();
        oldRS.MoveFirst();

        pivotCache.Recordset = newRS;
        pivotCache.Refresh();

And the recordset deep copy method (not easy to find in C# on the web…)

private static Recordset RecordsetDeepCopy(Recordset src)
{
    var clone = new Recordset();
    int count = src.Fields.Count;
    var names = new object[count];
    int i = 0;

    foreach (ADODB.Field field in src.Fields)
    {
        names[i++] = field.Name;
        var attr = (FieldAttributeEnum)field.Attributes;
        clone.Fields._Append(field.Name, field.Type, field.DefinedSize, attr);
    }

    clone.Open(Missing.Value, Missing.Value, CursorTypeEnum.adOpenUnspecified, LockTypeEnum.adLockUnspecified, 0);

    src.MoveFirst();

    while (!src.EOF)
    {
        var values = new object[count];
        i = 0;
        foreach (ADODB.Field field in src.Fields)
        {
            values[i++] = field.Value;
        }

        clone.AddNew(names, values);
        src.MoveNext();
    }

    clone.Update(Missing.Value, Missing.Value);
    return clone;
}

Hopefully this spares some headaches for others…

Sean