Getting an error on pivot table using linq - c#

I did pivot table from normal datatable using linq. However sometimes, i get an errors like;
There is no row at position 5(or any number like 7,8,11 etc..)
-----------------------------------------------------------
object reference not set to an instance of an object
What i didn't understand it, Why sometimes it works very well, with the same conditions like 'sample_time' etc. and why sometimes doesn't work.
Here is the my code to swap rows to columns with linq. Also when i debug, I found the part that throws an error.
var dtExist = dtTopAll.AsEnumerable().Where(l => l.Field<DateTime>("SAMPLE_TIME") >= minDate && l.Field<DateTime>("SAMPLE_TIME") <= maxDate).Any();
if (dtExist == true)
{
var dt = (from dr1 in dtTopAll.AsEnumerable()
where dr1.Field<DateTime>("SAMPLE_TIME") >= minDate && dr1.Field<DateTime>("SAMPLE_TIME") <= maxDate
group dr1 by new
{
WAIT_CLASS = dr1.Field<string>("WAIT_CLASS"),
SAMPLE_TIME = dr1.Field<DateTime>("SAMPLE_TIME")
} into g
select new
{
SAMPLE_TIME = g.Key.SAMPLE_TIME,
WAIT_CLASS = g.Key.WAIT_CLASS,
WAITS = g.Sum(z => z.Field<double>("WAITS") / 100)
}).ToDataTable();
var groups = dt.AsEnumerable().GroupBy(x => x.Field<DateTime>("SAMPLE_TIME")).ToList();
foreach (var group in groups) // From this line to end, throws an error .
{
DataRow newRow = dtPivotCustom.Rows.Add();
newRow[0] = group.Key;
foreach (string item in items)
{
newRow[item] = group.Where(x => x.Field<string>("WAIT_CLASS") == item).Select(x => x.Field<double>("WAITS")).Sum();
}
}
}

Related

Get DataRow if column names in a string array have matching values in a string array

