c# calculate distinct value in datatable - c#

what i have in my datatable
Resource
1 // 1 represent normal
1
2 // 2 represent sql
2
3 // 3 css
4 // 4 unicode
4
4
how can i perform calculation so that i could display the value in a textbox
normal 2
sql 2
css 1
unicode 3
total hits 9
what ive tried so far
var result = my_datatable.AsEnumerable().Sum(x => Convert.ToInt32(x["Resource"]));
string result2 = result.ToString();
totalTxtBox.Text = result2;
but it calculate the whole column (output is: 24 instead of 9)

Use the following example-
int[] res = { 1,1,2,2,3,4,4,4};
var words = res.AsEnumerable().GroupBy(x => x);
foreach (var x in words)
{
Console.WriteLine(x.Key+"-"+x.Count());
}
It will print output as-
1-2
2-2
3-1
4-3

You can try to use Distinct()
var result = my_datatable.AsEnumerable().Distinct().Sum(x => Convert.ToInt32(x["Resource"]));
Distinct() Returns distinct elements from a sequence by using the default equality comparer to compare values.
for more info: https://msdn.microsoft.com/en-us/library/bb348436(v=vs.110).aspx
You can try to use this example as your reference. Firstly, I get the Distinct values from the datatable and converted it to list and then use the sum function.
Updated Answer:
DataTable my_datatable = new DataTable();
my_datatable.Columns.Add("Value", typeof(int));
my_datatable.Columns.Add("Type", typeof(string));
my_datatable.Rows.Add(1, "Normal");
my_datatable.Rows.Add(1, "Normal");
my_datatable.Rows.Add(2, "SQL");
my_datatable.Rows.Add(2, "SQL");
my_datatable.Rows.Add(3, "CSS");
my_datatable.Rows.Add(4, "UNICODE");
my_datatable.Rows.Add(4, "UNICODE");
my_datatable.Rows.Add(4, "UNICODE");
var distinctIds = my_datatable.AsEnumerable()
.Select(s => new {
value = s.Field<int>("value"),
})
.Distinct().ToList();
int total = distinctIds.Sum(item => item.value);

I figured it out by myself
use this line (linq) in order to filter out which value you want
int normalcount = my_datatable
.AsEnumerable()
.Where(r => r.Field<string>("Resource") == "1")
.Count();
try to change this line to filter out which value, according to your column value
.Where(r => r.Field<string>("Resource") == "2")
.Count();

Related

Retrieving data from sql to datagridview using LinqToSql

I have a datatable in sql and a datagridview in winform. datatable holds measurement results from a mould with a MouldID. For every measurement 50 lines of results are logged to table. To track measurement count for same mould, i also have MeasId column which incremented by 1 for every measurement input. Please see picture for table view.
What i need to do, retrieve only the rows with choosen MouldID (from a combobox) with last MeasID.
I tried following codes but i couldn't figure out how to group this rows with MeasId.
using (LinqDataClassesDataContext dataContext = new
LinqDataClassesDataContext())
{
// attemp 1
var query=dataContext.SupplierVals
.Where(m=>m.MouldID==comboBMouldID.SelectedValue.ToString())
.OrderByDescending(m => m.MeasId).FirstOrDefault();
// attemp 2
var query=dataContext.SupplierVals
.Where(mr=>mr.MouldID==comboBMouldID.SelectedValue.ToString())
.OrderByDescending(mr => mr.MeasId).Select();
// attemp 3
var query = (from x in dataContext.SupplierVals
where x.MouldID == comboBMouldID.SelectedValue.ToString()
select x).First();
// attemp 4
var query = from x in dataContext.SupplierVals
where x.MouldID == comboBMouldID.SelectedValue.ToString()
group x by x.MeasId into grp
select grp.OrderByDescending(x => x.MeasId).First();
daGridUnused.AutoGenerateColumns = false;
daGridUnused.Columns["unusedShowDist"].DataPropertyName = "Distnc";
daGridUnused.Columns["unusedShowAper"].DataPropertyName = "Apert";
daGridUnused.Columns["unusedShowTap"].DataPropertyName = "Taper";
daGridUnused.DataSource = query;
}
None of these queries return what i need from datatable.
What am i doing wrong?
It seems that you were almost there. You simply need to filter also by the Max value and order by the ValueId:
string mouldId = comboBMouldID.SelectedValue.ToString();
int max = dataContext.SupplierVals
.Where(m=>m.MouldID == mouldId)
.Max(m => m.MeasId);
var query=dataContext.SupplierVals
.Where(m=>m.MouldID == mouldId && m.MeasId == max).ToList();
disclaimer: this query can surely be optimized, I am working on a better solution

