0

Forgive my disgusting method: I haven't been able to do this in SQL (as my use is dynamic and I'm not versed in the ways of SQL enough to have it create the dynamic output I'm wanting/needing) or EF as I haven't been able to get it to work for some unknown reason (if anyone has an example that may help a beginner with EF and DataTables please share)

I have a DataTable (dt) with values that I'm trying to pull, calculate and then input into another DataTable (fDt).

Example of dt:

+-------------------------------------------------------+
| ID | CustName | 201501 | 201502 | 201503 | 201504 | ..|
+-------------------------------------------------------+
| 32 | CustOne  | 100.00 | 200.00 | 400.00 | 700.00 | ..|
| 56 | CustTwo  |        |        |        | 500.00 | ..|
| 89 | CustThree| 222.22 | 333.33 | 444.44 | 555.55 | ..|
| .. | ...      |   ..   |   ..   |   ..   |   ..   | ..|
+-------------------------------------------------------+

I'm wanting to take the values after CustName to use in my calculation.

The calculation then gives the percent difference between two of the columns from the previous table:

+-------------------------------------------------------+
| ID | CustName | PerDiff02 | PerDiff03 | PerDiff04 | ..|
+-------------------------------------------------------+
| 32 | CustOne  |     0     |     100   |     200   | ..|
| 56 | CustTwo  |           |           |    85.00  | ..|
| 89 | CustThree|   66.66   |    75.00  |    80.00  | ..|
| .. | ...      |   ....    |    ....   |    ....   | ..|
+-------------------------------------------------------+