I'm trying to get a DataRow from a dtResult datatable if column name in [colName] list has a matching value as [grbByValue] list. my goal in the below code is to get [test1] and [test2] return datarow from dtResult and should be the same as [update] (which is hard coded). but have issue in both test1 & test2. test1 has error and don't know how fix and test2 is returning null.
rule is a DataTable that looks like this:
All the below logic is run for each row of rule.
dtResult is also a DataTable that looks like this:
EDITED CODE
string[] grpby = { "ageband","gender","code"};
List<string> grbByValue = new List<string>() { "1","85+","1","1010"};
DataTable dtResult = new DataTable();
DataColumn dc = dtResult.Columns.Add("id", typeof(int));
dc.AutoIncrement = true;
dc.AutoIncrementSeed = 1;
dc.AutoIncrementStep = 1;
dtResult.Columns.Add("DataSourceID");
dtResult.Columns["DataSourceID"].DefaultValue = "1";
dtResult.Columns.Add("RuleID");
dtResult.Columns.Add("GroupBy0");
dtResult.Columns.Add("GroupBy1");
dtResult.Columns.Add("GroupBy2");
dtResult.Columns.Add("GroupBy3");
dtResult.Columns.Add("GroupBy4");
dtResult.Columns.Add("GroupBy5");
dtResult.Columns.Add("Result", typeof(decimal));
dtResult.Columns["Result"].DefaultValue = 0.00;
var colName = (from a in dtResult.Columns.Cast<DataColumn>()
where a.ColumnName.ToString().StartsWith("GroupBy")
select a.ColumnName).OrderBy(x => x).ToList();
colName.Insert(0, "RuleID");
colName = colName.GetRange(0, grbByValue.Count);
//comment/UNCOMMENT below to test [test1]
//DataRow z = dtResult.NewRow();
//for (int i = 0; i < grbByValue.Count; i++)
//{
// z[colName[i]] = grbByValue[i];
//}
//dtResult.Rows.Add(z.ItemArray);
var distDtResult = dtResult.DefaultView.ToTable(true, colName.ToArray());
bool exist = false;
DataRow update = null;
foreach (DataRow dr in distDtResult.Rows)
{
var row = dr.ItemArray.ToList();
exist = row.SequenceEqual(grbByValue);
if (exist == true)
{
//var test1 = (from t1 in distDtResult.AsEnumerable().Where(r => r.ItemArray == dr.ItemArray)
// join t2 in (from m in dtResult.AsEnumerable()
// select new
// {
// //ideally the below column list will be derived from [colName] dynamically
// RuleID = m.Field<string>("RuleID"),
// GroupBy0 = m.Field<string>("GroupBy0"),
// GroupBy1 = m.Field<string>("GroupBy1"),
// GroupBy2 = m.Field<string>("GroupBy2")
// }) on t1.ItemArray equals t2.ItemArray
// select new
// {
// t2
// }).FirstOrDefault();
update = dtResult.AsEnumerable().Where(r =>
r.Field<int>("id") == 1 &&
r.Field<string>("DataSourceID") == "1" &&
r.Field<string>("RuleID") == "1" &&
r.Field<string>("GroupBy0") == "85+" &&
r.Field<string>("GroupBy1") == "1" &&
r.Field<string>("GroupBy2") == "1010").FirstOrDefault();
break;
}
}
if (exist == false)
{
DataRow a = dtResult.NewRow();
for (int i = 0; i < grbByValue.Count; i++)
{
a[colName[i]] = grbByValue[i];
}
dtResult.Rows.Add(a.ItemArray);
var test2 = dtResult.AsEnumerable().Where(r => r.ItemArray.Equals(a.ItemArray)).FirstOrDefault();
update = dtResult.AsEnumerable().Where(r =>
r.Field<int>("id") == 1 &&
r.Field<string>("DataSourceID") == "1" &&
r.Field<string>("RuleID") == "1" &&
r.Field<string>("GroupBy0") == "85+" &&
r.Field<string>("GroupBy1") == "1" &&
r.Field<string>("GroupBy2") == "1010").FirstOrDefault();
}
This might be a good starting point, at least to better ask questions and move towards an answer.
string[] colName = { "RuleID", "GroupBy0", "GroupBy1", "GroupBy2" };
// "All the below logic is run for each row of rule"
// this goes through each row of the rule DataTable
foreach (DataRow rule in ruleTable.Rows)
{
// This is going to be equivalent to the grpby variable you specified
var groupRules = rule.Field<string>("GroupBy").ToString().Split("|");
// Some sort of mapping may need to go here to go from "ageband" to "GroupBy0", "gender" to "GroupBy1", etc.
foreach(DataRow row in dtResult.Rows)
{
DataTable distDtResult = dtResult.DefaultView.ToTable(true, colName);
var updateTEST = from dr in distDtResult.AsEnumerable()
where dr.Field<string>("RuleID") == rule["RuleID"].ToString()
&& dr.Field<string>("GroupBy0") == row["GroupBy0"].ToString() // ageband
&& dr.Field<string>("GroupBy1") == row["GroupBy1"].ToString() // gender
&& dr.Field<string>("GroupBy2") == row["GroupBy2"].ToString() // code
// more
select dr;
}
}

Search DataGridView for duplicates