LinQ returning no datarow error

In below code, Hours is passed with selected values from the UI as below
Hours = "10.1, 11.2";
If there is no value from UI, these will contain as below.
Hours = "";
Below code is to slipt them & get the value from Database. This gives error if the values are having "".
Error at q.CopyToDataTable() The source contains no DataRows.
public void Populate_Gridview(string Hours)
{
string[] selectedHours = Hours.Split(',');
var q = from a in dt.AsEnumerable()
where selectedHours.Contains(a.Field<double?>("Hours").ToString())
select a;
GridView1.DataSource = q.CopyToDataTable();
GridView1.DataBind();
}
If I have Double array, I cannot slipt them if the input is not having any value i.e. "". I get error Input string was not in a correct format.
Double[] selectedHours = Array.ConvertAll(All.Split(','), Double.Parse);
Please guide on how I can solve this issue.
Try this
var q = from a in dt.AsEnumerable()
where (selectedHours.Contains(a.Field<double?>("Hours").ToString())
|| selectedHours.Length==0)
select a;
First, i would improve the way you are deriving the doubles from the string since your way is error prone and inefficient. You could try this approach:
double[] selectedHours = hours.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries)
.Select(str => {
str = str.Trim();
double hour;
bool isDouble = double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out hour);
return new { str, isDouble, hour };
})
.Where(h => h.isDouble)
.Select(h => h.hour)
.ToArray();
var q = from row in dt.AsEnumerable()
join hour in selectedHours
on row.Field<double?>("Hours") equals hour
select row;
var dataSource = dt.Clone(); // empty
if (q.Any())
dataSource = q.CopyToDataTable();
The first issue is that CopyToDataTable only works if the source has some rows in it. Otherwise, it cannot determine the columns to be included in the table. A possible solution is to create an empty destination table with the right columns, and copy the rows to that table:
var destTable = dt.Copy();
destTable.Rows.Clear();
q.CopyToDataTable(destTable, LoadOption.OverwriteChanges);
GridView1.DataSource = destTable;
GridView1.DataBind();
The other issue is that, when called on an empty string, by default Split returns an array with on empty string, hence the error when you call Double.Parse on it. To avoid this, use StringSplitOptions.RemoveEmptyEntries:
string[] splitted = All.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
Double[] selectedHours = Array.ConvertAll(splitted, Double.Parse);

LINQ: Add RowNumber Column

