How to select a subset from a DataTable through LINQ? - c#

I am new to LINQ.
I have the following DataTable
Name Date price1 price2
string DateTime decimal decimal
Jan09 14.01.2009 10.0 12.0
Feb09 14.01.2009 11.0 13.0
Jan09 15.01.2009 10.0 12.5
Feb09 15.01.2009 9.0 10.0
Jan09 18.01.2009 10.0 12.5
Feb09 18.01.2009 9.0 10.0
Name and Date are the primary compound key.
I want to select all Names for each Date, then iterate through the new collection and select the next date.
var subCollection = tab.Rows.Cast<DataRow>().Select(r1 => r1["Date"]).Select<string>(r2 => r2["Name"])
foreach (DataRow row in subCollection)
{
// do something with row
}
My Linq expression is wrong

I think what you want is to group by your Date, then look at all the Names for a given date, then most onto the next.
If that is the case, you want to use the Linq group syntax...
var query = from row in table.AsEnumerable()
group row by row["Date"] into g
select g;
You can find a lot of examples online for doing various things with the Linq group syntax. The thing I find important is realizing that you can group by multiple columns and still apply aggregate functions like Sum, Max, or Count using the following syntax:
var query = from row in table.AsEnumerable()
group row by new { Date = row["Date"], Price1 = row["Price1"] } into g
select new
{
Date = g.Key.Date,
Price = g.Key.Price1,
Count = g.Count()
};

Related

Convert sql to LINQ query for selection of multiple max columns in c#

SELECT MAX(sectionid) AS SectionId,MAX(displayorder) AS DisplayOrder,propertyid AS PropertyId,1 AS IsSpecSection FROM (
SELECT mp.SectionId ,mp.DisplayOrder ,mp.PropertyId FROM
ModelProperties mp
INNER JOIN PropertySections PS ON mp.SectionId =
ps.SectionId
WHERE ps.IsSpecSection = 1 )s
GROUP BY propertyid
I want to convert above query into LINQ, able to do it for selection of single max column but not for multiple.
I haven't tested the code you have to modify the code as you need
using (var dbContext = new YourEntityName())
{
var result = (from mp in dbContext.ModelProperties
join ps in dbContext.PropertySections on mp.SectionId equals ps.SectionId
where ps.IsSpecSection = 1
group a by new { propertyid } into g
select sectionid , MAX(displayorder)AS DisplayOrder,propertyid AS PropertyId, 1 AS IsSpecSection).ToList();
}
Max value in Linq select Within Innerjoin
You can use this code,
var list=(from mp in ModelProperties
join ps in PropertySections on mp.SectionId equals ps.SectionId
where ps.IsSpecSection == 1
group new { mp, ps } by new { mp.PropertyId } into mgrp
from grp in mgrp.DefaultIfEmpty()
select new
{
grp.mp.SectionId,
grp.mp.PropertyId,
grp.mp.DisplayOrder,
grp.ps.IsSpecSection
}).OrderByDescending(x=>x.SectionId).First();
This query helps you to retrieve ModelProperties rows that has matching SectionId in PropertySections and IsSpecSection has the value 1. Matching rows are then grouped by PropertyId. OrderByDescending sort the retrieved results in descending order of SectionId. First() retrieve the rows that has maximum SectionId for each PropertySections as the rows are sorted in descending order of SectionId.

select records with max aggregate using Linq To SQL