I have this code to find duplicate values in DataGridView and mark them with different colors.
var rows = dataGridView1.Rows.OfType<DataGridViewRow>().Reverse().Skip(1);
var dupRos = rows.GroupBy(r => r.Cells["Date"].Value.ToString()).Where(g =>
g.Count() > 1).SelectMany(r => r.ToList());
foreach (var r in dupRos)
{
r.DefaultCellStyle.BackColor = Color.Pink;
}
foreach (var r in rows.Except(dupRos))
{
r.DefaultCellStyle.BackColor = Color.Cyan;
}
The code works fine.
I have changed the code so it will write in the second column the word Unique or Duplicate and a counter number for the duplicate cells.
int counter = 1;
var rows = dataGridView1.Rows.OfType<DataGridViewRow>().Reverse().Skip(1);
//ignore the last empty line
var dupRos = rows.GroupBy(r => r.Cells["Date"].Value.ToString()).Where(g =>
g.Count() > 1).SelectMany(r => r.ToList());
foreach (var r in dupRos)
{
r.DefaultCellStyle.BackColor = Color.Pink;
r.Cells["Time"].Value = "Dup" + counter;
counter++;
}
foreach (var r in rows.Except(dupRos))
{
r.DefaultCellStyle.BackColor = Color.Cyan;
r.Cells["Time"].Value = "Unick";
}
My problem is that the counter continues to count for all the duplicate groups and not reset itself every time its start with a different group of duplicate values.
How can I fix it?
Please try this one. If it works you can replace string type for lastDupRowDate with actual date type.
int counter = 1;
var rows = dataGridView1.Rows.OfType<DataGridViewRow>().Reverse().Skip(1);
//ignore the last empty line
var dupRos = rows.GroupBy(r => r.Cells["Date"].Value.ToString()).Where(g =>
g.Count() > 1).SelectMany(r => r.ToList());
string lastDupRowDate = null;
foreach (var r in dupRos)
{
r.DefaultCellStyle.BackColor = Color.Pink;
counter = lastDupRowDate == r.Cells["Date"].Value.ToString() ? count + 1 : 1;
lastDupRowDate = r.Cells["Date"].Value.ToString();
r.Cells["Time"].Value = "Dup" + counter;
}
foreach (var r in rows.Except(dupRos))
{
r.DefaultCellStyle.BackColor = Color.Cyan;
r.Cells["Time"].Value = "Unick";
}

Linq group by multiple columns, one of which is selectable

I have a table consisting of
MyDate(DATETIME),
Type nvarchar(255),
PropertyAId(int),
PropertyBId(int),
Data1 (float),
Data2 (float),
...
Data50 (float)
I want to return a table grouped by
MyDate,Type, and one of PropertyAId,PropertyBId (depending on user selection)
and sum all of the Data columns.I would prefer summing and selecting Data columns based on prefix or data type, and not having to repeat the same line 50 times
What I have so far is a bit ugly
DataTable dt2 = dt.Clone();
var grouped = dt.AsEnumerable().
GroupBy(r => new
{
MyDate = r.Field<DateTime>("MyDate"),
PropertyAId = selectedGroupingColumn == "PropertyAId" ? r.Field<int?>("PropertyAId") : null,
PropertyBId = selectedGroupingColumn == "PropertyBId" ? r.Field<int?>("PropertyBId") : null,
Type = r.Field<string>("Type")
});
foreach (var group in grouped)
{
DataRow row = dt2.NewRow();
foreach (var col in dt.Columns.Cast<DataColumn>())
{
if (col.ColumnName.StartsWith("Data"))
{
double sum = 0;
if (col.DataType == typeof(double))
sum = group.Sum(r => r.Field<double>(col));
row.SetField(col.ColumnName, sum);
}
else
row[col.ColumnName] = group.First()[col];
}
dt2.Rows.Add(row);
}
//dt2.Columns.Remove unselected property and return table
Your logic seems (mostly) sound, but I would suggest just using selectedGroupingColumn directly to get the value to group by, and only set the sum for "Data" columns of type double:
DataTable dt2 = dt.Clone();
var grouped = dt.AsEnumerable().
GroupBy(r => new {
MyDate = r.Field<DateTime>("MyDate"),
GroupingColumnValue = r.Field<int?>(selectedGroupingColumn),
Type = r.Field<string>("Type")
});
foreach (var group in grouped) {
DataRow row = dt2.NewRow();
foreach (var col in dt.Columns.Cast<DataColumn>())
if (col.ColumnName.StartsWith("Data") && col.DataType == typeof(double))
row.SetField(col.ColumnName, group.Sum(r => r.Field<double>(col)));
else
row[col.ColumnName] = group.First()[col];
dt2.Rows.Add(row);
}

Extract the sum of two things from datatable