How can the query below be modified to include a column for row number (ie: one-based index of results)?
var myResult = from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select currRow;
EDIT1: I'm looking for the results to be {idx, col1, col2...col-n} not {idx, row}.
EDIT2: The row number should correspond to result rows not the table rows.
EDIT3: I DataBind these results to a GridView. My goal was to add a row number column to the GridView. Perhaps a different approach would be better.
Use the method-syntax where Enumerable.Select has an overload with the index:
var myResult = someTable.Select((r, i) => new { Row = r, Index = i })
.Where(x => x.Row.someCategory == someCategoryValue)
.OrderByDescending(x => x.Row.createdDate);
Note that this approach presumes that you want the original index of the row in the table and not in the filtered result since i select the index before i filter with Where.
EDIT: I'm looking for the results to be {idx, col1, col2...col-n} not
{idx, row}. The row number should correspond to result rows not
the table rows.
Then select the anonymous type with all columns you need:
var myResult = someTable.Where(r => r.someCategory == someCategoryValue)
.OrderByDescending(r => r.createdDate)
.Select((r, i) => new { idx = i, col1 = r.col1, col2 = r.col2, ...col-n = r.ColN });
Use this Select method:
Projects each element of a sequence into a new form by incorporating the element's index.
Example:
var myResult = someTable.Where(currRow => currRow.someCategory == someCategoryValue)
.OrderByDescending(currRow => currRow.createdDate)
.Select((currRow, index) => new {Row = currRow, Index = index + 1});
In response to your edit:
If you want a DataTable as result, you can go the non-Linq way by simply using a DataView and add a additional column afterwards.
someTable.DefaultView.RowFilter = String.Format("someCategory = '{0}'", someCategoryValue);
someTable.DefaultView.Sort = "createdDate";
var resultTable = someTable.DefaultView.ToTable();
resultTable.Columns.Add("Number", typeof(int));
int i = 0;
foreach (DataRow row in resultTable.Rows)
row["Number"] = ++i;
what about?
int i;
var myResult = from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select new {Record = i++, currRow};
Just for fun, here's an alternative to Select with two arguments:
var resultsWithIndexes = myResult.Zip(Enumerable.Range(1, int.MaxValue - 1),
(o, i) => new { Index = i, Result = o });
According to you edit 1. NO, YOU CAN'T Linq returns the table as it is. You can build each column, but you lose the power of mapped entities.
This has been asked multiple times before: How do you add an index field to Linq results
There is no straightforward way if want to keep a flat list of columns (i.e. OP's Edit2) and also want a generic solution that works with any IEnumerable without requiring you to list out the set of expected columns.
However, there is a roundabout way to kinda go about it which is to dump the query results into a DataTable using the ToDataTable() method from here and then add a RowNumber column to that table.
var table = query.ToList().ToDataTable();
table.Columns.Add("RowNum", typeof(int));
int i = 0;
foreach (DataRow row in table.Rows)
row["RowNum"] = ++i;
This would likely cause performance issues with large datasets but it's not insanely slow either. On my machine a dataset with ~6500 rows took 33ms to process.
If your original query returned an anonymous type, then that type definition will get lost in the conversion so you'll lose the static typing on the column names of the resulting IEnumerable when you call table.AsEnumerable(). In other words, instead of being able to write something like table.AsEnumerable().First().RowNum you instead have to write table.AsEnumerable().First()["RowNum"]
However, if you don't care about performance and really want your static typing back, then you can use JSON.NET to convert the DataTable to a json string and then back to a list based on the anonymous type from the original query result. This method requires a placeholder RowNum field to be present in the original query results.
var query = (from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select new { currRow.someCategory, currRow.createdDate, RowNum = -1 }).ToList();
var table = query.ToDataTable();
//Placeholder RowNum column has to already exist in query results
//So not adding a new column, but merely populating it
int i = 0;
foreach (DataRow row in table.Rows)
row["RowNum"] = ++i;
string json = JsonConvert.SerializeObject(table);
var staticallyTypedList = JsonConvert.DeserializeAnonymousType(json, query);
Console.WriteLine(staticallyTypedList.First().RowNum);
This added about 120ms to the processing time for my 6500 item dataset.
It's crazy, but it works.
I know I'm late to the party, but I wanted to show what worked for me.
I have a list of objects, and the object has an integer property on it for "row number"... or in this case, "Sequence Number". This is what I did to populate that field:
myListOfObjects = myListOfObjects.Select((o, i) => { o.SequenceNumber = i; return o; }).ToList();
I was surprised to see that this worked.
This one helped me in my case - Excel sheet extraction. anonymous type
var UploadItemList = ItemMaster.Worksheet().AsEnumerable().Select((x, index) => new
{
Code = x["Code"].Value == null ? "" : x["Code"].Value.ToString().Trim(),
Description = x["Description"].Value == null ? "" : x["Description"].Value.ToString().Trim(),
Unit = x["Unit"].Value == null ? "" : x["Unit"].Value.ToString().Trim(),
Quantity = x["Quantity"].Value == null ? "" : x["Quantity"].Value.ToString().Trim(),
Rate = x["Rate"].Value == null ? "" : x["Rate"].Value.ToString().Trim(),
Amount = x["Amount"].Value == null ? "" : x["Amount"].Value.ToString().Trim(),
RowNumber = index+1
}).ToList();
int Lc = 1;
var Lst = LstItemGrid.GroupBy(item => item.CategoryName)
.Select(group => new { CategoryName = group.Key, Items = group.ToList() ,RowIndex= Lc++ })
.ToList();

Sort column in DataTable with same values

I have a question about sorting in Datatable. I have a table like below and want to sort it from small to big. The problem is when i have same numbers, i want to have the first as the last and so on...
Table:-----------------------------------After Sorting:
Name Bit Size Name Bit Size (corrected)
A 0 1 A 0 1
C 1 2 C 1 2
B 1 3 B 1 3
D 1 1 D 1 1
Result that i want:
Name Bit Size (corrected)
A 0 1
D 1 1
B 1 3
C 1 2
My Code:
arraySBit.DefaultView.Sort = "Bit";
arraySBit = arraySBit.DefaultView.ToTable();
You can use Linq-To-DataTable:
var tblSorted = table.AsEnumerable()
.OrderBy(r => r.Field<int>("Bit"))
.CopyToDataTable();
Edit: But actually DataView.Sort should also work (tested).
Since you have edited your question. Your requirement seems weired. If the Bit is the same you don't want to order by something but you want to reverse the "order" of the rows of the equal rows (so the Ordinal position in the DataTable).
This does what you want although i'm not sure that it's really what you need:
DataTable tblSorted = table.AsEnumerable()
.Select((Row, Ordinal) => new {Row,Ordinal})
.OrderBy(x => x.Row.Field<int>("Bit"))
.ThenByDescending(x => x.Ordinal)
.Select(x => x.Row)
.CopyToDataTable();
Basically it passes the index of the row in the table via this overload Enumerable.Select into an anonymous type. Then it'll sort by Bit first and the index/ordinal second.
A workaround,
After these lines, you should have result in arrySBit as you want
DataTable arrySBitClone = arrySBit.Copy();
arrySBit.DefaultViewSort.Sort = "Bit";
bool different = false;
for(int i=0; i<arrySBit.Rows.Count; i++)
{
if(arrySBit.Rows[i]["Bit"]!=arrySBitClone.Rows[i]["Bit"])
{
difference = true;
break;
}
}
if(!different)
{
arrySBit = arrySBit.Copy();
}
if i'm getting right your question.
why not use Select?
DataTable dt = arraySBit.Select("", "Bit, Size").CopyToDataTable();
The first parameter of the Select Method is a condition to filter, the second is an Order By, so it should work

most matched field value

I have a DataTable. I can also use Linq.
In a DataTable have many columns, and rows. One of the column is called as feedCode. its type is string. in database it's length is 7 varchar, nullable.
feedCode may contain values as 9051245, 9051246, 9051247, 9031454, 9021447.
Method must return most matched (in this case starting with 905) value 905 (first 3 character of string)?
thanks.
Try to use this code:
var feedCodes = new string[] { "9051245", "9051246", "9051247", "9051245", "9031454", "9021447" };
var mostOccuring = feedCodes.Where(feedCode => feedCode != null)
.GroupBy(feedCode => feedCode.Length < 3 ? feedCode : feedCode.Substring(0, 3))
.OrderByDescending(group => group.Count())
.FirstOrDefault();
if(mostOccuring == null)
{
//some exception handling
}
else
{
//process mostoccuring.Key
}
this code also handle feedcodes with length less than 3 (even empty strings). If you don't want to use them just filter them out in where statement.
Maybe i didn't understand your question correctly but maybe this will be a starting point for your:
//The feedCodes (i put one in two times, to have one appearing most often)
var values = new string[] { "9051245", "9051246", "9051247", null, "", "9051245", "9031454", "9021447" };
//Just filter the list for filled up values
var query = values.Where(value => !String.IsNullOrEmpty(value))
//and group them by their starting text
.GroupBy(value => value.Substring(0, 3))
//order by the most occuring group first
.OrderByDescending(group => group.Count());
//Iterate over all groups or just take the first one with query.First() or query.FirstOrDefault()
foreach (var group in query)
{
Console.WriteLine(group.Key + " Count: " + group.Count());
}

Categories