(The percentages are spoofed, but they show what I'm trying to achieve.)

The PerDiffs should start from the second month shown (201502) and continue to current.

I believe I've done this, however with the code I have now:

for (i = 2; i <= (dt.Columns.Count - 2); i++)
{
    for (int j = 0; j < (dt.Columns.Count); j++)
    {
        //decimal? month1 = dt.Rows[i].Field<decimal?>(j);
        //decimal? month2 = dt.Rows[i].Field<decimal?>(j + 2);

        decimal? month1 = (decimal?)dt.Rows[i][j];
        decimal? month2 = (decimal?)dt.Rows[i][j + 2];

        fDt.Rows[i][j - 1] = ((month1 - month2) / month1) * 100;

    }            
} 

I have included two commented out lines as they produce the same error that I'm currently getting with this loop:

Specified cast is not valid.

For the lines declaring month1 and month2.

QUESTION:

How can I take the data from one of my Cells in one DataTable in an equation, and then input into another DataTable?

If anything is unclear, please let me know!

EDIT:

Here is my entire Method for getting my dataTable:

public DataTable GetPerDiff(DataTable dt)
    {
        var fDt = new DataTable();

        int i;
        int fieldCount = dt.Columns.Count;
        string[] colHeaders = new string[fieldCount];

        for (i = 0; i < fieldCount; i++)
        {
            colHeaders[i] = dt.Columns[i].ToString();
        }

        fDt.Columns.Add(colHeaders[0]);
        fDt.Columns.Add(colHeaders[1]);


        //Get's the data into the new table
        for (i = 1; i < dt.Rows.Count-1; i++)
        {
            fDt.Rows.Add(dt.Rows[i][0], dt.Rows[i][1]);
        }

        // Gets the column headers for dataTable fDt
        for (i = 2; i <= (dt.Columns.Count - 2); i++)
        {
            string colName = "PerDiff" + dt.Columns[i + 1];
            fDt.Columns.Add(colName);
        }

        for (i = 2; i <= (dt.Columns.Count - 2); i++)
        {
            for (int j = 0; j < (dt.Columns.Count); j++)
            {
                //decimal? month1 = dt.Rows[i].Field<decimal?>(j);
                //decimal? month2 = dt.Rows[i].Field<decimal?>(j + 2);

                decimal? month1 = (decimal?)dt.Rows[i][j];
                decimal? month2 = (decimal?)dt.Rows[i][j + 2];

                fDt.Rows[i][j - 1] = ((month1 - month2) / month1) * 100;

            }

        } 


        return dt;
    }
2
  • Does the second table already exist and you are trying to fill it, or are you trying to create it from the first? Commented Nov 29, 2018 at 21:55
  • @BrianRogers , the second table already exists and the required first two columns data (ID, CustName) and required column headers for the rest of the columns are all created already. Commented Nov 29, 2018 at 21:57

2 Answers 2

1

I think the main problem here is that your loop indexes and conditions are wrong, which leads to the invalid cast when you retrieve the data. You have this code:

for (i = 2; i <= (dt.Columns.Count - 2); i++)
{
    for (int j = 0; j < (dt.Columns.Count); j++)
    {
        //decimal? month1 = dt.Rows[i].Field<decimal?>(j);
        //decimal? month2 = dt.Rows[i].Field<decimal?>(j + 2);

        decimal? month1 = (decimal?)dt.Rows[i][j];
        decimal? month2 = (decimal?)dt.Rows[i][j + 2];

        fDt.Rows[i][j - 1] = ((month1 - month2) / month1) * 100;
    }            
} 

The inner loop, which indexes the columns of the table, starts with j = 0. But column 0 contains the customer ID, and column 1 contains the customer name. Neither of these are decimals, so when you retrieve dt.Rows[i][j] and cast it to decimal? it causes an InvalidCastException. This loop should start at index 2 (to skip over the ID and name columns) and the loop condition should be j < dt.Columns.Count - 1 because there is one less column in the destination table than the source table.

Next problem: It appears you intend the outer loop variable i to index into the rows collection, but you are starting the loop with index 2 instead of 0 and the loop condition is going against the number of columns, not rows. So you will end up skipping over the first two rows and will probably miss a lot of rows at the end depending on how many rows you have compared to columns. This looks like it might have been a copy-paste error or something.

There's more:

  • If you are retrieving month1 from column j, then month2 should be the next month, or j + 1. You have it as j + 2. This will give you the wrong results and also end up throwing an IndexOutOfRangeException on the last month.

  • I think your subtraction may be backward. If the later month (month2) represents an increase over the earlier month (month1), then you want to show that as a positive number, right? So you would need (month2 - month1) / month1 * 100. You have it as (month1 - month2)....

  • You are not properly handling possible null values in the table. In a DataTable, a null is represented by the value DBNull, which cannot be cast to a decimal?. So that could be another source of an InvalidCastException. The Field<T> extension method handles the conversion between DBNull and null for you, so I would recommend using that. (It looks like you were using this method at one time but have since commented it out.) But note, however, that if you do a calculation on two nullable decimals where one or the other has a null value, the result is also null. And when you try to set the null value in the fDt row after the calculation, you will have the same problem in reverse, because the DataTable requires DBNull instead of null. So you should use the SetField<T> method to handle storing the value back into the table.

  • You are also not considering the possibility that month1 may be zero, which could lead to a DivideByZeroException in your calculation.

Now that you have posted the rest of your code I noticed a few other issues:

  • You're not copying the column data types from the first table to the second.

  • In the loop where you are adding the rows to the destination table and copying the customer IDs and names into it, you are starting with row index 1 instead of 0. This will skip the first row. You will also miss the last row, because your loop condition is i < dt.Rows.Count - 1 instead of i < dt.Rows.Count. (You can actually combine this code with the outer calculation loop for simplicity.)

  • Your method is returning the wrong table-- it should return fDt, not dt.

After fixing all these problems (and some simplification creating the ID and name columns), the code should look something like this, which should be pretty close to what you want:

public DataTable GetPerDiff(DataTable dt)
{
    var fDt = new DataTable();

    // Copy columns for customer name and ID
    fDt.Columns.Add(dt.Columns[0].ColumnName, dt.Columns[0].DataType);
    fDt.Columns.Add(dt.Columns[1].ColumnName, dt.Columns[1].DataType);

    // Create the PerDiff columns
    for (int j = 2; j < dt.Columns.Count - 1; j++)
    {
        string colName = "PerDiff" + dt.Columns[j + 1];
        fDt.Columns.Add(colName, dt.Columns[j + 1].DataType);
    }

    for (int i = 0; i < dt.Rows.Count; i++)
    {
        fDt.Rows.Add(dt.Rows[i][0], dt.Rows[i][1]);

        for (int j = 2; j < dt.Columns.Count - 1; j++)
        {
            decimal? month1 = dt.Rows[i].Field<decimal?>(j);
            decimal? month2 = dt.Rows[i].Field<decimal?>(j + 1);

            if (month1 != decimal.Zero)
            {
                fDt.Rows[i].SetField(j, (month2 - month1) / month1 * 100);
            }
        }
    }
    return fDt;
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! This seems to work 100%! I have been working on this for months! (I'm a Junior dev that kinda got thrown in and so many little errors that I had over looked!) Thank you for your time spent on this answer and assisting! I can't upvote enough!
0

Try following :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("ID", typeof(int));
            dt.Columns.Add("CustName", typeof(string));
            dt.Columns.Add("201501", typeof(decimal));
            dt.Columns.Add("201502", typeof(decimal));
            dt.Columns.Add("201503", typeof(decimal));
            dt.Columns.Add("201504", typeof(decimal));

            dt.Rows.Add(new object[] {32, "CustOne",100.00, 200.00, 400.00, 700.00});
            dt.Rows.Add(new object[] {56, "CustTwo", 100.00 , 200.00 , 300.00, 500.00});
            dt.Rows.Add(new object[] {89, "CustThree", 222.22 ,333.33, 444.44, 555.55});

            DataTable dt2 = new DataTable();
            dt2.Columns.Add("ID", typeof(int));
            dt2.Columns.Add("CustName", typeof(string));
            for (int i = 2; i < dt.Columns.Count - 1; i++)
            {
                dt2.Columns.Add("PerDiff" + (i).ToString("0#"), typeof(decimal));
            }

            foreach (DataRow row in dt.AsEnumerable())
            {
                DataRow newRow = dt2.Rows.Add();
                newRow["ID"] = row.Field<int?>("ID");
                newRow["CustName"] = row.Field<string>("CustName");
                for (int i = 2; i < dt.Columns.Count - 1; i++)
                {
                    newRow[i] = row.Field<decimal?>(i + 1) / row.Field<decimal?>(i);
                }
            }


        }
    }
}

1 Comment

Thanks for you answer! I applied your foreach as that was the only thing I was needing to add, it seems (I've added my complete method to the post now). Your foreach gives the Specified cast is not valid error on the newRow["ID"] = row.Field<int?>("ID"); line

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.