I have following two tables:
DocumentType
Id INT,
Name VARCHAR(100),
Active BIT,
CreatedBy INT
Document
Id INT,
DocumentTypeId INT,
Version SMALLINT,
Text NTEXT
I want to select DocumentType and related Document record with maximum value for Version. I tried following query:
from t in Documents
join tt in DocumentTypes on t.DocumentTypeId equals tt.Id
where tt.CreatedBy == 10
group t by t.DocumentTypeId into g
//let v = new {Version = g.Max( t => t.Version), TypeId =g.Key}
select new
{
Key = g.Key,
Version = g.Max(t=>t.Version),
Text = t.Text //ERROR AT t.Text
};
but it is giving me an error at following line:
Text = t.Text
The name 't' does not exist in the current context
I have tried g.Text also but it is not helping. Kindly help me to fix this query. I am trying this in LinqPad.
It seems that you need to retrieve a Document entity withing the same DocumentType which has the max value for the Version property. There is no need to group by ntext column.
After grouping you have groups of documents. The only thing left is to get one with the max Version value for each group. I'd order the group by this property in descending order, and get the first value:
from t in Documents
join tt in DocumentTypes on t.DocumentTypeId equals tt.Id
where tt.CreatedBy == 10
group t by t.DocumentTypeId into g
select g.OrderByDescending(t => t.Version).FirstOrDefault();
You could project the result Document entity into an anonymous type if you want.
Try
from t in Documents
join tt in DocumentTypes on t.DocumentTypeId equals tt.Id
where tt.CreatedBy == 10
orderby t.Version descending
group t by t.DocumentTypeId into g
select new
{
Key = g.Key,
Version = g.First().Version,
Text = g.First().Text
};
t already stand for something else.
Try this way
Version = g.Max(x=>t.Version),

Group by one column and Distinct by another column using Linq

I am using a Linq query to groupBy a column name and return a list of rows.
var query = from row in ProcessSummaryData.AsEnumerable()
group row by new { Key = row .Field<string>("GroupDescription") } into g
select new
{
GroupDescription = g.Key,
Values = g.ToList(),
};
The output of this query is something like this
GroupDescription Values
1 12,abc,xyz
12,abx,yut
13,tye,lki
2 14,asd,acd
Now the in the above example Values is a DataRow and I have just given an example of values in it.
Now what I want is that for GroupDescription '1' the output only has one row with '12' value.
I have tried a few things one of which is to have another Linq query on first list but that's over complicating things.
How do I use linq to group by first column and then use Distinct on certain column returned list to get only Distinct rows?
To get the first occurrence of a field's values you can group by that field and then take the first row of each grouping.
var query = from row in ProcessSummaryData.AsEnumerable()
group row by new { Key = row .Field<string>("GroupDescription") } into g
select new
{
GroupDescription = g.Key,
Values = (from value in g.ToList()
group value by value["Id"] into valueGroup
select valueGroup.First()).ToList()
};

C# Linq filter DataTable using array elements

I want filter the data in a data table using linq.
My scenario is I have an array of elements which contains dates created dynamically and in the data table we have columns as id,date,etc.
We have to retrieve the id's which contains all the dates in array
ex:
string[] arr={"10/10/2012","11/11/2012","9/9/2012"}
Table :
ID date
1 10/10/2012
2 11/11/2012
1 9/9/2012
6 9/9/2012
3 9/9/2012
6 11/11/2012
1 11/11/2012
Output would be 1 - because only id '1' has all the array elements.
To accomplish above functionality I am using the Linq query shown below. But I am literally failing.
Dim volunteers As DataTable =
(From leftTable In dtavailableVolunteers.AsEnumerable()
Join rightTable In dtavailableVolunteers.AsEnumerable()
On leftTable.VolunteerId Equals rightTable.VolunteerId
Where SelectedDatesArray.All(Function(i) rightTable.Field(Of String)("SelectedDate").Equals(i.ToString()))
Select rightTable).CopyToDataTable()
Lets say your datatable is dt
DataRow[] dr = dt.Select("date in (" + string.join("," , arr) + ")");
string[] st = dr.Select(ss => ss["id"].ToString()).ToArray();
OR
DataTable newdt = dr.CopyToDataTable();
Second line is of LINQ
You could group the rows by ID, and then find the groups where: there does not exist an arr element which the group's dates doesn't contain that element. I mean something like:
var result = from item in list
group item by item.ID into grouping
where !arr.Exists(date =>
!grouping.Select(x => x.Date).Contains(date))
select grouping.Key;
Here is another version:
from volunteer in dtavailableVolunteers
group volunteer by volunteer.Id into g
let volunteerDates = g.Select(groupedElement=>groupedElement.date)
where arr.All(date=>volunteerDates.Contains(date))
select g.Key

LINQ GroupBy confusion

