Optimize linq query by storing value in select - c#

I have problem with linq query. In Select I am getting the same item twice which makes code execution much longer than I can afford. Is there any way to store x.OrderByDescending(z => z.Date).FirstOrDefault() item inside Select query?
Execution time: 180 ms
var groups = dataContext.History
.GroupBy(a => new { a.BankName, a.AccountNo })
.Select(x => new HistoryReportItem
{
AccountNo = x.FirstOrDefault().AccountNo,
BankName = x.FirstOrDefault().BankName,
IsActive = x.FirstOrDefault().IncludeInCheck,
})
.ToList();
Execution time: 1200 ms
var groups = dataContext.History
.GroupBy(a => new { a.BankName, a.AccountNo })
.Select(x => new HistoryReportItem
{
AccountNo = x.FirstOrDefault().AccountNo,
BankName = x.FirstOrDefault().BankName,
IsActive = x.FirstOrDefault().IncludeInCheck,
LastDate = x.OrderByDescending(z => z.Date).FirstOrDefault().Date,
})
.ToList();
Execution time: 2400 ms
var groups = dataContext.History
.GroupBy(a => new { a.BankName, a.AccountNo })
.Select(x => new HistoryReportItem
{
AccountNo = x.FirstOrDefault().AccountNo,
BankName = x.FirstOrDefault().BankName,
IsActive = x.FirstOrDefault().IncludeInCheck,
LastDate = x.OrderByDescending(z => z.Date).FirstOrDefault().Date,
DataItemsCount = x.OrderByDescending(z => z.Date).FirstOrDefault().CountItemsSend
})
.ToList();

