linq to datatable incorrect column calculation - c#

Trying to calculate a column value with linq. It sums correctly but produces incorrect value when dividing or multiplying. Example is below.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
DataTable dtWidgets = Get_widgets();
DataTable dtHits = Get_hits();
var hit_rate =
from w in dtWidgets.AsEnumerable()
join h in dtHits.AsEnumerable()
on w[0] equals h[0]
select new
{
Date = w.Field<DateTime>("calendar_date"),
Widgets = w.Field<int>("widgets"),
Hits = h.Field<int>("hits"),
TestSum = w.Field<int>("widgets")+h.Field<int>("hits"),
TestMult = w.Field<int>("widgets") * h.Field<int>("hits")
};
gvWidgets.DataSource = dtWidgets;
gvWidgets.DataBind();
gvHits.DataSource = dtHits;
gvHits.DataBind();
gvLinq.DataSource = hit_rate.ToArray();
gvLinq.DataBind();
}
static DataTable Get_widgets()
{
DataTable widgets = new DataTable();
widgets.Columns.Add("calendar_date", typeof(DateTime));
widgets.Columns.Add("widgets", typeof(int));
widgets.Rows.Add("05/15/2012", 200000);
widgets.Rows.Add("05/16/2012", 210000);
return widgets;
}
static DataTable Get_hits()
{
DataTable hits = new DataTable();
hits.Columns.Add("calendar_date", typeof(DateTime));
hits.Columns.Add("hits", typeof(int));
hits.Rows.Add("05/15/2012", 100000000);
hits.Rows.Add("05/16/2012", 120000000);
return hits;
}
}
Returns:
Date Widgets Hits TestSum TestMult
5/15/2012 12:00:00 AM 200000 100000000 100200000 -1662697472
5/16/2012 12:00:00 AM 210000 120000000 120210000 1426874368

Given the example numbers you have listed underneath the code, it looks like you're getting integer overflow. Try making the field type Int64.

If I where you, I would look at the limit of int in c#... (2147483647)
Try with long (= Int64)
20000000000000
>
2147483647

Related

Rounding to 2 decimal points a DataTable based NuGet package

