Initially I have the following query in a function.
This query is used in the join in another query, which I didn't add due to its size.
var latestGuestRegistration = from c in Context.Guest
group c by c.PersonId
into cc
select new { PersonId = cc.Key, CreationTime = cc.Max(a => a.CreationTime) };
I would like to add a filter, however I could filter by name or email. So I would like to pass this filter as a function parameter.
I couldn't do this using C#'s inline SQL syntax, so I tried converting to LINQ:
var tmp = Context.GuestRegistrations
.Where(filter) // Here I filter by email or name
.GroupBy(x => x.PersonId)
.Select(cc => new { PersonId = cc.Key, CreationTime = cc.Max(a => a.CreationTime) })
.AsQueryable();
According to Rider, both implementations have the same data type, which is Queryable<{ID, CreationTime}>
// Joins above
join lt in tmp on
new { guestRegistration.PersonId, guestRegistration.CreationTime } equals
new { lt.PersonId, lt.CreationTime }
// Joins below
When this join is executed using my LINQ, it seems that it already returns the data, while the original solution enters as a subquery. How can I add a filter to my original query?
In the end, I would like something more or less like this:
Expression<Func<GuestRegistration,bool>> wherePredicate = registration => registration.FullName = "Gabriel";
var latestGuestRegistration = from c in Context.GuestRegistrations
where wherePredicate
group c by c.PersonId
into cc
select new { PersonId = cc.Key, CreationTime = cc.Max(a => a.CreationTime) };
Solution
Thanks to everyone who responded and commented, below is a summary of the final code.
private IQueryable<...> GetSearch(Expression<Func<G, bool>> filter) {
var filteredG= Context.G.Where(filter);
var latestG = from c in filteredG
group c by c.PersonId
into cc
select new { PersonId = cc.Key, CreationTime = cc.Max(a => a.CreationTime) };
var bigQuery = from ... join latestG ...
}
GetSearch(x => x.Fullname == "Gabriel")
Some expressions cannot be injected via Query syntax, but you can mix it with Method Chain syntax:
Expression<Func<GuestRegistration,bool>> wherePredicate = registration => registration.FullName = "Gabriel";
var guestRegistrations = Context.GuestRegistrations
.Where(wherePredicate);
var latestGuestRegistration =
from c in guestRegistrations
group c by c.PersonId
into cc
select new { PersonId = cc.Key, CreationTime = cc.Max(a => a.CreationTime) };
I have this query that I need to run:
IEnumerable<MerchantWithParameters> merchants =
from i in (
from m in d.GetTable<Merchant>()
join mtp in d.GetTable<MerchantToParameters>() on m.Id equals mtp.MerchantId into mtps
from mtp in mtps.DefaultIfEmpty()
join cp in d.GetTable<ContextParameters>() on mtp.ContextParametersId equals cp.Id into cps
from cp in cps.DefaultIfEmpty()
select new {Merchant = m, ContextParameter = cp}
)
group i by new { i.Merchant.Id } into ig
select new MerchantWithParameters()
{
Id = ig.Key.Id,
Parameters = ig.Where(g => g.ContextParameter != null).ToDictionary(g => g.ContextParameter.Key, g => g.ContextParameter.Text)
};
For some reason it takes really long time for this query to be completed.
I believe that it has something to do with
Parameters = ig.Where(g => g.ContextParameter != null).ToDictionary(g => g.ContextParameter.Key, g => g.ContextParameter.Text)
Because when I remove this line, query starts to execute really fast.
Could you please show me what am I doing wrong?
UPDATE:
I am using ToList() to extract data from the database.
It is known SQL limitation. You cannot get grouped items, only grouping key or aggregation result. Since you need all records, we can do grouping on the client side, but previously maximally limit retrieved data.
var query =
from m in d.GetTable<Merchant>()
from mtp in d.GetTable<MerchantToParameters>().LeftJoin(mtp => m.Id == mtp.MerchantId)
from cp in d.GetTable<ContextParameters>().LeftJoin(cp => mtp.ContextParametersId == cp.Id)
select new
{
MerchantId = m.Id,
ContextParameterKey = (int?)cp.Key,
ContextParameterText = cp.Text
};
var result =
from q in query.AsEnumerable()
group q by q.MerchantId into g
select new MerchantWithParameters
{
Id = g.Key,
Parameters = g.Where(g => g.ContextParameterKey != null)
.ToDictionary(g => g.ContextParameterKey.Value, g => g.ContextParameterText)
};
var merchants = result.ToList();
I have a rather complex LINQ query but that is the point of the question
var result = await _context.TblBreakpoints
.GroupBy(b => new { b.BpgroupId, b.ResultType, b.DrugId, b.Susceptible, b.LowIntermediate, b.Intermediate, b.Resistant})
.Join(_context.TblBreakpointgroups,
bg => bg.Key.BpgroupId,
g => g.BpgroupId,
(bg, g) => new
{
GroupId = bg.Key.BpgroupId,
DrugId = bg.Key.DrugId,
Susceptible = bg.Key.Susceptible,
LowIntermediate = bg.Key.LowIntermediate,
Intermediate = bg.Key.Intermediate,
Method = bg.Key.ResultType,
Resistant = bg.Key.Resistant,
StandardId = g.BpstandardId,
GroupName = g.BpgroupName,
Count = bg.Count(),
})
.Join(_context.TblBreakpointStandards,
i => i.StandardId,
j => j.BpstandardId,
(i, j) => new
{
Standard = j.Bpstandard,
GroupId = i.GroupId,
GroupName = i.GroupName,
Count = i.Count,
Method = i.Method,
DrugId = i.DrugId,
Susceptible = i.Susceptible,
LowIntermediate = i.LowIntermediate,
Intermediate = i.Intermediate,
Resistant = i.Resistant
})
.Join(_context.TblDrugs,
i => i.DrugId,
j => j.DrugId,
(i, j) => new
{
DrugName = j.DrugName,
Standard = i.Standard,
GroupId = i.GroupId,
GroupName = i.GroupName,
Count = i.Count,
Method = i.Method,
Susceptible = i.Susceptible,
LowIntermediate = i.LowIntermediate,
Intermediate = i.Intermediate,
Resistant = i.Resistant
})
.Join(_context.TblBreakpointgroupmembers,
i => i.GroupId,
j => j.BpgroupId,
(i, j) => new
{
OrganismId = j.OrganismId,
Standard = i.Standard,
GroupId = i.GroupId,
GroupName = i.GroupName,
Count = i.Count,
Method = i.Method,
DrugName = i.DrugName,
Susceptible = i.Susceptible,
LowIntermediate = i.LowIntermediate,
Intermediate = i.Intermediate,
Resistant = i.Resistant
})
.Join(_context.TblOrganismNames,
i => i.OrganismId,
j => j.OrganismId,
(i, j) => new BreakpointSummary
{
OrganismName = j.OrganismName,
Standard = i.Standard,
GroupName = i.GroupName,
Count = i.Count,
Method = i.Method,
DrugName = i.DrugName,
Susceptible = i.Susceptible,
LowIntermediate = i.LowIntermediate,
Intermediate = i.Intermediate,
Resistant = i.Resistant
})
.ToListAsync().ConfigureAwait(false);
From the query with each Join I keep passing the previous values and add the value(s) that come from the join. It is already tedious with just 5 joins, it would get even more so with more joins. Is there a better way that I am missing?
I think the equivalent SQL is
WITH bpg (BPGroupId, ResultType, DrugId, Susceptible, LowIntermediate, Intermediate, Resistant, Count)
AS (
SELECT BPGroupId, ResultType, DrugId, Susceptible, LowIntermediate, Intermediate, Resistant, COUNT(BPGroupId)
FROM dbo.tbl_Breakpoint a
GROUP BY BPGroupId,
ResultType,
DrugId,
Susceptible,
LowIntermediate,
Intermediate,
Resistant
)
SELECT a.BpgroupName, b.BPStandard, c.DrugName, e.OrganismName, CTE.ResultType, CTE.Susceptible, CTE.LowIntermediate, CTE.Intermediate, CTE.Resistant, CTE.Count
FROM dbo.tbl_breakpointgroup a
INNER JOIN bpg CTE ON a.BPGroupId = CTE.BPGroupId
INNER JOIN tbl_BreakpointStandard b ON b.BPStandardId = a.BPStandardId
INNER JOIN tbl_Drug c ON c.DrugID = CTE.DrugId
INNER JOIN tbl_breakpointgroupmember d ON d.BPGroupId = CTE.BPGroupId
INNER JOIN tbl_OrganismName e ON e.OrganismId = d.OrganismId
WHERE a.BPGroupId = CTE.BPGroupId
In general when using manual joins in LINQ, it's better to use the query syntax since it provides range variables (which correspond to table/query aliases in SQL) transparency. e.g. (in pseudo code)
from a in queryA
join b in queryB on ... // can use any field from a
join c in queryC on ... // can use any field from a and b
join d in queryD on ... // can use any field from a, b and c
...
select new
{
// can use any field for a, b, c, d etc.
}
The same with method syntax is a bit more complicated, but the principle is to wrap the previous "variables" in simple tuple like anonymous types until you get to the final projection, e.g. (in pseudo code)
queryA
.Join(queryB, a => {a fields}, b => {b fields), (a, b) => new { a, b }) // first result
.Join(queryC, r => {r.a, r.b fields), c => { c fields }, (r, c) => new { r.a, r.b, c } // second result
.Join(queryD, r => {r.a, r.b, r.c fields), d => { d fields }, (r, d) => new { r.a, r.b, r.c, d } // third result
...
.Select(r => new { r.a, r.b, r.c, r.d... fields });
Applying it to your example, the corresponding query syntax could be like (note that sub queries inside can use whatever syntax is appropriate):
var query =
from cte in _context.TblBreakpoints
.GroupBy(b => new { b.BpgroupId, b.ResultType, b.DrugId, b.Susceptible, b.LowIntermediate, b.Intermediate, b.Resistant})
.Select(g => new
{
g.Key.BpgroupId, g.Key.ResultType, g.Key.DrugId, g.Key.Susceptible, h.Key.LowIntermediate, g.Key.Intermediate, g.Key.Resistant,
Count = g.Count(),
})
join a in _context.TblBreakpointgroups on cte.BpgroupId equals a.BpgroupId
join b in _context.TblBreakpointStandards on a.BpstandardId equals b.BpstandardId
join c in _context.TblDrugs on cte.DrugId equals c.DrugId
join d in _context.TblBreakpointgroupmembers on cte.BpgroupId equals d.BpgroupId
join e in _context.TblOrganismNames on d.OrganismId equals e.OrganismId
select new BreakpointSummary
{
OrganismName = e.OrganismName,
Standard = b.Bpstandard,
GroupName = a.BpgroupName,
Count = cte.Count,
Method = cte.ResultType,
DrugName = d.DrugName,
Susceptible = cte.Susceptible,
LowIntermediate = cte.LowIntermediate,
Intermediate = cte.Intermediate,
Resistant = cte.Resistant,
};
You can convert it to method syntax using the aforementioned rules, but for me it doesn't worth the effort.
i have this inherited SQL view code that I converted to linq to get some data out in the meantime, in LINQPad it works as expected, but upon transferring it to my c# solution, the subcollections are not loaded.
var query = from poh in _pckOrderHeadeRepository.GetAllIncluding(pd => pd.PckOrderDetail)
join mcs in _mstrConsigneeShipToRepository.GetAll() on poh.RouteId equals mcs.Consignee
//select new {poh, mcs}; //works
join det in (
from d in _pckOrderDetailRepository.GetAllIncluding(pd=> pd.PckOrderHeader, pd => pd.MstrSku)
join s in (
from shpCartonHeader in _shpCartonHeaderRepository.GetAll()
group shpCartonHeader by new
{
shpCartonHeader.OrderNum
}
into g
select new
{
g.Key.OrderNum,
CartonWeight = g.Sum(p => p.TotalWeight)
}) on d.PckOrderHeader.OrderNum equals s.OrderNum into sJoin
from s in sJoin.DefaultIfEmpty()
group new {d.PckOrderHeader, d, d.MstrSku, s} by new
{
d.PckOrderHeader.OrderNum
}
into g
select new
{
g.Key.OrderNum,
OrderQty = g.Sum(p => p.d.OrderQty),
OrderWeightOpen = (decimal?)g.Where(p => p.d.PckOrderHeader.OrderStat.Equals("OPEN")).Sum(p => p.d.MstrSku.Weight * p.d.OrderQty),
OrderWeightReleased = (decimal?)g.Where(p => p.d.PckOrderHeader.OrderStat.Equals("RELEASED")).Sum(p => p.d.MstrSku.Weight * p.d.PickingQty + p.s.CartonWeight),
OrderWeightPacked = (decimal?)g.Where(p => p.d.PckOrderHeader.OrderStat.Equals("PACKED")).Sum(p => p.s.CartonWeight),
PrePack = g.Max(p => p.d.MstrSku.Prepack)
}) on poh.OrderNum equals det.OrderNum
join toa in _shpTrailerOrderAssignmentRepository.GetAll() on poh.OrderNum equals toa.OrderNum into
toaJoin
from toa in toaJoin.DefaultIfEmpty()
select new
{
det,
poh,
toa,
mcs
};
this part in particular:
select new
{
g.Key.OrderNum,
OrderQty = g.Sum(p => p.d.OrderQty),
OrderWeightOpen = (decimal?)g.Where(p => p.d.PckOrderHeader.OrderStat.Equals("OPEN")).Sum(p => p.d.MstrSku.Weight * p.d.OrderQty),
OrderWeightReleased = (decimal?)g.Where(p => p.d.PckOrderHeader.OrderStat.Equals("RELEASED")).Sum(p => p.d.MstrSku.Weight * p.d.PickingQty + p.s.CartonWeight),
OrderWeightPacked = (decimal?)g.Where(p => p.d.PckOrderHeader.OrderStat.Equals("PACKED")).Sum(p => p.s.CartonWeight),
PrePack = g.Max(p => p.d.MstrSku.Prepack)
}) on poh.OrderNum equals det.OrderNum
for example this:
PrePack = g.Max(p => p.d.MstrSku.Prepack) // MstrSku is not loaded
and the property inside p.d (PckOrderDetail)
public virtual MstrSku MstrSku { get; set; }
the equivalent query in linqpad works correctly, so im wondering what am I missing to properly load the sub properties to mimic LINQPads behaviour.
if you want to use p.d.MstrSku.Prepack then you have to include it in the outer table. when you include it in joined table you cannot select.
use it in the first line like the sample below
.Include(x => x.OrderDetails.Select(y => y.MstrSku)).ToList();
I have a long list with ships that I get from a Linq to SQL query, but I only want one row per ImoNo. Today i have about 4 rows per ImoNo. I just need the one row that has been last updated (so in this example I need 2013-01-27).
This is my Linq To SQL query:
var res = from positions in context.Lloyds_ETAs
join vessels in context.Lloyds_Vessels on positions.ImoNumber equals vessels.imo_no
select new PositionData {
ImoNo = positions.ImoNumber,
PositionCordinates = positions.AIS_Latest_Position,
CompassOverGround = positions.Compass_over_Ground_Heading,
VesselId = positions.Vessel_ID,
Equipment = vessels.vessel_type,
Updated = positions.Last_Place_Location
};
return res.ToList();
var res = (from positions in context.Lloyds_ETAs
join vessels in context.Lloyds_Vessels on positions.ImoNumber equals vessels.imo_no
select new PositionData {
ImoNo = positions.ImoNumber,
PositionCordinates = positions.AIS_Latest_Position,
CompassOverGround = positions.Compass_over_Ground_Heading,
VesselId = positions.Vessel_ID,
Equipment = vessels.vessel_type,
Updated = positions.Last_Place_Location
})
.GroupBy(x => x.ImoNo)
.Select(g => g.OrderByDescending(pd => pd.Updated).First());
If you want the last one, all you have to do is append .OrderBy(pd => pd.Updated).Last() after your select.
var res = (from positions in context.Lloyds_ETAs
join vessels in context.Lloyds_Vessels on positions.ImoNumber equals vessels.imo_no
select new PositionData {
ImoNo = positions.ImoNumber,
PositionCordinates = positions.AIS_Latest_Position,
CompassOverGround = positions.Compass_over_Ground_Heading,
VesselId = positions.Vessel_ID,
Equipment = vessels.vessel_type,
Updated = positions.Last_Place_Location
}).OrderBy(pd => pd.Updated).Last();
return res.ToList();
(yourQuery).OrderByDescending(pd=>pd.Updated).First()
There are several ways to get just one "row" as a result:
res.OrderByDescending(x => x.Updated).Take(1);
res.OrderByDescending(x => x.Updated).First();
res.Order(x => x.Updated).Last();
It seems that you have some duplication though, so maybe doing a group by would be more appropriate.