failing in result for left outer join c# - c#

I want to do a left outter join between two query and populate the result in new instance of object
this is the code for first query , which individually works fine, fetches the data
var finances = _context.Finances
.GroupBy(p => p.CustomerID)
.Select(m => new
{
CustomerID = m.Key,
Debtor = m.Sum(p => p.Debtor),
Creditor = m.Sum(p => p.Creditor),
}).ToList();
and second query , which individually works fine, fetches the data
var events = _context.Events
.Include(p=>p.Customer)
.Where(p=>p.Start.ToString().CompareTo(reqDate) >= 0)
.Select(p => new
{
CustomerID = p.CustomerID,
FullName =p.Customer.FirstName + ' ' +p.Customer.LastName,
End =p.End,
Start = p.Start,
}).ToList();
So the left outer join would be like this
var qry = events.GroupJoin(
finances,
ev1 => ev1.CustomerID,
fi1 => fi1.CustomerID,
(f, bs) => new { events = f, finances = bs })
.SelectMany(
eventsFinances => eventsFinances.finances.DefaultIfEmpty(),
(x, y) => new RequestEventsDto
{
CustomerID=x.events.CustomerID,
Start = x.events.Start,
End = x.events.End,
FullName = x.events.FullName,
Debtor = y.Debtor,
Creditor = y.Creditor,
});
the events table would populate the data but the finance table wouldn't ( which actuallu has data inside of it) and gives this error
"Object reference not set to an instance of an object."
if I comment these two lines form finance table ,it works fine the rest
Debtor = y.Debtor,
Creditor = y.Creditor,
which part of the left outer join has a problem?

try this
var var qry = from e in events
join f in finances on e.CustomerID equals f.CustomerID into fj
from f in fj.DefaultIfEmpty()
select new {
CustomerID=e.CustomerID,
Start =e.Start,
End = e.End,
FullName = e.FullName,
Debtor = f.Debtor,
Creditor = f.Creditor
};

Related

Pass where condition as parameter using LINQ expression query

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) };

Slow LINQ query because of Where+ToDictionary

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();

Simplify many LINQ Join

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.

NULL Subcollections in framework, OK in LINQPad

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();

Linq to SQL in C# Unique data

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.

Categories