Lambda expressions in C# with If\Else - c#

I built an SQL Query that order table by complex Condition.
here is a example with the same principle: (Table Toto include 3 cols: id ,num1,num2)
select t.id
from Toto as t
group by t.id
order by sum(case when t.id<100 Then t.num1 ELSE t.num2 END)
and my question is, there is option to write this query in Lambda?(using when\else)
thank you!

It would look something like this:
db.Toto
.GroupBy(t => t.id)
.OrderBy(g => g.Sum(t => t.id<100 ? t.num1 : t.num2)
.Select(g => g.Key) // since you're grouping by Id
it's the cleaner equivalent of:
db.Toto
.GroupBy(t => t.id)
.OrderBy(g => g.Sum(t => {
if (t.id<100)
return t.num1;
else
return t.num2;
}
)
.Select(g => g.Key) // since you're grouping by Id

Related

EF Core Linq-to-Sql GroupBy SelectMany not working with SQL Server

I am trying the following Linq with LinqPad connecting to SQL Server with EF Core:
MyTable.GroupBy(x => x.SomeField)
.OrderBy(x => x.Key)
.Take(5)
.SelectMany(x => x)
I get this error:
The LINQ expression 'x => x' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explic...
However, this works:
MyTable.AsEnumerable()
.GroupBy(x => x.SomeField)
.OrderBy(x => x.Key)
.Take(5)
.SelectMany(x => x)
I was under the impression that EF Core should be able to translate such an expression.
Am I doing anything wrong?
That exception message is an EF Core message, not EF6.
In EF 6 your expression should work, though with something like a ToList() on the end. I suspect the error you are encountering is that you may be trying to do something more prior to materializing the collection, and that is conflicting with the group by SelectMany evaluation.
For instance, something like this EF might take exception to:
var results = MyTable
.GroupBy(x => x.SomeField)
.OrderBy(x => x.Key)
.Take(5)
.SelectMany(x => x)
.Select(x => new ViewModel { Id = x.Id, Name = x.Name} )
.ToList();
where something like this should work:
var results = MyTable
.GroupBy(x => x.SomeField)
.OrderBy(x => x.Key)
.Take(5)
.SelectMany(x => x.Select(y => new ViewModel { Id = y.Id, Name = y.Name} ))
.ToList();
You don't want to use:
MyTable.AsEnumerable(). ...
As this is materializing your entire table into memory, which might be ok if the table is guaranteed to remain relatively small, but if the production system grows significantly it forms a cascading performance decline over time.
Edit: Did a bit of digging, credit to this post as it does look like another limitation in EF Core's parser. (No idea how something that works in EF6 cannot be successfully integrated into EF Core... Reinventing wheels I guess)
This should work:
var results = MyTable
.GroupBy(x => x.SomeField)
.OrderBy(x => x.Key)
.Take(5)
.Select(x => x.Key)
.SelectMany(x => _context.MyTable.Where(y => y.Key == x))
.ToList();
So for example where I had a Parent and Child table where I wanted to group by ParentId, take the top 5 parents and select all of their children:
var results = context.Children
.GroupBy(x => x.ParentId)
.OrderBy(x => x.Key) // ParentId
.Take(5)
.Select(x => x.Key) // Select the top 5 parent ID
.SelectMany(x => context.Children.Where(c => c.ParentId == x)).ToList();
EF pieces this back together by doing a SelectMany back on the DbSet against the selected group IDs.
Credit to the discussions here: How to select top N rows for each group in a Entity Framework GroupBy with EF 3.1
Edit 2: The more I look at this, the more hacky it feels. Another alternative would be to look at breaking it up into two simpler queries:
var keys = MyTable.OrderBy(x => x.SomeField)
.Select(x => x.SomeField)
.Take(5)
.ToList();
var results = MyTable.Where(x => keys.Contains(x.SomeField))
.ToList();
I think that translates your original example, but the gist is to select the applicable ID/Discriminating keys first, then query for the desired data using those keys. So in the case of my All children from the first 5 parents that have children:
var parentIds = context.Children
.Select(x => x.ParentId)
.OrderBy(x => x)
.Take(5)
.ToList();
var children = context.Children
.Where(x => parentIds.Contains(x.ParentId))
.ToList();
EF Core has limitation for such query, which is fixed in EF Core 6. This is SQL limitation and there is no direct translation to SQL for such GroupBy.
EF Core 6 is creating the following query when translating this GroupBy.
var results = var results = _context.MyTable
.Select(x => new { x.SomeField })
.Distinct()
.OrderBy(x => x.SomeField)
.Take(5)
.SelectMany(x => _context.MyTable.Where(y => y.SomeField == x.SomeField))
.ToList();
It is not most optimal query for such task, because in SQL it can be expressed by Window Function ROW_NUMBER() with PARTITION on SomeField and additional JOIN can be omitted.
Also check this function, which makes such query automatically.
_context.MyTable.TakeDistinct(5, x => x.SomeField);

Linq - Group By Id, Order By and Then select top 5 of each grouping

Is there some way in linq group By Id, Order By descending and then select top 5 of each grouping? Right now I have some code shown below, but I used .Take(5) and it obviously selects the top 5 regardless of grouping.
Items = list.GroupBy(x => x.Id)
.Select(x => x.OrderByDescending(y => y.Value))
.Select(y => new Home.SubModels.Item {
Name= y.FirstOrDefault().Name,
Value = y.FirstOrDefault().Value,
Id = y.FirstOrDefault().Id
})
You are almost there. Use Take in the Select statement:
var items = list.GroupBy(x => x.Id)
//For each IGrouping - order nested items and take 5 of them
.Select(x => x.OrderByDescending(y => y.Value).Take(5))
This will return an IEnumerable<IEnumerable<T>>. If you want it flattened replace Select with SelectMany

How to convert linq query to non-query expression?

I use EF6, I have this linq to entity query:
from s in SensorObservationEntities.SensorsMeasures
group s by s.SensorUnitId into g
let latest = g.OrderByDescending(s => s.MeasureDate).FirstOrDefault()
select latest
How can I convet it to non-query expression?
You mean method syntax :
SensorObservationEntities.SensorsMeasures.GroupBy(g => g.SensorUnitId)
.Select(y => y.OrderByDescending(x => x.MeasureDate).FirstOrDefault());
If you want to convert this to the method syntax version, you can do this step by step. I like to start at the end and work through to the beginning:
select to Select defines the source:
.Select(g => g.OrderByDescending(s => s.MeasureDate).FirstOrDefault());
group is GroupBy:
.GroupBy(s => s.SensorUnitId)
.Select(g => g.OrderByDescending(s => s.MeasureDate).FirstOrDefault());
from the source
SensorObservationEntities.SensorsMeasures
.GroupBy(s => s.SensorUnitId)
.Select(g => g.OrderByDescending(s => s.MeasureDate).FirstOrDefault());

Linq select same id and remove other

I need to filter this list.
I just want to have those person that have the same SalId and those must be at least two.
How do I do it?
var dupSalIdPersons = persons
.GroupBy(p => p.SalId)
.Where(g => g.Count() >= 2)
.SelectMany(g => g);

Subquery parent id in NHibernate QueryOver

I'm trying to understand how this may work.
What I would have is to have all Trees from a given list of IDs that has not rotten apples.
Looks easy but I'm fresh of NHibernate, not so good in SQL and as you can see I'm stuck.
I wrote this code down here:
Tree treeitem = null;
QueryOver<Apple> qapple = QueryOver.Of<Apple>()
.Where(x => (!x.IsRotten))
.And(Restrictions.IdEq(Projections.Property<Tree>(y => y.Id)))
// Or this one...
//.And(Restrictions.EqProperty(
// Projections.Property<Apple>(y => y.Tree.Id),
// Projections.Property<Tree>(y => y.Id)))
.Select(x => x.Id);
return this.NHibernateSession.QueryOver<Tree>()
.Where(x => x.Id.IsIn(ListOfTreeId))
.WithSubquery.WhereExists<Apple>(qapple)
.SelectList(list => list
.Select(z => z.Id).WithAlias(() => treeitem.Id)
.Select(z => z.Name).WithAlias(() => treeitem.Name)
.Select(z => z.Type).WithAlias(() => critem.Type)
.TransformUsing(Transformers.AliasToBean<Tree>())
.List<T>();
And the pseudo SQL I get is something like this:
SELECT id, name, type FROM trees WHERE id IN (1, 2, 3)
AND EXIST(SELECT id FROM apples WHERE NOT rotten AND apples.idtree = apples.id)
As you can see there's a problem with the subquery that use the same table Id instead of something like that:
EXIST(SELECT id FROM apples WHERE NOT rotten AND apples.idtree = tree.id)
I'm bit lost actually. Maybe there's another way to build this up.
Any help is welcome, thanks.
Im not sure why you are using resulttransformer when the return type is the same as the query type
return NHibernateSession.QueryOver<Tree>()
.Where(t => t.Id.IsIn(ListOfTreeId))
.JoinQueryOver<Apple>(t => t.Apples)
.Where(a => !a.IsRotten)
.List();
Update: the Compiler chooses ICollection<Apple> while it really should choose Apple therefor specify the generic argument in JoinQueryOver explicitly
Update2: to get them unique
opt 1)
...
.SetResultTransformer(Transformers.DistinctRootEntity());
.List();
opt 2)
Tree treeAlias = null;
var nonRottenApples = QueryOver.Of<Apple>()
.Where(a => !a.IsRotten)
.Where(a => a.Tree.Id == treeAlias.Id)
.Select(x => x.Id); <- optional
return NHibernateSession.QueryOver(() => treeAlias)
.Where(t => t.Id.IsIn(ListOfTreeId))
.WithSubquery.WhereExists(nonRottenApples)
.List();

Categories