LINQ TO Entities OrderBy Conundrum - c#

I am having trouble trying to understand how to perform an order by in a LINQ to Entities call to return data organized in the desired order. The database used is postgresql. The order by in postgres is:
SELECT
*
FROM
part
ORDER BY
split_part(partnumber, '-',1)::int
, split_part(partnumber, '-',2)::int
Partnumber is a string field which is formated into 2-3 segments which are numeric separated by '-'. eg:
1-000235
10-100364
9-123456
etc.
I would want the sorted result to return:
1-000235
9-123456
10-100364
I have a test VB.Net app I am trying to figure out how to do this:
Using ctx As New EFWeb.MaverickEntities
Dim myparts = ctx.parts.
OrderBy(Function(e) e.partnumber).
ToList()
For Each pt As part In myparts
Console.WriteLine("{0} - {1}", pt.partnumber, pt.description)
Next
End Using
I tried doing: CInt(e.partnumber.Split("-")(0)) to force sorting for the first segment of the partnumber, but errored out because of the the compiler did not like the array referrence for the result of the Split() call.
If anybody knows of a good current reference for LINQ to Entities ... that would be appreciated.

You didn't share your Linq code. Anyway I would get the data to client side and then do the ordering. In C#:
var result = ctx.Parts.AsEnumerable()
.Select(p => new {p, pnSplit = p.PartNumber.Split('-')})
.OrderBy(x => int.Parse(x.pnSplit[0]))
.ThenBy(x => int.Parse(x.pnSplit[1]))
.Select(x => x.p);
In VB it should be:
Dim result = ctx.Parts.AsEnumerable()
Select(Function(p) New With {p, .pnSplit = p.PartNumber.Split("-"c)}).
OrderBy(Function(x) Integer.Parse(x.pnSplit(0))).
ThenBy(Function(x) Integer.Parse(x.pnSplit(1))).
Select(Function(x) x.p)
Note the integer.Parse. Otherwise it would be alphabetic sort.

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 to get IEnumerable<Foo> instead of IEnumerable<string> from LINQ?

I am stuck on a LINQ query where I have being trying to return a list records from an SQL Table using EntityFramework 6, instead of getting that list, I keep end up getting an IEnumerable<string[]>.
This is what I have made to get IEnumerable<string[]>. I have an column in the table that needs to be split and then I test to see if a list contains those strings : The value in the table column columnOne can be something like this "a-b" or "b-c" etc, hence the need to use this s.columnOne.Split('-')
list<string> checkList = new list<string>();
checkList.add("a")
checkList.add("b")
checkList.add("c")
List<Foo> fooList = dbContext.Foos.ToList();
IEnumerable<string[]> items = fooList.Select(s => s.columnOne.Split('-'));
var result = items.SelectMany(x => x)
.Where(s => checkList.Contains(s)).ToList();
The above works as it should, but in the end it returns a list of string, which is not what I need.
I tried this below:
List<Foo> fooList = dbContext.Foos.ToList();
var test = fooList.Where(s => s.columnOne.Split('-'));
And this is where I run into the error and can go no further as I am ultimately trying to return a list of <Foo>, not <string>
Now I know that the Where clause needs to return a bool value such as fooList.Where(s => s.columnOne == "someString");, but I am at a loss as to how to go about structuring the LINQ query to get the results I am trying to get.
Any shove in the right direction would be great.
The where clause needs to contain the check against the checkList
var test = fooList.Where(foo => foo.columnOne.Split('-').Any(str => checkList.Contains(str)));
dbContext.Foos
.ToList()
.Where(foo => foo.columnOne.Split('-')
.Any(x => checkList.Contains(x))
I hope you don't ever have very many Foos because that ToList function will read them all into memory. You'll need that, though, because I don't think Linq can understand how to translate that where statement. There are a few ways to get around that, if it's a problem to you.
Also, consider using a Set<string> instead of a List<string> for your checklist variable, as it'll perform better for membership checks like this one.

Select 1 column from a Group By LINQ query

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.

Get ICollection out from IQueryable<ICollection> LINQ Query

I'm trying to write a query that grabs a list of countries out from my joined data.
Places is List<Country>.
var zonedCountries = (from dz in db.DeliveryZones.Include(d => d.Places)
where model.DeliveryZones.Contains(dz.ID)
select dz.Places);
I would expect zonedCountries to be a List but instead it is a IQueryable<ICollection<Country>>.
How do I extract the list from this?
If you want to get flattened list of countries:
var zonedCountries = (from dz in db.DeliveryZones.Include(d => d.Places)
where model.DeliveryZones.Contains(dz.ID)
from p in dz.Places
select p);
Or use SelectMany:
var zonedCountries = db.DeliveryZones.Include(d => d.Places)
.Where(dz => model.DeliveryZones.Contains(dz.ID))
.SelectMany(dz => dz.Places);
BTW I'm not sure if you need to include places manually in this case (thus you are selecting places instead of delivery zones). And you will probably want to select distinct countries only - Distinct() will help you here. Also if you want to store results in list, then simple ToList() call will do the job.

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