I'm using ConsoleTableExt to print a tabulated table. I wanted to round a few decimals to 2 points. The NuGet package is using DataTable. Currently, I'm using .ToString(f2"), but I read a post which states that it's a bad practice to do that in a DataTable. Any suggestions?
// Main()
var tableBuilder = ConsoleTableBuilder.From(backtest.ReportResults(backtestResults)).WithFormat(ConsoleTableBuilderFormat.Alternative);
tableBuilder.ExportAndWriteLine();
public static DataTable ReportResults(List<BacktestResult> backtestResults)
{
var table = new DataTable();
table.Columns.Add("Pair", typeof(string));
table.Columns.Add("Trades", typeof(int));
table.Columns.Add("Average Profit %", typeof(string));
table.Columns.Add("Cumulative Profit %", typeof(string));
table.Columns.Add($"Total Profit", typeof(string));
table.Columns.Add($"Total Profit %", typeof(string));
foreach (var pair in _backtestOptions.Pairs)
{
var results = backtestResults.Where(e => e.Pair.Equals(pair)).ToList();
var trades = results.Count;
var profitMean = results.Count > 0 ? results.Average(e => e.ProfitPercentage) : 0;
var profitMeanPercentage = results.Count > 0 ? results.Average(e => e.ProfitPercentage) * 100 : 0;
var profitSum = results.Sum(e => e.ProfitPercentage);
var profitSumPercentage = results.Sum(e => e.ProfitPercentage) * 100;
var profitTotalAbs = results.Sum(e => e.ProfitAbs);
var profitTotal = results.Sum(e => e.ProfitAbs) / 1;
var profitTotalPercentage = results.Sum(e => e.ProfitPercentage) * 100;
table.Rows.Add(pair, trades, profitMeanPercentage.ToString("f2"), profitSumPercentage.ToString("f2"), profitTotalAbs.ToString("f8"),
profitTotalPercentage.ToString("f2"));
}
return table;
}
You can use the function Round of Math for round value. Round(double value, MidpointRounding mode) where the second parameter specification for how to round value if it is midway between two other numbers.
Example:
decimal decimalVal = 123.456M;
Math.Round(decimalVal, 2);

C# Constructing a Dynamic Query From DataTable

Trying to Generate a Dynamic Linq Query, based on DataTable returned to me... The column names in the DataTable will change, but I will know which ones I want to total, and which ones I will want to be grouped.
I can get this to work with loops and writing the output to a variable, then recasting the parts back into a data table, but I'm hoping there is a more elegant way of doing this.
//C#
DataTable dt = new DataTable;
Dt.columns(DynamicData1)
Dt.columns(DynamicData1)
Dt.columns(DynamicCount)
In this case the columns are LastName, FirstName, Age. I want to total ages by LastName,FirstName columns (yes both in the group by). So one of my parameters would specify group by = LastName, FirstName and another TotalBy = Age. The next query may return different column names.
Datarow dr =..
dr[0] = {"Smith","John",10}
dr[1] = {"Smith","John",11}
dr[2] = {"Smith","Sarah",8}
Given these different potential columns names...I'm looking to generate a linq query that creates a generic group by and Total output.
Result:
LastName, FirstName, AgeTotal
Smith, John = 21
Smith, Sarah = 8
If you use a simple converter for Linq you can achieve that easily.
Here a quick data generation i did for the sample :
// create dummy table
var dt = new DataTable();
dt.Columns.Add("LastName", typeof(string));
dt.Columns.Add("FirstName", typeof(string));
dt.Columns.Add("Age", typeof(int));
// action to create easily the records
var addData = new Action<string, string, int>((ln, fn, age) =>
{
var dr = dt.NewRow();
dr["LastName"] = ln;
dr["FirstName"] = fn;
dr["Age"] = age;
dt.Rows.Add(dr);
});
// add 3 datarows records
addData("Smith", "John", 10);
addData("Smith", "John", 11);
addData("Smith", "Sarah", 8);
This is how to use my simple transformation class :
// create a linq version of the table
var lqTable = new LinqTable(dt);
// make the group by query
var groupByNames = lqTable.Rows.GroupBy(row => row["LastName"].ToString() + "-" + row["FirstName"].ToString()).ToList();
// for each group create a brand new linqRow
var linqRows = groupByNames.Select(grp =>
{
//get all items. so we can use first item for last and first name and sum the age easily at the same time
var items = grp.ToList();
// return a new linq row
return new LinqRow()
{
Fields = new List<LinqField>()
{
new LinqField("LastName",items[0]["LastName"].ToString()),
new LinqField("FirstName",items[0]["FirstName"].ToString()),
new LinqField("Age",items.Sum(item => Convert.ToInt32(item["Age"]))),
}
};
}).ToList();
// create new linq Table since it handle the datatable format ad transform it directly
var finalTable = new LinqTable() { Rows = linqRows }.AsDataTable();
And finally here are the custom class that are used
public class LinqTable
{
public LinqTable()
{
}
public LinqTable(DataTable sourceTable)
{
LoadFromTable(sourceTable);
}
public List<LinqRow> Rows = new List<LinqRow>();
public List<string> Columns
{
get
{
var columns = new List<string>();
if (Rows != null && Rows.Count > 0)
{
Rows[0].Fields.ForEach(field => columns.Add(field.Name));
}
return columns;
}
}
public void LoadFromTable(DataTable sourceTable)
{
sourceTable.Rows.Cast<DataRow>().ToList().ForEach(row => Rows.Add(new LinqRow(row)));
}
public DataTable AsDataTable()
{
var dt = new DataTable("data");
if (Rows != null && Rows.Count > 0)
{
Rows[0].Fields.ForEach(field =>
{
dt.Columns.Add(field.Name, field.DataType);
});
Rows.ForEach(row =>
{
var dr = dt.NewRow();
row.Fields.ForEach(field => dr[field.Name] = field.Value);
dt.Rows.Add(dr);
});
}
return dt;
}
}
public class LinqRow
{
public List<LinqField> Fields = new List<LinqField>();
public LinqRow()
{
}
public LinqRow(DataRow sourceRow)
{
sourceRow.Table.Columns.Cast<DataColumn>().ToList().ForEach(col => Fields.Add(new LinqField(col.ColumnName, sourceRow[col], col.DataType)));
}
public object this[int index]
{
get
{
return Fields[index].Value;
}
set
{
Fields[index].Value = value;
}
}
public object this[string name]
{
get
{
return Fields.Find(f => f.Name == name).Value;
}
set
{
var fieldIndex = Fields.FindIndex(f => f.Name == name);
if (fieldIndex >= 0)
{
Fields[fieldIndex].Value = value;
}
}
}
public DataTable AsSingleRowDataTable()
{
var dt = new DataTable("data");
if (Fields != null && Fields.Count > 0)
{
Fields.ForEach(field =>
{
dt.Columns.Add(field.Name, field.DataType);
});
var dr = dt.NewRow();
Fields.ForEach(field => dr[field.Name] = field.Value);
dt.Rows.Add(dr);
}
return dt;
}
}
public class LinqField
{
public Type DataType;
public object Value;
public string Name;
public LinqField(string name, object value, Type dataType)
{
DataType = dataType;
Value = value;
Name = name;
}
public LinqField(string name, object value)
{
DataType = value.GetType();
Value = value;
Name = name;
}
public override string ToString()
{
return Value.ToString();
}
}
I think I'd just use a dictionary:
public Dictionary<string, int> GroupTot(DataTable dt, string[] groupBy, string tot){
var d = new Dictionary<string, int>();
foreach(DataRow ro in dt.Rows){
string key = "";
foreach(string col in groupBy)
key += (string)ro[col] + '\n';
if(!d.ContainsKey(key))
d[key] = 0;
d[key]+= (int)ro[tot];
}
return d;
}
If you want the total on each row, we could get cute and create a column that is an array of one int instead of an int:
public void GroupTot(DataTable dt, string[] groupBy, string tot){
var d = new Dictionary<string, int>();
var dc = dt.Columns.Add("Total_" + tot, typeof(int[]));
foreach(DataRow ro in dt.Rows){
string key = "";
foreach(string col in groupBy)
key += (string)ro[col] + '\n'; //build a grouping key from first and last name
if(!d.ContainsKey(key)) //have we seen this name pair before?
d[key] = new int[1]; //no we haven't, ensure we have a tracker for our total, for this first+last name
d[key][0] += (int)ro[tot]; //add the total
ro[dc] = d[key]; //link the row to the total tracker
}
}
At the end of the operation every row will have an array of int in the "Total_age" column that represents the total for that First+Last name. The reason I used int[] rather than int, is because int is a value type, whereas int[] is a reference. Because as the table is being iterated each row gets assigned a reference to an int[] some of them with the same First+Last name will end up with their int[] references pointing to the same object in memory, so incrementing a later one increments all the earlier ones too (all "John Smith" rows total column holds a refernece to the same int[]. If we'd made the column an int type, then every row would point to a different counter, because every time we say ro[dc] = d[key] it would copy the current value of d[key] int into ro[dc]'s int. Any reference type would do for this trick to work, but value types wouldn't. If you wanted your column to be value type you'd have to iterate the table again, or have two dictionaries, one that maps DataRow -> total and iterate the keys, assigning the totals back into the row

Understanding the concept of a Stub with an example

I recently received some feedback about some tests we wrote a long time ago from a contractor for a legacy application. I am confused with the terminology that he used and I won't get chance to talk to him again for another month (when I will speak to him). Please see the code below:
[TestMethod]
public void Is_Correct_Months()
{
IService service = new Service();
DataTable t1 = service.GetMonths();
DataTable t2 = BuildExpectedTable();
Assert.IsNotNull(t1);
Assert.AreEqual(t1.Rows.Count, t2.Rows.Count);
for (int i = 0; i <= 2; i++)
{
Assert.AreEqual(t1.Rows[i][0], t2.Rows[i][0]);
Assert.AreEqual(t1.Rows[i][1], t2.Rows[i][1]);
}
}
public DataTable BuildExpectedTable()
{
DataTable dt = new DataTable();
//Add column names to datatable
dt.Columns.Add("Column1", typeof(string));
dt.Rows.Add("January");
dt.Rows.Add("May");
dt.Rows.Add("December");
return dt;
}
Question: Is the BuildExpectedTable method a Stub or not in this case? Is it a bad idea to Assert against BuildExpectedTable (as I have done)?
Now see the code below:
var mockCalculator = new Moq.Mock<ICalculator>();
mockChangeCalculator.SetupProperty(client => client.Number1, 400).SetupProperty(client => client.Number2, 200);
This is obviously a mock. Question: Could it ever be construed as a Stub?
Now see the code below:
int[] values = new int[] { 1, 3, 5, 9 };
 
    IEnumerable<int> processed = ProcessInput(values);
 
    int[] expected = new int[] { 1, 9, 25, 81 };
    CollectionAssert.AreEqual(expected, processed);
Question: Is expected a stub in this case?
Update
[TestMethod]
public void Is_Correct_Months()
{
IService service = new Service();
DataTable t1 = service.GetMonths();
DataTable dt2 = new DataTable();
//Add column names to datatable
dt.Columns.Add("Column1", typeof(string));
dt.Rows.Add("January");
dt.Rows.Add("May");
dt.Rows.Add("December");
Assert.IsNotNull(t1);
Assert.AreEqual(t1.Rows.Count, t2.Rows.Count);
for (int i = 0; i <= 2; i++)
{
Assert.AreEqual(t1.Rows[i][0], t2.Rows[i][0]);
Assert.AreEqual(t1.Rows[i][1], t2.Rows[i][1]);
}
}

How to compare two datatables and only check for the rows found

var qry = from r1 in dtCMS.AsEnumerable() //row count - 416
from r2 in dtEcho.AsEnumerable() //row count - 175
where
r1.Field<string>("Name") == r2.Field<string>("Name")
select r2;
DataTable dt1 = dtEcho.AsEnumerable().Except(qry).CopyToDataTable();
Error:
The source contains no DataRows.
How can I modify the code above so that the comparison will only happen for the rows in r2?
I would like two functions:
One, that compare dtEcho to dtCMS, and return anything that is in
dtEcho but not in dtCMS.
Two, that compare dtEcho to dtCMS, and return anything that is in
dtCMS but not in dtEcho.
The code fails if the DataTable I am comparing against has more rows and hence it can't compare.
CopyToDataTable will throw an InvalidOperationException if the source sequence does not contain any DataRow objects:
The source contains no DataRows.
Your code is OK in general and to solve the problem with minimum changes, the only thing that you should change is the way that you create the result.
Instead of using CopyToDataTable put results in the result table using a for loop. To do so, first you should create a clone of your second table.
var qry = from r1 in table1.AsEnumerable() //row count - 416
from r2 in table2.AsEnumerable() //row count - 175
where
r1.Field<string>("Name") == r2.Field<string>("Name")
select r2;
var dt = table2.Clone();
table2.AsEnumerable().Except(qry).ToList().ForEach(x =>
{
dt.Rows.Add(x);
});
this.dataGridView1.DataSource = dt;
If I got right what you want, the code should look like this:
// setup
var dtCMS = new DataTable();
dtCMS.Columns.Add("ID", typeof(int));
dtCMS.Columns.Add("Name", typeof(String));
for (int i = 0; i < 416; i++)
dtCMS.Rows.Add(i, "Name " + i);
var dtEcho = new DataTable();
dtEcho.Columns.Add("ID", typeof(int));
dtEcho.Columns.Add("Name", typeof(String));
for (int i = 400; i < 575; i++)
dtEcho.Rows.Add(i, "Name " + i);
// convert to enumerables
var cmsEnum = dtCMS.AsEnumerable();
var echoEnum = dtEcho.AsEnumerable();
var CmsMinusEcho = cmsEnum.Where(cms => !echoEnum.Any(echo => echo.Field<String>("Name").Equals(cms.Field<String>("Name"))));
var EchoMinusCms = echoEnum.Where(echo => !cmsEnum.Any(cms => cms.Field<String>("Name").Equals(echo.Field<String>("Name"))));
// get DataTable with check
var CmsMinusEchoDt = CmsMinusEcho.Count() > 0 ? CmsMinusEcho.CopyToDataTable() : null;
var EchoMinusCmsDt = EchoMinusCms.Count() > 0 ? EchoMinusCms.CopyToDataTable() : null;

Re-arrange rows of Datatable in runtime

I have multiple rows in a datatable, see a sample below:
Existing Table
Name Date Value Type
ABC(I) 11/11/2013 12.36 I
DEF(I) 11/11/2013 1 I
GHI(I) -do- -do- I
JKL(P) P
MNO(P) P
PQR(D) D
STU(D) -d0- -do- D
Required Table
Name Date Value Type
JKL(P) P
MNO(P) P
PQR(D) D
STU(D) -d0- -do- D
ABC(I) 11/11/2013 12.36 I
DEF(I) 11/11/2013 1 I
GHI(I) -do- -do- I
COndition to use
Sorting should be as per the column Type. Now I need a small change in order of the rows to be shown in the gridview. That is rows of Payment will come first then all Dues and at last all Interests types will come.
What I tried:
Sorting of column but it was not what I need.
Custom Grouping suggested by Tim Schmelter here
Code was:
public DataTable GroupBy(string i_sGroupByColumn, string i_sAggregateColumn, DataTable i_dSourceTable)
{
DataView dv = new DataView(i_dSourceTable);
//getting distinct values for group column
DataTable dtGroup = dv.ToTable(true, new string[] { i_sGroupByColumn });
//adding column for the row count
dtGroup.Columns.Add("Count", typeof(int));
//looping thru distinct values for the group, counting
foreach (DataRow dr in dtGroup.Rows) {
dr["Count"] = i_dSourceTable.Compute("Count(" + i_sAggregateColumn + ")", i_sGroupByColumn + " = '" + dr[i_sGroupByColumn] + "'");
}
//returning grouped/counted result
return dtGroup;
}
I dont know where and what I am lacking/missing. Kindly help.
try linq to order your table:
var query = dtGroup.AsEnumerable()
.OrderBy(c=> c.Field<DateTime?>("Date"))
.ThenByDescending(c=> c.Field<string>("Name"));
DataView dv2 = query.AsDataView();
If I understand correctly you want first sorting on P, D, I and then on date
Dictionary<string, int> sortDictionary = new Dictionary<string, int>();
sortDictionary.Add("P", 1);
sortDictionary.Add("D", 2);
sortDictionary.Add("I", 3);
var q = from row in dtGroup.AsEnumerable()
let type = sortDictionary[row.Field<string>("Name").Substring(4, 1)]
orderby type, row.Field<string>("Name")
select row;
foreach (var r in q)
{
string x = r["Name"].ToString() + r["Date"].ToString();
}

Categories