I have
var result = (from rev in Revisions
join usr in Users on rev.UserID equals usr.ID
join clc in ChangedLinesCounts on rev.Revision equals clc.Revision
select new {rev.Revision,
rev.Date, usr.UserName, usr.ID, clc.LinesCount}).Take(6);
I make a couple of joins on different tables, not relevant for this question what keys are, but at the end of this query my result "table" contains
{Revision, Date, UserName, ID, LinesCount}
Now I execute e GroupBy in order to calculate a total lines count per user.
So..
from row in result group row by row.ID into g {1}
select new {
g.Key,
totalCount = g.Sum(count=>count.LinesCount)
};
So I get a Key=ID, and totalCount=Sum, but
Confusion
I would like to have also other fields in final result.
In my understanding "table" after {1} grouping query consist of
{Revision, Date, UserName, ID, LinesCount, TotalCount}
If my assumption is correct, why I can not do something like this:
from row in result group row by row.ID into g {1}
select new {
g.Key,
g.Revision //Revision doesn't exist ! Why ??
totalCount = g.Sum(count=>count.LinesCount)
};
but
from row in result group row by row.ID into g {1}
select new {
g.Key,
Revision = g.Select(x=>x.Revision), //Works !
totalCount = g.Sum(count=>count.LinesCount)
};
Works !, but imo, sucks, cause I execute another Select.
Infact looking on LinqPad SQL output I get 2 SQL queries.
Question
Is there any elegant and optimal way to do this, or I always need to run Select
on groupped data, in order to be able to access the fields, that exists ?
The problem is, that you only group by ID - if you'd do that in SQL, you couldn't access the other fields either...
To have the other fields as well, you have to include them in you group clause:
from row in result group row by new { row.ID, row.Revision } into g
select new {
g.Key.ID,
g.Key.Revision
totalCount = g.Sum(count=>count.LinesCount)
};
The problem here is your output logically looks something like this:
Key = 1
Id = 1, Revision = 3587, UserName = Bob, LinesCount = 34, TotalCount = 45
Id = 1, Revision = 3588, UserName = Joe, LinesCount = 64, TotalCount = 54
Id = 1, Revision = 3589, UserName = Jim, LinesCount = 37, TotalCount = 26
Key = 2
Id = 2, Revision = 3587, UserName = Bob, LinesCount = 34, TotalCount = 45
Id = 2, Revision = 3588, UserName = Joe, LinesCount = 64, TotalCount = 54
Id = 2, Revision = 3589, UserName = Jim, LinesCount = 37, TotalCount = 26
Much like if you were to perform a an SQL GROUP BY, an value is either part of the key and thus unique per group, or is in the details and thus is repeated multiple times and possibly different for each row.
Now, logically, it might be that Revision and UserName are unique for each Id but Linq has no way to know that (the same as SQL has no way to know that).
To solve this you'll need to some how specify which revision you want. For instance:
Revision = g.FirstOrDefault(x => x.Revision)
To avoid the multiple SQL problem you would need to use an aggregate function that can be translated in to SQL since most SQL dialects do not have a first operator (the result set is considered unordered so technically no item is "first").
Revision = g.Min(x => x.Revision)
Revision = g.Max(x => x.Revision)
Unfortunately Linq does not have a min/max operator for strings, so although the SQL might support this, Linq does not.
In this case you can produce an intermediate result set for the Id and totals, then join this back to the original set to get the details, eg:
from d in items
join t in (
from t in items
group by t.Id into g
select new { Id = g.Key, Total = g.Sum(x => x.LineCount) }
) on d.Id equals t.Id
select new { Id = d.Id, Revision = d.Revision, Total = t.Total }
Revision doesn't exist in your second example because it's not a member of IGrouping<T>, in IGrouping<T> you have a Key property, and it's also an IEnumerable<T> for all the rows grouped together. Thus each of those rows has a Revision, but there is no Revision for the grouping itself.
If the Revision will be the same for all rows with the same ID, you could use FirstOrDefault() so that the select nets at most one answer:
from row in result group row by row.ID into g {1}
select new {
g.Key,
Revision = g.Select(x=>x.Revision).FirstOrDefault(),
totalCount = g.Sum(count=>count.LinesCount)
};
If the Revision is not unique per ID, though, you'd want to use an anonymous type as #Tobias suggests for the grouping, then you will get a grouping based on ID and Revision.

Categories

Resources