I have a table in a SQL Server database with many columns but the important columns are LoggedState and InteractionType.
I need to find the number of break agents and the number of idle agents.
What I have tried
SqlCommand GraphCmd = new SqlCommand("getAgentStatues", Graphsqlcon);
SqlParameter tdate = new SqlParameter();
GraphCmd.CommandType = CommandType.StoredProcedure; ;
SqlDataAdapter DAGraph = new SqlDataAdapter(GraphCmd);
DataSet DSGraph = new DataSet();
DSGraph.Clear();
DAGraph.Fill(DSGraph);
DataTable DTgraph = new DataTable();
DTgraph = DSGraph.Tables[0];
int numberOfBreakAgents = 0;
int numberOfIdelAgents = 0;
foreach (DataRow row in DTgraph.Rows)
{
String LoggedState = row["LoggedState"].ToString().Trim().ToLower();
String InteractionType = row["InteractionType"].ToString();
if (LoggedState == "break")
{
numberOfBreakAgents++;
}
else if ((LoggedState == "activo") && (row["InteractionType"] == DBNull.Value))
{
numberOfIdelAgents++;
}
}
it works perfectly, but I am asking if there is a way (like grouping) to avoid the foreach statement
You could use the Group function from Linq:
var loggedStateGroups = dt.AsEnumerable().GroupBy(d => d["LoggedState"].ToString(), (group, row) => new
{
LoggedState = group,
AllCount = row.Count(),
NullCount = row.Where(r => r["InteractionType"] == DBNull.Value).Count()
});
That will group by the LoggedState with a count for each matching row (AllCount) and a count for rows where the InteractionType is DBNull.Value (NullCount).
We can then select the counts we are after by doing:
int numberOfBreakAgents = loggedStateGroups.Where(y => y.LoggedState == "break").First().AllCount;
int numberOfIdelAgents = loggedStateGroups.Where(y => y.LoggedState == "activo").First().NullCount;
Note I'm only using First assuming you will always have results. If you won't always have results you should use FirstOrDefault and perform a null check.
You could filter before using the Group by adding the following Where depending on your data.
.Where(r => r["LoggedState"].ToString() == "break" || r["LoggedState"].ToString() == "activo")
I've tested this with the following setup:
DataTable dt = new DataTable();
dt.Columns.Add("LoggedState");
dt.Columns.Add("InteractionType");
dt.Rows.Add("break", "inter1");
dt.Rows.Add("activo", DBNull.Value);
dt.Rows.Add("break", "inter1");
dt.Rows.Add("break", "inter2");
dt.Rows.Add("activo", "inter2");
And I get 3 and 1 for the numberOfBreakAgents and numberOfIdelAgents respectively.
Edit for using FirstOrDefault:
If you'd like to perform the null check as mentioned above you can replace the two int declaration lines above with:
var breakAgents = loggedStateGroups.Where(y => y.LoggedState == "break").FirstOrDefault();
var idelAgents = loggedStateGroups.Where(y => y.LoggedState == "activo").FirstOrDefault();
int numberOfBreakAgents = breakAgents != null ? breakAgents.AllCount : 0;
int numberOfIdelAgents = idelAgents != null ? idelAgents.NullCount : 0;
This is taking the first group that has the LoggedState of "break" or null if there isn't one. It then assigns numberOfBreakAgents the AllCount property if the group is not null or 0 if it is.
A similar thing is done for numberOfIdelAgents except we filter for the "activo" group and use the NullCount property as we aren't interested in all rows we are only interested in those where the InteractionType was DBNull.Value which we've captured in the NullCount property.
The null check is necessary if the result set will ever contain zero rows with the LoggedState of "activo" or zero rows with the LoggedState of "break". In that instance the .First() will return null and accessing AllCount or NullCount from that will result in a "Sequence contains no elements" exception.
Using the following DataTable definition will highlight the difference as it causes an exception for numberOfBreakAgents using First() but correctly returns 0 when using FirstOrDefault.
DataTable dt = new DataTable();
dt.Columns.Add("LoggedState");
dt.Columns.Add("InteractionType");
dt.Rows.Add("activo", "inter1");
dt.Rows.Add("activo", DBNull.Value);
dt.Rows.Add("activo", "inter1");
dt.Rows.Add("activo", "inter2");
dt.Rows.Add("activo", "inter2");
Could you not do
var breakAgents = from row in DTgraph.AsEnumerable()
where row["LoggedState"].ToString().Trim().ToLower() == "break"
select row;
var breakAgentsCount = breakAgents.Count();
and
var idleAgents = from row in DTgraph.AsEnumerable()
where row["LoggedState"].ToString().Trim().ToLower() == "activo"
&& row["InteractionType"] == DBNull.Value
select row;
var idleAgentsCount = idleAgents.Count();
Using the Count-function that LINQ provides us, the following solution should work:
// Cast the rows to a collection of DataRows.
IEnumerable<DataRow> collection = DTgraph.Rows.Cast<DataRow>();
// Get the number of Break Agents.
int numberOfBreakAgents = collection.Count(row => row["LoggedState"].ToString().Trim().ToLower() == "break");
// Get the number of Idel Agents.
int numberOfIdelAgents = collection.Count(row => row["LoggedState"].ToString().Trim().ToLower() == "activo" && row["InteractionType"] == DBNull.Value);
The cast is used to allow the use of LINQ on the DataRow-collection.
Another option would be to cast the DataRow-collection to a List of type DataRow. Then using a ForEach (also LINQ), to determine the agent-type:
List<DataRow> collection = DTgraph.Rows.Cast<DataRow>().ToList();
collection.ForEach(row =>
{
if (row["LoggedState"].ToString().Trim().ToLower() == "break")
numberOfBreakAgents++;
else if (row["LoggedState"].ToString().Trim().ToLower() == "activo" && row["InteractionType"] == DBNull.Value)
numberOfIdelAgents++;
});
Above example is very much the same to your example, but written a bit shorter and without the use of two strings (LoggedState and InteractionType).
You can execute sql query like this:
select
sum(case when LoggedState = "break" then 1 else 0 end) break_count,
sum(case when LoggedState = "activo" and InteractionType is null then 1 else 0 end) active_count
from table_name