You can try doing the select in two steps:
var groups = dataContext.History
.GroupBy(a => new { a.BankName, a.AccountNo })
.Select(x => new
{
first = x.FirstOrDefault();
lastDate = x.OrderByDescending(z => z.Date).FirstOrDefault();
}
.Select(x => new HistoryReportItem
{
AccountNo = x.first.AccountNo,
BankName = x.first.BankName,
IsActive = x.first.IncludeInCheck,
LastDate = x.lastDate.Date,
DataItemsCount = x.lastDate.CountItemsSend
})
.ToList();
If this fails, it might be because the engine can't convert it completely to SQL, and you can try adding an AsEnumerable() between the two Selects.

Related

How do I create groups and subgroup1 and subgroup2 Use linq

How do I create groups and subgroup1 and subgroup2 Use linq.
Example of this picture
I want to create json.
Example of this picture.
I tried to do this but there was a problem.
The items are repeated within one subgroup2.
var list = result
.GroupBy(x => new { x.GroupId, x.GroupName })
.Select(g => new
{
ID = g.Key.GroupId,
Name = g.Key.GroupName,
SubGroup1 = g.GroupBy(x => new { x.SubGroupID1, x.SubGroupName1 })
.Select(cg => new
{
ID = cg.Key.SubGroupID1,
Name = cg.Key.SubGroupName1,
SubGroup2 = g.GroupBy(x => new { x.SubGroupID2, x.SubGroupName2 })
.Select(ii => new
{
ID = ii.Key.SubGroupID2,
Name = ii.Key.SubGroupName2,
item = ii.GroupBy(x => new { x.Stock_Id, x.Stock_Name, x.Prices, x.ScreenNumber })
.Select(oo => new
{
Stock_Id = oo.Key.Stock_Id,
Stock_Name = oo.Key.Stock_Name,
Prices = oo.Key.Prices,
ScreenNumber = oo.Key.ScreenNumber
}).OrderBy(Or => Or.Stock_Id)
.ToList()
}).OrderBy(Or => Or.ID)
.ToList()
}).OrderBy(Or => Or.ID)
.ToList()
}).OrderBy(Or => Or.ID)
.ToList();
Your query could be a lot cleaner if you grouped the groups up front, then project out to your desired results.
var query =
from x in data
group new { x.StockId, x.StockName, x.Prices, x.ScreenNumber }
by new { x.GroupId, x.GroupName, x.SubGroupId1, x.SubGroupName1, x.SubGroupId2, x.SubGroupName2 }
into g
group g
by new { g.Key.GroupId, g.Key.GroupName, g.Key.SubGroupId1, g.Key.SubGroupName1 }
into g2
group g2
by new { g2.Key.GroupId, g2.Key.GroupName }
into g1
select new
{
Id = g1.Key.GroupId,
Name = g1.Key.GroupName,
SubGroup1 = g1.Select(g2 => new
{
Id = g2.Key.SubGroupId1,
Name = g2.Key.SubGroupName1,
SubGroup2 = g2.Select(g => new
{
Id = g.Key.SubGroupId2,
Name = g.Key.SubGroupName2,
Items = g.Select(x => new
{
x.StockId,
x.StockName,
x.Prices,
x.ScreenNumber,
}),
}),
}),
};
The idea is to start off with the most specific grouping first, then one-by-one group the groups by the next layer, and so on.
SubGroup2 = g.GroupBy(x => new { x.SubGroupID2, x.SubGroupName2 })
You are grouping g instead of cg.
I suggest structuring your code a bit, which would help avoiding this kind of mistake.

How can I improve this LINQ query?

I fear that I'm doing n+1 query here, how can I improve this?
var inventories = AppContext.Inventories
.GroupBy(i => new { i.LocationId, i.ProductId })
.Select(g => new InventoryAvailableQuantity
{
ProductId = g.Key.ProductId,
LocationId = g.Key.LocationId,
Product = g.FirstOrDefault().Product.Name,
Location = g.FirstOrDefault().Location.Name,
PurchasePrice = AppContext.Inventories.Where(i => i.ProductId == g.Key.ProductId).OrderByDescending(i => i.DateAdded).FirstOrDefault().PurchasePrice,
ResellerPrice = AppContext.Inventories.Where(i => i.ProductId == g.Key.ProductId).OrderByDescending(i => i.DateAdded).FirstOrDefault().ResellerPrice,
RetailPrice = AppContext.Inventories.Where(i => i.ProductId == g.Key.ProductId).OrderByDescending(i => i.DateAdded).FirstOrDefault().RetailPrice
}).ToList();
You can use comprehension instead of method and gain the ability to use "let":
var inventories = from inv in AppContext.Inventories
group inv by new { i.LocationId, i.ProductId } into g
let firstInv = g.FirstOrDefault()
let firstPur = AppContext.Inventories
.Where(i => i.ProductId == g.Key.ProductId)
.OrderByDescending(i => i.DateAdded)
.FirstOrDefault()
select new InventoryAvailableQuantity
{
ProductId = g.Key.ProductId,
LocationId = g.Key.LocationId,
Product = firstInv.Product.Name,
Location = firstInv.Location.Name,
PurchasePrice = firstPur.PurchasePrice,
ResellerPrice = firstPur.ResellerPrice,
RetailPrice = firstPur.RetailPrice
}; // ( select ... { ... }).ToList(); if you will
Fast answer
var inventories = Inventories
.GroupBy(i => new {i.LocationId, i.ProductId})
.Select(g => new
{
g.Key.ProductId,
g.Key.LocationId,
CurrentInventories = g.FirstOrDefault(),
LastInventories = Inventories.Where(i => i.ProductId == g.Key.ProductId).OrderByDescending(i => i.DateAdded).FirstOrDefault()
})
.Select(g => new InventoryAvailableQuantity
{
ProductId = g.ProductId,
LocationId = g.LocationId,
Product = g.CurrentInventories.Product.Name,
Location = g.CurrentInventories.Location.Name,
PurchasePrice = g.LastInventories.PurchasePrice,
ResellerPrice = g.LastInventories.ResellerPrice,
RetailPrice = g.LastInventories.RetailPrice
})
.ToList();
You can take last item after grouping and take what you want.

Linq group by with parent object

How do I group so that I don't loose the parent identifier.
I have the following
var grouped = mymodel.GroupBy(l => new { l.AddressId })
.Select(g => new
{
AddressId = g.Key.AddressId,
Quotes = g.SelectMany(x => x.Quotes).ToList(),
}).ToList();
this returns
{ AddressId1, [Quote1, Quote2, Quote3...]}
{ AddressId2, [Quote12, Quote5, Quote8...]}
Now I would like to group these by Quote.Code and Quote.Currency, So that Each address has 1 Object-Quote (that is if all 4 quotes belonging to the address have the same Code and Currency). I would like the sum of Currency in that object.
This works, but I can't get how to add Address to this result:
var test = grouped.SelectMany(y => y.Quotes).GroupBy(x => new { x.Code, x.Currency }).Select(g => new
{
test = g.Key.ToString()
});}
this gives compile error, whenever i try to add AddressId to result:
var test1 = grouped.SelectMany(y => y.Quotes, (parent, child) => new { parent.AddressId, child }).GroupBy(x => new { x.Provider, x.Code, x.Currency, x.OriginalCurrency }).Select(g => new
{
test = g.Key.ToString(),
Sum = g.Sum(x => x.Price)
});
compiler error as well:
var test1 = grouped.Select(x => new { x.AddressId, x.Quotes.GroupBy(y => new { y.Provider, y.Code, y.Currency, y.OriginalCurrency }).Select(g => new
{
addr = x.AddressId,
test = g.Key.ToString(),
Sum = g.Sum(q => q.Price)
};
I would do that this way:
var grouped = mymodel.GroupBy(l => new { l.AddressId })
.Select(g => new
{
AddressId = g.Key.AddressId,
QuotesByCode = g.SelectMany(x => x.Quotes)
.GroupBy(x=>x.Code)
.Select(grp=>new
{
Code = grp.Key.Code,
SumOfCurrency=grp.Sum(z=>z.Currency)
}).ToList(),
}).ToList();

Linq - Get Max date from resultset

I need to convert the following SQL query to Linq :-
SELECT CODE,SCODE,MAX(SDATE) AS SDATE FROM SHIFTSCHEDULE
WHERE COMPANY = 'ABC'
GROUP BY CODE,SCODE
ORDER BY MAX(SDATE)
DESC
So far, I have tried this :-
var data = ctx.ShiftSchedule.Where(m =>
m.Company == company && m.EmployeeId == item.EmployeeId
)
.GroupBy(m =>
new
{
m.EmployeeId,
m.ShiftId
})
.Select(m =>
new
{
EmployeeId = m.Key.EmployeeId,
ShiftCode = m.Key.ShiftId,
ShiftDate = m.Max(gg => gg.ShiftDate)
}).ToList();
The results i get are :-
Now what i want is to get record or item in this result set which is MaxDate. In the above image the MaxDate is 1st record.
How to get the MAXDATE from the resultset?
This should work:-
var data = ctx.ShiftSchedule.Where(x => x.Company == company
&& x.EmployeeId == item.EmployeeId)
.GroupBy(x => new { x.CODE, x.SCODE })
.Select(x => new
{
CODE = x.Key.CODE,
SCODE = x.Key.SCODE,
SDATE = x.Max(z => z.SDATE)
})
.OrderByDescending(x => x.SDATE).FirstOrDefault();
You can order the resulting collection and fetch the first object using FirstOrDefault.
If you want just MAXDATE, you can only project that.
Just add .OrderByDescending(x => x.ShiftDate).First(); at the end.
OrderByDescending date and then take .First()
var data = ctx.ShiftSchedule.Where(m =>
m.Company == company && m.EmployeeId == item.EmployeeId
)
.GroupBy(m =>
new
{
m.EmployeeId,
m.ShiftId
})
.Select(m =>
new
{
EmployeeId = m.Key.EmployeeId,
ShiftCode = m.Key.ShiftId,
ShiftDate = m.Max(gg => gg.ShiftDate)
}).ToList().OrderByDescending(x => x.ShiftDate).First();

The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities

var residenceRep =
ctx.ShiftEmployees
.Include(s => s.UserData.NAME)
.Include(s => s.ResidenceShift.shiftName)
.Join(ctx.calc,
sh => new { sh.empNum, sh.dayDate },
o => new { empNum = o.emp_num, dayDate = o.trans_date },
(sh, o) => new { sh, o })
.Where(s => s.sh.recordId == recordId && s.o.day_flag.Contains("R1"))
.OrderBy(r => r.sh.dayDate)
.Select(r => new
{
dayDate = r.sh.dayDate,
empNum = r.sh.empNum,
empName = r.sh.UserData.NAME,
shiftId = r.sh.shiftId,
shiftName = r.sh.ResidenceShift.shiftName,
recordId,
dayState = r.o.day_desc.Split('[', ']')[1]
}).ToList();
I get an exception :
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to
Entities
How i could find an alternative to Split('[', ']')[1] in this query
You must commit the query and do the split after loading the data:
var residenceRep =
ctx.ShiftEmployees
.Include(s => s.UserData.NAME)
.Include(s => s.ResidenceShift.shiftName)
.Join(ctx.calc,
sh => new { sh.empNum, sh.dayDate },
o => new { empNum = o.emp_num, dayDate = o.trans_date },
(sh, o) => new { sh, o })
.Where(s => s.sh.recordId == recordId && s.o.day_flag.Contains("R1"))
.OrderBy(r => r.sh.dayDate)
.Select(r => new
{
dayDate = r.sh.dayDate,
empNum = r.sh.empNum,
empName = r.sh.UserData.NAME,
shiftId = r.sh.shiftId,
shiftName = r.sh.ResidenceShift.shiftName,
recordId = r.sh.recordId,
dayState = r.o.day_desc,
})
.ToList()//Here we commit the query and load data
.Select(x=> {
var parts = x.dayState.Split('[', ']');
return new {
x.dayDate,
x.empNum,
x.empName,
x.shiftId,
x.shiftName,
x.recordId,
dayState = parts.Length > 1 ?parts[1]:"",
};
})
.ToList();
I had this Issue and the approach that I've chose was that get all element I wanted and save them into a List and then filter the actual data on that list.
I know this is not the best answer but it worked for me.

Categories