I am reading an excel using .NET and need to perform certain checks on that excel.
The excel example is as below:
ColumnA ColumnB ColumnC
1001 Null 10
1001 W101 5
1001 W102 4
1001 W103 2
1002 Null 12
1002 W104 5
1002 W105 3
1003 W106 5
1003 W107 2
The requirement is as follows:
If there is a Null entry in ColumnB, then I need to compare the values under ColumnC,i.e. the sum of the values(5,4,2 as against the values W101, W102, W103) should be equal to 10(value against Null), if not then write an error in a log file.
My problem is that the there can be n values in ColumnC, how to loop against it.
In the above excel, the values corresponding to 1001 (ColumnA) are 4 in number whereas, the values corresponding to 1002, 1003 (ColumnA) are 3 and 2 in number respectively.
How to write a generic logic for it, I am not able to understand.
Below is the code which I have written, but it will work only when I have 4 values corresponding to 1001 in ColumnA.
FileStream file = File.Open(path, FileMode.Open, FileAccess.Read);
IExcelDataReader obj = ExcelReaderFactory.CreateOpenXmlReader(file);//uses a 3rd party library
obj.IsFirstRowAsColumnNames = true;
DataSet ds = obj.AsDataSet();
DataTable dt = ds.Tables[0];
for (int i = 0; i < dt.Rows.Count; i++)
{
if (dt.Rows[i][1].ToString() == "Null")
{
double a = (double)dt.Rows[i][2];
double x = (double)dt.Rows[i + 1][2];
double y = (double)dt.Rows[i + 2][2];
double z = (double)dt.Rows[i + 3][2];
if (a != (x+ y + z))
{
Response.Write("Mismatch in row: " + dt.Rows[i + 1][1]);
Response.Write("<br/>");
}
}
}
Also, I have used a 3rd party library to read the excel and convert it into a DataSet.
The following query will get the results you need:
Test data used:
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("A", typeof(string)));
dt.Columns.Add(new DataColumn("B", typeof(string)));
dt.Columns.Add(new DataColumn("C", typeof(int)));
dt.Rows.Add(new object[] { "1001", "Null", 10 });
dt.Rows.Add(new object[] { "1001", "W101", 5 });
dt.Rows.Add(new object[] { "1001", "W102", 4 });
dt.Rows.Add(new object[] { "1001", "W103", 1 });
dt.Rows.Add(new object[] { "1002", "Null", 12 });
dt.Rows.Add(new object[] { "1002", "W104", 5 });
dt.Rows.Add(new object[] { "1002", "W105", 3 });
dt.Rows.Add(new object[] { "1003", "W106", 5 });
dt.Rows.Add(new object[] { "1003", "W107", 2 });
LINQ:
var result =
dt
.AsEnumerable()
// Group by the A-column.
.GroupBy(r => (string)r["A"])
// Get only those groups where the first item in the B-column is 'Null':
.Where(g => g.FirstOrDefault(r => (string)r["B"] == "Null") != null)
// Get only those groups where the sum of the items of the C-column
// after the first one is different from the first item:
.Where(g => (int)g.First()["C"] != g.Skip(1).Sum(r => (int)r["C"]))
.ToList();
The result will contain rows groupped by the A-column that have invalid sums. If you are not interested in the actaul rows but only if they are valid then put .Any() in place of .ToList().
If you don't use a "Null" strings but a real null like:
dt.Rows.Add(new object[] { "1001", null, 10 });
you'll need to exchange the first Where to:
.Where(g => g.FirstOrDefault(r => r.Field<string>("B") == null) != null)
Related
My DataTable looks something like this:
id1
id2
id3
1
2
1
1
2
3
1
2
1
1
1
3
3
3
2
3
2
I want my program to remove all "duplicate combinations", so the table would look like this:
id1
id2
id3
1
2
1
1
1
3
3
3
2
3
2
Basically I want it to check if other rows are already contained within other rows, while treating the empty cells as a cell that could have any value at all.
I have tried working with the DataTable.DefaultView.ToTable(true) distinct method and adding column names as parameters but couldn't get it to do exactly what i want.
Also the size of the table is dynamic - There can be an infinite amount of columns.
I finally found a good solution. The distinct doesn't work (using IComarable because it leaves the row with the null instead of the row with values. I had to add a sort so the row with null is before the one with values. Then I delete the first row leaving the second row with values. Also found I had to add dt.AcceptChanges(); because the row did get deleted.
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("id1", typeof(int));
dt.Columns.Add("id2", typeof(int));
dt.Columns.Add("id3", typeof(int));
dt.Columns["id1"].AllowDBNull = true;
dt.Columns["id2"].AllowDBNull = true;
dt.Columns["id3"].AllowDBNull = true;
dt.Rows.Add(new object[] { 1});
dt.Rows.Add(new object[] { 2 });
dt.Rows.Add(new object[] { 1, 1 });
dt.Rows.Add(new object[] { 2, 3 });
dt.Rows.Add(new object[] { 1, 2, 1 });
dt.Rows.Add(new object[] { 1, 1, 3 });
dt.Rows.Add(new object[] { 3, 3 });
dt.Rows.Add(new object[] { 2, 3, 2 });
dt = RemoveDuplicates(dt);
}
static DataTable RemoveDuplicates(DataTable dt)
{
for (int col = dt.Columns.Count - 1; col >= 0; col--)
{
dt = dt.AsEnumerable().OrderBy(x => x.Field<int?>(col)).CopyToDataTable(); ;
}
for (int i = dt.Rows.Count - 2; i >= 0; i--)
{
bool delete = true;
for (int col = 0; col <= dt.Columns.Count - 1; col++ )
{
if (dt.Rows[i][col] == DBNull.Value)
continue;
if((int)dt.Rows[i][col] != (int)dt.Rows[i + 1][col])
{
delete = false;
break;
}
}
if (delete)
{
dt.Rows[i].Delete();
dt.AcceptChanges();
}
}
return dt;
}
So here is the issue I am facing. I have a one to many list relationship.
Here are examples of how the lists would look.
The objects in list 1 are dynamic and have a few properties with the most important one being "id".
The second list has a defined object looking something like this "id" "value desc" "actual value".
The second list can have many rows belonging to the first list. The "value desc" is the property name and the "actual value".
I need to combine these lists into something that looks like this. An object with all list 1 properties and all corresponding rows in the second list. If the second list had 3 items belonging to an item in list 1 then the new object should have all properties of list 1 along with all the rows gathered from list 2 in a flat structure like way.
Data examples
Table 1:
id
name
1
bob
2
joe
Table 2
id
propname
value
1
length
2
1
age
12
1
haircolor
blue
2
length
5
2
age
90
2
haircolor
red
How I want the data to look
id
name
length
haircolor
age
1
bob
2
blue
12
2
joe
5
red
90
Currently, I have this working.
public IEnumerable<dynamic> test(List<dynamic> data, List<modal>
dataset)//closest
{
var query = (from a in data
join b in dataset
on a.id equals b.id into t
select new {
a,
t
});
return query;
}
However, the result is an object with properties of list 1 and then a property on that object that is an array of items found in list 2. I need these items to not be in an array and be property names with values on the new created object.
I hope my explanation was clear enough!
Use a pivolt table :
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 dt1 = new DataTable();
dt1.Columns.Add("id", typeof(int));
dt1.Columns.Add("name", typeof(string));
dt1.Rows.Add(new object[] { 1, "bob" });
dt1.Rows.Add(new object[] { 2, "bob" });
DataTable dt2 = new DataTable();
dt2.Columns.Add("id", typeof(int));
dt2.Columns.Add("propname", typeof(string));
dt2.Columns.Add("value", typeof(object));
dt2.Rows.Add(new object[] { 1, "length", 2 });
dt2.Rows.Add(new object[] { 1, "age", 12 });
dt2.Rows.Add(new object[] { 1, "haircolor", "blue"});
dt2.Rows.Add(new object[] { 2, "length", 5 });
dt2.Rows.Add(new object[] { 2, "age", 90 });
dt2.Rows.Add(new object[] { 2, "haircolor", "red" });
DataTable pivot = new DataTable();
string[] properties = dt2.AsEnumerable().Select(x => x.Field<string>("propname")).Distinct().OrderBy(x => x).ToArray();
pivot.Columns.Add("id", typeof(int));
pivot.Columns.Add("name", typeof(string));
DataColumn[] columns = properties.Select(x => new DataColumn(x, typeof(object))).ToArray();
pivot.Columns.AddRange(columns);
var joins = from id1 in dt1.AsEnumerable()
join id2 in dt2.AsEnumerable() on id1.Field<int>("id") equals id2.Field<int>("id")
select new { id = id1.Field<int>("id"), name = id1.Field<string>("name"), id2 = id2 };
var groups = joins.GroupBy(x => x.id);
foreach (var group in groups)
{
DataRow newRow = pivot.Rows.Add();
newRow["id"] = group.Key;
newRow["name"] = group.First().name;
foreach (var row in group)
{
newRow[row.id2.Field<string>("propname")] = row.id2.Field<object>("value");
}
}
}
}
}
I have 2 database tables, joined on a FK (ResponseHeader) examples below (date is 6 October in the example)
ResponseHeader
ResponseHeaderId DateTime
0e24cf96-81eb-2122-7e4a-0d200474692f 06/10/2018 11:15:59
ResponseData
ResponseDataId ResponseHeaderId Response
41c831f1-0adc-2bd5-053e-00406fa526b6 0e24cf96-81eb-2122-7e4a-0d200474692f 1
78967068-82a6-4098-ba35-03211a923f46 0e24cf96-81eb-2122-7e4a-0d200474692f 2
854bc8a6-5877-a6fb-9072-00e358323350 0e24cf96-81eb-2122-7e4a-0d200474692f 2
fe2a667d-ca0e-49a6-b330-f4d4232bfe89 0e24cf96-81eb-2122-7e4a-0d200474692f 3
30f0270e-3e69-3408-7add-02a85f4b9aeb 0e24cf96-81eb-2122-7e4a-0d200474692f 1
There are multiple ResponseHeaders and each response header has 5 rows of data each)
The responses for each group of response data needs to be added up (example = 9)
What I want to achieve though is a table like this where everything is totalled by Month for the given year set.
2018
Month Responses Sum
October 1 9
But, as there are multiple rows for each Responseheader, what I eventually want is this:
2018
Month Responses Sum
October 185 234
September 564 985
...
2017
Month Responses Sum
January 54 123
...
I've got linq for summing by month, for example:
var total = 0;
var filteredResponses = Model.SurveyResponseHeader.Where(x => x.StartDate > DateTime.Today.LastMonth() && x.StartDate > DateTime.Today.AddMonths(1).LastMonth()).ToList();
foreach (var response in filteredResponses)
{
var responseTotal = (response.SurveyResponses.Sum(x => x.Response));
total += responseTotal;
}
#total
Given all this, how do I?
Loop through the data by month, summing each response set of data for the month
Group the data by month and year
I have no problem with some basic linq and C# for querying data by dates and summing, but grouping and summing like this is a bit beyond me
Try following code :
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable dt1 = new DataTable();
dt1.Columns.Add("ResponseHeaderId", typeof (string));
dt1.Columns.Add("DateTime", typeof (DateTime));
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d200474692f", DateTime.Parse("06/10/2018 11:15:59") });
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d2004746930", DateTime.Parse("05/10/2018 11:15:59") });
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d2004746931", DateTime.Parse("04/10/2018 11:15:59") });
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d2004746932", DateTime.Parse("03/10/2017 11:15:59") });
dt1.Rows.Add(new object[] { "0e24cf96-81eb-2122-7e4a-0d2004746933", DateTime.Parse("02/10/2017 11:15:59") });
DataTable dt2 = new DataTable();
dt2.Columns.Add("ResponseDataId", typeof (string));
dt2.Columns.Add("ResponseHeaderId", typeof (string));
dt2.Columns.Add("Response", typeof (int));
dt2.Rows.Add(new object[] { "41c831f1-0adc-2bd5-053e-00406fa526b6","0e24cf96-81eb-2122-7e4a-0d200474692f", 1 });
dt2.Rows.Add(new object[] { "78967068-82a6-4098-ba35-03211a923f46","0e24cf96-81eb-2122-7e4a-0d200474692f", 2 });
dt2.Rows.Add(new object[] { "854bc8a6-5877-a6fb-9072-00e358323350","0e24cf96-81eb-2122-7e4a-0d2004746930", 2 });
dt2.Rows.Add(new object[] { "fe2a667d-ca0e-49a6-b330-f4d4232bfe89","0e24cf96-81eb-2122-7e4a-0d2004746931", 3 });
dt2.Rows.Add(new object[] { "30f0270e-3e69-3408-7add-02a85f4b9aeb","0e24cf96-81eb-2122-7e4a-0d2004746932", 1 });
dt2.Rows.Add(new object[] { "30f0270e-3e69-3408-7add-02a85f4b9aeb","0e24cf96-81eb-2122-7e4a-0d2004746933", 1 });
var join = (from d1 in dt1.AsEnumerable()
join d2 in dt2.AsEnumerable() on d1.Field<string>("ResponseHeaderId") equals d2.Field<string>("ResponseHeaderId")
select new { d1 = d1, d2 = d2 })
.OrderByDescending(x => x.d1.Field<DateTime>("DateTime"))
.GroupBy(x => new { year = x.d1.Field<DateTime>("DateTime").Year, month = x.d1.Field<DateTime>("DateTime").ToString("MMMM") })
.Select(x => new { year = x.Key.year, month = x.Key.month, total = x.Select(y => y.d2.Field<int>("Response")).Sum()})
.ToList();
}
}
}
I have a datatable in which multiple data is populated. Need to check a column which contains same values in different rows and add up all the similar rows into one in the same datatable.
for example
id pid pname pAmountex vat vat
1 4 t1 123 2
2 3 t2 45 3
3 4 t3 56 7
4 3 t4 23 8
in the above table,pid column has similar values 4& 3 .i need to sum up the pamountex,vat column of 1st and 3 rd rows for pid 4 and sum up 2 and 3 rows for pid 3.
Here is an example with LINQ:
DataTable dt = new DataTable();
dt.Columns.Add("id");
dt.Columns.Add("pid");
dt.Columns.Add("pname");
dt.Columns.Add("pamountex",typeof(int));
dt.Columns.Add("vat",typeof(int));
dt.Rows.Add( new object[] { 1, 4, "t1", 123, 2 } );
dt.Rows.Add( new object[] { 2, 3, "t2", 45, 3 } );
dt.Rows.Add( new object[] { 3, 4, "t3", 56, 7 } );
dt.Rows.Add( new object[] { 4, 3, "t4", 23, 8 } );
dt.AsEnumerable()
.GroupBy(r => r.Field<string>("pid") )
.Where (r => r.Count() > 1)
.Select (gr =>
new { id = 0,
pid = gr.Key,
pname = "???",
pamountex = gr.Sum (g => g.Field<int>("pamountex")),
vat = gr.Sum (g => g.Field<int>("vat"))
})
.ToList()
.ForEach( r => dt.Rows.Add(
new object[] {
r.id,
r.pid,
r.pname,
r.pamountex,
r.vat } ));
You did not specify what values are required in id and pname columns for rows that keep sum so I added some defaults(id = 0 and pname = "???") - change it according to your needs.
I assumed that rows with source data should stay in the table.
I'm digging in in my Microsoft Visual Studio Documentation and I found this article under C# Reference (ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_csref/html/df01e266-5781-4aaa-80c4-67cf28ea093f.htm), It's about Interface Interface. Here's the example code:
class SelectSample1
{
static void Main()
{
//Create the data source
List<int> Scores = new List<int>() { 97, 92, 81, 60 };
// Create the query.
IEnumerable<int> queryHighScores =
from score in Scores
where score > 80
select score;
// Execute the query.
foreach (int i in queryHighScores)
{
Console.Write(i + " ");
}
}
}
//Output: 97 92 81
Instead of a List, is it also possible to query a DataTable and set the result of the query as the DataSource of a DataGridView?
If yes, suppose I have this structure:
Fruit | CategoryID
---------------------------------------
Lemon | 1
Orange | 1
Apple | 2
Pear | 2
Can anyone please give me an example (if possible, for a beginner's approach.. :). What I want is to display the result in a DataGridView. Display all fruits where its CategoryID is equal to 1. Please help,
Thanks in advance guys.
You need to use AsEnumerable() extension of Databe to select the rows and bind to DataGridView like this:
DataTable table = new DataTable();
table.Columns.Add("Fruit");
table.Columns.Add("ID", typeof(int));
table.Rows.Add(new object[] { "Lemon", 1 });
table.Rows.Add(new object[] { "Orange", 1 });
table.Rows.Add(new object[] { "Apple", 2 });
table.Rows.Add(new object[] { "Pear", 2 });
BindingSource bs = new BindingSource();
bs.DataSource = from row in table.AsEnumerable()
where row.Field<int>("ID") == 1
select new {Fruit = row.Field<string>("Fruit"), ID = row.Field<int>("ID")};
dataGridView1.DataSource = bs;
Try this
var results = from row in dataTable.AsEnumerable()
where row.Field<int>("CategoryID") == 1
select row ;
and you can bind the result row easily to your control.