How can I do a select following a select in LINQ - c#

Here's the code I have:
var a = Times
.Where(x => Int32.Parse(x.DateYYMMDD) > Int32.Parse("200309"))
.GroupBy(x => x.DateYYMMDD)
.OrderBy(g => g.Key)
.Select(g => new ScreenTimeModel
{
DateYYMMDD = $"20{g.Key.Substring(0, 2)}/{g.Key.Substring(2, 2)}/{g.Key.Substring(4, 2)}"
})
.Select(g => new ScreenTimeModel
{
DateYYMMDD = (g => g.DateYYMMDD),
})
It's simplified but what I would like to do is some processing of the value of DateYYMMDD that's put into the first .Select.
I tried to get that data with this (g => g.DateYYMMDD) but it's not working.
Can someone tell me how I can get references to the DateYYMMDD in the first select?

Try to remove the first Select method and update the second a little bit, to get a ScreenTimeModel with DateYYMMDD assigned to the group key
.Select(g => new ScreenTimeModel
{
DateYYMMDD = g.DateYYMMDD,
}
in your current code the first Select get the IGrouping<TKey,TElement> as g parameter, in the second Select parameter g is ScreenTimeModel. I don't think that you will need to call it twice

Related

GroupBy in subquery with MAX() in C#

I Have this code. It works fine but when I have two same maximal values it appear 2 times. So I need to use OrderBy. But I dont know how. Thanks for any help.
IQueryable<PerformanceRealization> pr = _context.PerformanceRealization
.Where(u => u.Deadline == _context.PerformanceRealization
.Where(x => x.GroupRealizationId == u.GroupRealizationId)
.Max(x => x.Deadline)
)
.Select(u => u);
Here is the SQL code with GROUP BY
SELECT PR.GroupRealizationId
FROM Stores.PerformanceRealization PR
LEFT JOIN Stores.GroupRealization ON Stores.GroupRealization.Id = PR.GroupRealizationId
WHERE PR.Deadline = (SELECT MAX(Deadline)
FROM Stores.PerformanceRealization PR2
WHERE PR.GroupRealizationId = PR2.GroupRealizationId)
GROUP BY PR.GroupRealizationId
You can select the first object from the group
IQueryable<PerformanceRealization> pr2 = pr
.GroupBy(x => x.GroupRealizationId)
.Select(g => g.First());
If you need a specific object from the group, then you can order by another column
IQueryable<PerformanceRealization> pr2 = pr
.GroupBy(x => x.GroupRealizationId)
.Select(g => g.OrderBy(x => x.SomeColumn).First());
for SomeColumn having the smallest value. For the greatest value, use OderByDescending instead.
Of course, you can integrate this approach into the first query:
IQueryable<PerformanceRealization> pr = _context.PerformanceRealization
.Where(u => u.Deadline == _context.PerformanceRealization
.Where(x => x.GroupRealizationId == u.GroupRealizationId)
.Max(x => x.Deadline)
)
.GroupBy(x => x.GroupRealizationId)
.Select(g => g.OrderBy(x => x.SomeColumn).First());
Note, you don't need to have a Select at the end like .Select(u => u). Since it has no effect, you can just drop it.
If your EF Core version cannot handle it (as revealed in a comment), then transition to LINQ-to-Objects with AsEnumerable(), but do the filtering in EF Core to minimize the number of records sent to the front-end:
IQueryable<PerformanceRealization> pr = _context.PerformanceRealization
.Where(u => u.Deadline == _context.PerformanceRealization
.Where(x => x.GroupRealizationId == u.GroupRealizationId)
.Max(x => x.Deadline)
)
.AsEnumerable() // <===== transition from LINQ-to-EF-Core to LINQ-to-Objects
.GroupBy(x => x.GroupRealizationId)
.Select(g => g.OrderBy(x => x.SomeColumn).First());

EF Core LINQ GROUPBY Then Select to get more than one properties of the entity

I have 2 tables Outlet and Order with below schemas:
Outlet Order
------ -------------------
Id Id
Name Name
OrderCompletedTime
NextOrderDueTime
OutletIds
Earlier when I wanted to get the NextOrderDueTime for each outlet using entity framework core, I did:
return _dbAccessor.RequestContext.MyDbContext.Order
.Where(i => i.OutletId == _dbAccessor.RequestContext.OutletId &&
!i.IsRemoved && i.NextOrderDueTime.HasValue)
.GroupBy(i => i.OutletId)
.Select(g => new { OutletId = g.Key, NextOrderDueTime = g.Min(x => x.NextOrderDueTime) })
.ToDictionary(i => i.OutletId, i => i.NextOrderDueTime);
Now on the UI we need to make this due time as link and wants user to get navigated to that Order details page based on order id
How can I change the above query to also return OrderId along with time?
My thoughts:
Change return type of method from Dictionary<int, DateTimeOffset?> to Dictionary<int, Tuple<int,DateTimeOffset?>>
I tried changing the Linq query to :
return _dbAccessor.RequestContext.MyDbContext.Order
.Where(i => i.OutletId == _dbAccessor.RequestContext.OutletId &&
!i.IsRemoved && i.NextOrderDueTime.HasValue)
.GroupBy(i => i.OutletId)
.Select(g =>
new
{
OutletId = g.Key,
NextOrderDueTime = g.FirstOrDefault(x => x.NextOrderDueTime == g.Min(y => y.NextOrderDueTime)).NextOrderDueTime,
NextOrderId = g.FirstOrDefault(x => x.NextOrderDueTime == g.Min(y => y.NextOrderDueTime)).OrderId
})
.ToDictionary(i => i.OutletId, i => new Tuple<int, DateTimeOffset?>(i.NextOrderId, i.NextOrderDueTime));
But this throws exception at runtime?
Please help to let me know what I am doing wrong here.
You could just take the entire order along with the outletid when you return:
.Select(g => new {
OutletId = g.Key,
NextOrder = g.OrderBy(x => x.NextOrderDueTime).FirstOrDefault()
})
You could select on this to take multiple properties from the order:
.Select(g => new {
OutletId = g.Key,
NextOrder = g.OrderBy(x => x.NextOrderDueTime).FirstOrDefault()
})
.Select(s => new {
s.OutletId,
NextOrderId = NextOrder.Id,
NextOrder.NextOrderDueTime,
NextOrderName = NextOrder.Name
})
etc..
The main thing to appreciate is that grouping gives you an object that has a key, but itself is a list of all things that have that key, so if you order the list by something like the DUeDat and take the first thing then you have an entire object with the lowest duedate from which you can take various things
The .GroupBy(...).Select(...).ToDictionary(...); cannot be converted to SQL since EF Core 3.0.
Due to the breaking change in EF Core 3.0. https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes , EF Core 3.0 will throw exception to make sure you know that all records in Order will be fetched from database before grouping and map to Dictionary.
I was able to get my query working as below:
return _dbAccessor.RequestContext.MyDbContext.Order
.Where(i => i.OutletId == _dbAccessor.RequestContext.OutletId &&
!i.IsRemoved && i.NextOrderDueTime.HasValue).AsEnumerable()
.GroupBy(i => i.OutletId)
.Select(g =>
new
{
OutletId = g.Key,
NextOrderDueTime = g.FirstOrDefault(x => x.NextOrderDueTime == g.Min(y => y.NextOrderDueTime)).NextOrderDueTime,
NextOrderId = g.FirstOrDefault(x => x.NextOrderDueTime == g.Min(y => y.NextOrderDueTime)).OrderId
})
.ToDictionary(i => i.OutletId, i => new Tuple<int, DateTimeOffset?>(i.NextOrderId, i.NextOrderDueTime));
The same can be done as shown in another answer by just adding AsEnumerable() before GroupBy:
_dbAccessor.RequestContext.MyDbContext.Order
.Where(i => i.OutletId == _dbAccessor.RequestContext.OutletId &&
!i.IsRemoved && i.NextOrderDueTime.HasValue).AsEnumerable()
.GroupBy(i => i.OutletId)
.Select(g => new {
OutletId = g.Key,
NextOrder = g.OrderBy(x => x.NextOrderDueTime).FirstOrDefault()
})
.Select(s => new {
s.OutletId,
NextOrderId = NextOrder.Id,
NextOrder.NextOrderDueTime,
NextOrderName = NextOrder.Name
})`enter code here`;

LINQ Lambda Improvement

I have a line like so:
var lstOfIds = db.TBL_AssocIncidentSpecialCat
.Where(x => x.IncidentId == incidentVm.ID)
.Select(t => t.SpecialCategoriesId)
.ToList();
This line gathers me a list of of the SpecialCategoriesIds. Then I have to do this:
incidentVm.LstSpecialCategories = db.TBL_SpecialCategories
.Where(x => lstOfIds.Contains(x.Id))
.Select(t => t.SpecialCategory)
.ToList();
Is there a way to combine these two lines into one? Even though it's only two lines of code.. I feel as though having to grab the Ids first then having to grab the associated property based on the Id is just an extra step and could be shortened to just one line. But I may be wrong.
Any help is appreciated.
UPDATE
incidentVm.LstSpecialCategories = db.TBL_AssocIncidentSpecialCat
.Where(x => x.IncidentId == incidentVm.ID)
.Join(
db.TBL_SpecialCategories,
x => new{Id = x.SpecialCategoriesId},
t => new{Id = t.Id},
(x,t) => {return t.SpecialCategory}
);
I am getting red squiggly under last part in Join:
A lambda expression with a statement body cannot be converted to an expression tree
You can combine the two lines using Join. Something like,
var result = db.TBL_AssocIncidentSpecialCat
.Join(
db.TBL_SpecialCategories,
ais => new { Id = ais.IncidentId },
sc => new { Id = sc.Id },
(ais, sc) => { return sc; }
)
.ToList();
C# Fiddle for this.
Update with Where Clause: You should use your Where condition after the Join.
var result = db.TBL_AssocIncidentSpecialCat
.Join(
db.TBL_SpecialCategories,
ais => new { Id = ais.IncidentId },
sc => new { Id = sc.Id },
(ais, sc) => new { ais = ais, sc = sc }
)
.Where(x => x.ais.IncidentId == 1)
.Select(y => y.sc)
.ToList();
You can try a LINQ query-style join:
incidentVm.LstSpecialCategories = (from aispc in db.TBL_AssocIncidentSpecialCat
join spc in db.TBL_SpecialCategories
on aispc.SpecialCategoriesId equals lspc.Id
where aispc.IncidentId == incidentVm.ID
select lspc.SpecialCategory).ToList();
I was able to figure this out with the help of some answers and me testing it on my own. Here is my solution:
incidentVm.LstSpecialCategories = db.TBL_AssocIncidentSpecialCat
.Where(t => t.IncidentId == incidentVm.ID)
.Join(db.TBL_SpecialCategories,
ik => ik.SpecialCategoriesId,
ok => ok.Id,
(ik, ok) => ok.SpecialCategory
)
.ToList();
Thank you for all of your help.

LINQ: Select all from each group except the first item

It is easy to select the first of each group:
var firstOfEachGroup = dbContext.Measurements
.OrderByDescending(m => m.MeasurementId)
.GroupBy(m => new { m.SomeColumn })
.Where(g => g.Count() > 1)
.Select(g => g.First());
But...
Question: how can I select all from each group except the first item?
var everythingButFirstOfEachGroup = dbContext.Measurements
.OrderByDescending(m => m.MeasurementId)
.GroupBy(m => new { m.SomeColumn })
.Where(g => g.Count() > 1)
.Select( ...? );
Additional information:
My real goal is to delete all duplicates except the last (in a bulk way, ie: not using an in-memory foreach), so after the previous query I want to use RemoveRange:
dbContext.Measurements.RemoveRange(everythingButFirstOfEachGroup);
So, if my question has no sense, this information might be handy.
Use Skip(1) to skip the first record and select the rest.
Something like:
var firstOfEachGroup = dbContext.Measurements
.OrderByDescending(m => m.MeasurementId)
.GroupBy(m => new { m.SomeColumn })
.Where(g => g.Count() > 1)
.SelectMany(g => g.OrderByDescending(r => r.SomeColumn).Skip(1));
See: Enumerable.Skip
If you do not need a flattened collection then replace SelectMany with Select in code snippet.
IGrouping<K, V> implements IEnumerable<V>; you simply need to skip inside the select clause to apply it to each group:
.Select(g => g.Skip(1))
You can always use .Distinct() to remove duplicates; presumably sorting or reverse-sorting and then applying .distinct() would give you what you want.

Why does this combination of Select, Where and GroupBy cause an exception?

I have a simple table structure of services with each a number of facilities. In the database, this is a Service table and a Facility table, where the Facility table has a reference to a row in the Service table.
In our application, we have the following LINQ working:
Services
.Where(s => s.Facilities.Any(f => f.Name == "Sample"))
.GroupBy(s => s.Type)
.Select(g => new { Type = g.Key, Count = g.Count() })
But for reasons beyond my control, the source set is projected to a non-entity object before the Where call, in this way:
Services
.Select(s => new { Id = s.Id, Type = s.Type, Facilities = s.Facilities })
.Where(s => s.Facilities.Any(f => f.Name == "Sample"))
.GroupBy(s => s.Type)
.Select(g => new { Type = g.Key, Count = g.Count() })
But this raises the following exception, with no inner exception:
EntityCommandCompilationException: The nested query is not supported. Operation1='GroupBy' Operation2='MultiStreamNest'
Removing the Where, however, makes it work, which makes me believe it's only in this specific combination of method calls:
Services
.Select(s => new { Id = s.Id, Type = s.Type, Facilities = s.Facilities })
//.Where(s => s.Facilities.Any(f => f.Name == "Sample"))
.GroupBy(s => s.Type)
.Select(g => new { Type = g.Key, Count = g.Count() })
Is there a way to make the above work: select to an non-entity object, and then use Where and GroupBy on the resulting queryable? Adding ToList after the Select works, but the large source set makes this unfeasible (it would execute the query on the database and then do grouping logic in C#).
This exception originates from this piece of code in the EF source...
// <summary>
// Not Supported common processing
// For all those cases where we don't intend to support
// a nest operation as a child, we have this routine to
// do the work.
// </summary>
private Node NestingNotSupported(Op op, Node n)
{
// First, visit my children
VisitChildren(n);
m_varRemapper.RemapNode(n);
// Make sure we don't have a child that is a nest op.
foreach (var chi in n.Children)
{
if (IsNestOpNode(chi))
{
throw new NotSupportedException(Strings.ADP_NestingNotSupported(op.OpType.ToString(), chi.Op.OpType.ToString()));
}
}
return n;
}
I have to admit: it's not obvious what happens here and there's no technical design document disclosing all of EF's query building strategies. But this piece of code...
// We can only pull the nest over a Join/Apply if it has keys, so
// we can order things; if it doesn't have keys, we throw a NotSupported
// exception.
foreach (var chi in n.Children)
{
if (op.OpType != OpType.MultiStreamNest
&& chi.Op.IsRelOp)
{
var keys = Command.PullupKeys(chi);
if (null == keys
|| keys.NoKeys)
{
throw new NotSupportedException(Strings.ADP_KeysRequiredForJoinOverNest(op.OpType.ToString()));
}
}
}
Gives a little peek behind the curtains. I just tried an OrderBy in a case of my own that exactly reproduced yours, and it worked. So I'm pretty sure that if you do...
Services
.Select(s => new { Id = s.Id, Type = s.Type, Facilities = s.Facilities })
.OrderBy(x => x.Id)
.Where(s => s.Facilities.Any(f => f.Name == "Sample"))
.GroupBy(s => s.Type)
.Select(g => new { Type = g.Key, Count = g.Count() })
the exception will be gone.

Categories

Resources