Select 1 column from a Group By LINQ query - c#

I think what I need is relatively simple but every example I Google just returns results using First(), which I'm already doing. Here is my expression:
var options = configData.AsEnumerable().GroupBy(row => row["myColumn"]).Select(grp => grp.First());
What I need is only ONE column from the grp portion and to be able to suffix .ToList() on there without an error. As it stands I receive 4 columns, but only need a specific one, kind of like if this (grp => grp["myColumn"]), didn't result in error the Error 153 Cannot apply indexing with [] to an expression of type 'System.Linq.IGrouping<object,System.Data.DataRow>'
Also, Key does not work in the grouping portion as these results are from a DataTable object. See here - >

If you want only the keys, you can use
var options = configData.AsEnumerable().Select(row=>row["myColumn"]).Distinct();

I think that this is what you want:
configData.AsEnumerable()
.GroupBy(r => r["myColumn"])
.Select(g => new
{
myColumnValue = g.Key,
myColumnItems = g.Select(r => r["OtherColumn"]).ToList()
});
Do you understand how/what this does though? Try it out and inspect the resulting IEnumerable. I'm not sure you have a perfect understanding on how GroupBy works but take your time with above example.
See this part:
new
{
myColumnValue = g.Key,
myColumnItems = g.Select(r => r["OtherColumn"]).ToList()
}
This creates an anonymous type which outputs the values of "OtherColumn" column into a list grouped by "myColumn" where value of "myColumn" is in the myColumnValue property.
I'm not sure this answers your question but it looks like this is what you want.
The variable g is of the type IGrouping<object, DataRow>, it's not DataRow. The IGrouping interface is designed to provide a list of DataRow's grouped by object values - it does not produce a flat list, if it did then it would just be a Sort, not GroupBy.

Just specify the field you want after your call to First() e.g.
.Select(grp => grp.FirstOrDefault()["MyFieldName"]);
This will take the first record from the grouping and select the specified field from that record.

Related

Using LINQ to filter records in an object

I have a deserialized Json object that I am trying to filter before processing. The data looks like this...
Company Division LastModDate Lot's of other columns/objects
123 1 7/1/2021
123 1 8/1/2022
123 2 8/1/2022
How can I get all the information in the original object and get rid of records that are not the latest for each Company/Division group?
I tried this...
var filtered = origObject.GroupBy(g=> new {g.Company,g.Division})
I don't know where to go next.
If I were doing this in SQL then I would be using row_number and just taking the 1 for example.
You could try something like
var filtered = origObject
.GroupBy(x => new {g.Company,g.Division})
.Select(g => g.OrderByDescending(x => x.LastModDate).First());
This will select one latest object from each group.
Edit: I'm not sure without a compiler at hand if this will group correctly - your grouping key is an anonymous object, I don't remember if they have any equality comparer other than by reference. You could try using a record instead, records have equality by value of all their properties - .GroupBy(g => (g.Company,g.Division)). Or just group by a string key such as $"{g.Company},{g.Division}",
A much more efficient way of doing this is as follows:
var filtered = origObject
.OrderByDescending(w => w.LastModDate)
.DistinctBy(w => (w.Company, w.Division));
This avoids the heavy array allocation and copying of GroupBy, especially since you only care about one item from its result.

How can I select dynamically from a datatable in linq?

I have a method that groups and selects from a datatable (among other things). The select needs to be dynamically passed into the method if possible.
I tried passing a Func<...> to the method. I read other answers that suggest I might need to create a strong Type and possibly use generics to make it work.
var dtDistinct = dtRaw.AsEnumerable()
.GroupBy(g => g.Field<string>("DisplayName").ToString())
.Select(group => new
{
DisplayName = group.Key,
Value = string.Join("_", group.Select(s => s.Field<int>("Value"))),
OrderBy = group.Max(s => s.Field<object>(columnNameToOrderBy).ToString())
})
.ToDataTable();
Everything in the above code will always be the case, except sometimes there may be additional columns in dtRaw that i need to select as well. These additional columns will not always have the same name or datatypes.

LINQ Intersect on inner collection