Update two columns in a DataTable using LINQ

I want to update two columns of DataTable in a single line using LINQ query. Currently I am using following two lines to do the same:
oldSP.Select(string.Format("[itemGuid] = '{0}'", itemGuid)).ToList<DataRow>().ForEach(r => r["startdate"] = stDate);
oldSP.Select(string.Format("[itemGuid] = '{0}'", itemGuid)).ToList<DataRow>().ForEach(r => r["enddate"] = enDate);
How can I do this in one line, using one Select?
You can do it in one 'line', just pass appropriate action delegate to ForEach method:
oldSP.Select(string.Format("[itemGuid] = '{0}'", itemGuid))
.ToList<DataRow>()
.ForEach(r => {
r["startdate"] = stDate;
r["enddate"] = enDate;
});
Also you can use LINQ to DataSet (looks more readable to me, than one-liner):
var rowsToUpdate =
oldSP.AsEnumerable().Where(r => r.Field<string>("itemGuid") == itemGuid);
foreach(var row in rowsToUpdate)
{
row.SetField("startdate", stDate);
row.SetField("enddate", enDate);
}
Use curly bracers to do two on more operations:
oldSP.Select(string.Format("[itemGuid] = '{0}'", itemGuid))
.ToList<DataRow>()
.ForEach(r => { r["enddate"] = enDate); r["startdate"] = stDate; });
But for code readability I would use old-fashioned foreach loop.
Try this :
oldSP.Select(string.Format("[itemGuid] = '{0}'", itemGuid)).ToList<DataRow>()
.ForEach(r => { r["startdate"] = stDate; r["enddate"] = enDate; });
I didn't like any of the examples I saw on the web, so here's my example
DataTable dt = new DataTable();
dt.Columns.Add("Year");
dt.Columns.Add("Month");
dt.Columns.Add("Views");
for (int year = 2011; year < 2015; year++)
{
for (int month = 1; month < 13; month++)
{
DataRow newRow = dt.NewRow();
newRow[0] = year;
newRow[1] = month;
newRow[2] = 0;
dt.Rows.Add(newRow);
}
}
dataGridView1.DataSource = dt;
//if using Lambda
//var test = dt.AsEnumerable().Where(x => x.Field<string>("Year") == "2013" && x.Field<string>("Month") == "2").ToList();
var test = (from x in dt.AsEnumerable()
where x.Field<string>("Year") == "2013"
where x.Field<string>("Month") == "2"
select x).ToList();
test[0][0] = "2015";
dt.AcceptChanges();
//if writing to sql use dt.SubmitChanges() instead

Categories