I have a list of Stores (of type ObservableCollection<Store>) and the Store object has a property called Features ( of type List<Feature> ). and the Feature object has a Name property (of type string).
To recap, a list of Stores that has a list of Features
I have a second collection of DesiredFeatures (of type List<string> ).
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures. So far, I've only been able to come up with a query that gives me an OR result instead of AND.
Here's what that looks like:
var q = Stores.Where(s=> s.Features.Any(f=> DesiredFeatures.Contains(f.name)));
I know Intersect can help, and here's how I've used it:
var q = Stores.Where(s => s.Features.Intersect<Feature>(DesiredFeatures));
This is where I'm stuck, Intersect wants a Feature object, what I need to intersect is on the Feature.Name.
The goal is to end up with an ObservableCollection where each Store has all of the DesiredFeatures.
Thank you!
You've almost done what you need. A small refine would be to swap DesiredFeatures and s.Features.
var q = Stores.Where(s => DesiredFeatures.All(df => s.Features.Contains(df)));
It means take only those stores where desired features are all contained in features of the store.
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures.
In other words, each desired feature must have a matching store feature.
I don't see how Intersect can help in this case. The direct translation of the above criteria to LINQ is like this:
var q = Stores.Where(s =>
DesiredFeatures.All(df => s.Features.Any(f => f.Name == df))
);
A more efficient way could be to use a GroupJoin for performing the match:
var q = Stores.Where(s =>
DesiredFeatures.GroupJoin(s.Features,
df => df, sf => sf.Name, (df, sf) => sf.Any()
).All(match => match)
);
or Except to check for unmatched items:
var q = Stores.Where(s =>
!DesiredFeatures.Except(s.Features.Select(sf => sf.Name)).Any()
);
Going on your intersect idea, the only way I thought of making this work was by using Select to get the Store.Features (List<Feature>) as a list of Feature Names (List<string>) and intersect that with DesiredFeatures.
Updated Answer:
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures).Any());
or
var q = Stores.Where(s => DesiredFeatures.Intersect(s.Features.Select(f => f.Name)).Any());
Old Answer (if DesiredFeatures is a List<Feature>):
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures.Select(df => df.Name)).Any());
Two things you want your code to perform.
var q = Stores.Where(s=> s.Features.All(f=> DesiredFeatures.Contains(f.name)) &&
s.Features.Count() == DesiredFeatures.Count()); // Incude Distinct in the comparison if Features list is not unique
Ensure that every Feature is DesiredFeature
Store contains all Desired features.
Code above assumes uniqueness in Features collection as well as DesiredFeatures, modify code as stated in comment line if this is not right

LINQ filter a database list with another database list

I have tried to find an answer for this.
I am using LINQ and trying to filter a database list with another list, to remove countries from a list of countries where a member is already a citizen.
var currentCitizenships = DbContext.Citizenships
.Where(c => c.MemberId == CurrentUser.MemberId)
.Include(c => c.Country)
.ToList();
var filtered = DbContext.Countries
.Where(c => !currentCitizenships.Any(current => current.Country.CountryId == c.CountryId));
I am getting a Not supported exception with the following message:
Unable to create a constant value of type 'Project.EntityFramework.Models.Citizenship'. Only primitive types or enumeration types are supported in this context.
Two Solutions Worked
Remove the ToList() of the first query.
The selected answer.
I've picked 1. due to using less lines and was a simpler solution with the same result.
It seems that it cannot create a valid SQL query using whatever is stored in currentCitizenships.
Get the list of country id's you need first, and then modify your query to use Contains on the simple collection of integers (or whatever CountryId is) instead.
var countryIds = currentCitizenships.Select(x => x.Country.CountryId).ToList();
var filtered = DbContext.Countries.Where(c => !countryIds.Contains(c.CountryId));

Numbering the rows per group after an orderby in LINQ

Say you have columns AppleType, CreationDate and want to order each group of AppleType by CreationDate. Furthermore, you want to create a new column which explicitly ranks the order of the CreationDate per AppleType.
So, the resulting DataSet would have three columns, AppleType, CreationDate, OrderIntroduced.
Is there a LINQ way of doing this? Would I have to actually go through the data programmatically (but not via LINQ), create an array, convert that to a column and add to the DataSet? I have there is a LINQ way of doing this. Please use LINQ non-method syntax if possible.
So are the values actually appearing in the right order? If so, it's easy - but you do need to use method syntax, as the query expression syntax doesn't support the relevant overload:
var queryWithIndex = queryWithoutIndex.Select((x, index) => new
{
x.AppleType,
x.CreationDate,
OrderIntroduced = index + 1,
});
(That's assuming you want OrderIntroduced starting at 1.)
I don't know offhand how you'd then put that back into a DataSet - but do you really need it in a DataSet as opposed to in the strongly-typed sequence?
EDIT: Okay, the requirements are still unclear, but I think you want something like:
var query = dataSource.GroupBy(x => x.AppleType)
.SelectMany(g => g.OrderBy(x => x.CreationDate)
.Select((x, index ) => new {
x.AppleType,
x.CreationDate,
OrderIntroduced = index + 1 }));
Note: The GroupBy and SelectMany calls here can be put in query expression syntax, but I believe it would make it more messy in this case. It's worth being comfortable with both forms.
If you want a pure Linq to Entities/SQL solution you can do something like this:
Modified to handle duplicate CreationDate's
var query = from a in context.AppleGroup
orderby a.CreationDate
select new
{
AppleType = a.AppleType,
CreationDate = a.CreationDate,
OrderIntroduced = (from b in context.AppleGroup
where b.CreationDate < a.CreationDate
select b).Count() + 1
};

Categories