.Net Core 3.1 Entity Framework Slow Query Problem - asp.net-core

There was slowness in my .net core webapi application, I reviewed and redesigned my queries to resolve this issue. The code snippet below was written to bring in sales, payments for sales, and sales territories.
I first include the condition query and then the sub-entities in the query. Then I choose which columns to work with ".Select()".
Then I want to combine this table with the name information in other tables. For example, information such as Customer Name, Product Name is in the other table.
The first block of code I share below is a slow running linq query.
result.Results =
(from s in context.Sales
join sa in context.SaleProductServiceAreas on s.SaleId equals sa.SaleId
join sp in context.SalePayments on s.SaleId equals sp.SaleId into spleft
from sp in spleft.DefaultIfEmpty()
join c in context.Customers on s.CustomerId equals c.CustomerId
join p in context.ProductServices on s.ProductServiceId equals p.ProductServiceId
where s.Date >= firstDayOfMonth && s.Date <= lastDayOfMonth
group s by
new
{
s.SaleId,
s.CustomerId,
CustomerNameSurname = c.Name + ' ' + c.Surname,
ProductServiceName = p.Name,
s.Date,
s.Total,
s.Session,
p.ProductServiceId
} into grp
select new SaleJoinDto()
{
SaleId = grp.Key.SaleId,
CustomerNameSurname = grp.Key.CustomerNameSurname,
ProductServiceName = grp.Key.ProductServiceName,
Total = grp.Key.Total,
Date = grp.Key.Date,
Session = grp.Key.Session,
CustomerId = grp.Key.CustomerId,
ProductServiceId = grp.Key.ProductServiceId,
SaleProductServiceAreas = (from sps in context.SaleProductServiceAreas
join spa in context.ProductServiceAreas on sps.ProductServiceAreaId equals spa.ProductServiceAreaId
where sps.SaleId == grp.Key.SaleId
select new SaleProductServiceAreaJoinDto()
{
SaleProductServiceAreaId = sps.SaleProductServiceAreaId,
ProductServiceAreaName = spa.Name,
ProductServiceAreaId = sps.ProductServiceAreaId,
SaleId = sps.SaleId
}).ToList(),
SalePayments = (from spp in context.SalePayments
where spp.SaleId == grp.Key.SaleId
select new SalePaymentDto()
{
SaleId = spp.SaleId,
SalePaymentId = spp.SalePaymentId,
Total = spp.Total,
PaymentMethod = spp.PaymentMethod,
Date = spp.Date
}).ToList()
}).ToList();
["NEW"] This query I wrote is the query I rewrite as a result of the articles I have shared below.
Document of Microsoft Link for query
using (var context = new AppDbContext())
{
var result = new PagedResult<SaleJoinDto>();
result.CurrentPage = pageNumber;
result.PageSize = pageSize;
var pageCount = (double)result.RowCount / pageSize;
result.PageCount = (int)Math.Ceiling(pageCount);
var skip = (pageNumber - 1) * pageSize;
var firstDayOfMonth = new DateTime(date.Year, date.Month, 1);
var lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1);
result.Results = await context.Sales
.Where(x => x.Date >= firstDayOfMonth && x.Date <= lastDayOfMonth)
.Include(x => x.SalePayments)
.Include(x => x.SaleProductServiceAreas)
.Select(s => new Sale()
{
SaleId = s.SaleId,
CustomerId = s.CustomerId,
Date = s.Date,
Total = s.Total,
Session = s.Session,
SalePayments = s.SalePayments,
SaleProductServiceAreas = s.SaleProductServiceAreas
})
.Join(context.Customers, sales => sales.CustomerId, customers => customers.CustomerId, (sales, customers) => new
{
sales,
customers
})
.Join(context.ProductServices, combinedSaleAndCus => combinedSaleAndCus.sales.ProductServiceId, product => product.ProductServiceId, (combinedSaleAndCus, product) => new SaleJoinDto()
{
SaleId = combinedSaleAndCus.sales.SaleId,
CustomerId = combinedSaleAndCus.sales.CustomerId,
Date = combinedSaleAndCus.sales.Date,
Total = combinedSaleAndCus.sales.Total,
Session = combinedSaleAndCus.sales.Session,
CustomerNameSurname = combinedSaleAndCus.customers.Name,
SalePayments = combinedSaleAndCus.sales.SalePayments.Select(x => new SalePaymentDto()
{
Date = x.Date,
PaymentMethod = x.PaymentMethod,
SaleId = x.SaleId,
SalePaymentId = x.SalePaymentId,
Total = x.Total
}).ToList(),
SaleProductServiceAreas = combinedSaleAndCus.sales.SaleProductServiceAreas.Join(context.ProductServiceAreas, sp => sp.ProductServiceAreaId, psa => psa.ProductServiceAreaId, (sp, psa) => new SaleProductServiceAreaJoinDto()
{
SaleProductServiceAreaId = sp.SaleProductServiceAreaId,
ProductServiceAreaName = psa.Name
}).ToList()
})
.ToListAsync();
result.RowCount = result.Results.Count();
result.Results = result.Results.OrderByDescending(x => x.Date).Skip(skip).Take(pageSize).ToList();
return result;
}
The problem is I do get the following error when I rewrite it according to this new query.
Where am I making a mistake in the query I just wrote?
{"The LINQ expression 'DbSet\r\n .Where(s => s.Date >=
__firstDayOfMonth_0 && s.Date <= __lastDayOfMonth_1)\r\n .Join(\r\n outer: DbSet, \r\n inner: s => s.CustomerId, \r\n
outerKeySelector: c => c.CustomerId, \r\n innerKeySelector: (s,
c) => new TransparentIdentifier<Sale, Customer>(\r\n Outer
= s, \r\n Inner = c\r\n ))\r\n .Join(\r\n outer: DbSet, \r\n inner: ti => new Sale{ \r\n
SaleId = ti.Outer.SaleId, \r\n CustomerId =
ti.Outer.CustomerId, \r\n Date = ti.Outer.Date, \r\n
Total = ti.Outer.Total, \r\n Session = ti.Outer.Session,
\r\n SalePayments = (MaterializeCollectionNavigation(\r\n
navigation: Navigation: Sale.SalePayments,\r\n
subquery: DbSet\r\n .Where(s0 =>
EF.Property(ti.Outer, "SaleId") != null &&
EF.Property(ti.Outer, "SaleId") == EF.Property(s0,
"SaleId"))), \r\n SaleProductServiceAreas =
(MaterializeCollectionNavigation(\r\n navigation:
Navigation: Sale.SaleProductServiceAreas,\r\n subquery:
DbSet\r\n .Where(s1 =>
EF.Property(ti.Outer, "SaleId") != null &&
EF.Property(ti.Outer, "SaleId") == EF.Property(s1,
"SaleId"))) \r\n }\r\n .ProductServiceId, \r\n
outerKeySelector: p => p.ProductServiceId, \r\n
innerKeySelector: (ti, p) => new
TransparentIdentifier<TransparentIdentifier<Sale, Customer>,
ProductService>(\r\n Outer = ti, \r\n Inner =
p\r\n ))'
could not be translated. Either rewrite the query in
a form that can be translated, or switch to client evaluation
explicitly by inserting a call to either AsEnumerable(),
AsAsyncEnumerable(), ToList(), or ToListAsync(). See
https://go.microsoft.com/fwlink/?linkid=2101038 for more
information."}

Removed not needed joins and grouping. It should be faster.
var query =
from s in context.Sales
join c in context.Customers on s.CustomerId equals c.CustomerId
join p in context.ProductServices on s.ProductServiceId equals p.ProductServiceId
where s.Date >= firstDayOfMonth && s.Date <= lastDayOfMonth
select new SaleJoinDto()
{
SaleId = s.SaleId,
CustomerNameSurname = c.Name + ' ' + c.Surname,
ProductServiceName = p.ProductServiceName,
Total = s.Total,
Date = s.Date,
Session = s.Session,
CustomerId = s.CustomerId,
ProductServiceId = p.ProductServiceId,
SaleProductServiceAreas = (from sps in context.SaleProductServiceAreas
join spa in context.ProductServiceAreas on sps.ProductServiceAreaId equals spa.ProductServiceAreaId
where sps.SaleId == s.SaleId
select new SaleProductServiceAreaJoinDto()
{
SaleProductServiceAreaId = sps.SaleProductServiceAreaId,
ProductServiceAreaName = spa.Name,
ProductServiceAreaId = sps.ProductServiceAreaId,
SaleId = sps.SaleId
}).ToList(),
SalePayments = (from spp in context.SalePayments
where spp.SaleId == s.SaleId
select new SalePaymentDto()
{
SaleId = spp.SaleId,
SalePaymentId = spp.SalePaymentId,
Total = spp.Total,
PaymentMethod = spp.PaymentMethod,
Date = spp.Date
}).ToList()
};
result.Results = query.ToList();
Anyway, even joins can be simplified if you have navigation properties which are not shown in original question.

I fixed the slow responsiveness of the app.
I was running the application on windows host. I changed my hosting to Centos 7 which is a linux distribution. Then when I run the application on Centos 7, the application accelerated perfectly and took flight.
My advice to all .net Core app developers, migrate your apps to linux. It is built to run on .net core linux distributions.
I share the problems I encountered while migrating the application to linux and how I solved it in the link below.
.Net Core 3.1 deploy on Centos 7

Related

I have a SQL query and I want to convert it to linq

I have a SQL query which I want to convert to Linq.
This is my SQL query:
SELECT
Calisanlar.CalisanId,
CovidYakalanmaTarih,
CovidBitisTarih
FROM
Calisanlar
INNER JOIN
Covids ON Calisanlar.CalisanId = Covids.CalisanId
WHERE
Calisanlar.CalisanId IN (SELECT TOP 10 CalisanId
FROM Hastaliklar
GROUP BY CalisanId
ORDER BY COUNT(*) DESC)
AND DATEDIFF(DAY, CovidYakalanmaTarih, GETDATE()) BETWEEN 0 AND 30
I wrote this C# code, but it doesn't work as expected because i didn't write "DATEDIFF(DAY, CovidYakalanmaTarih, GETDATE()) BETWEEN 0 AND 30" linq version:
var query = context.Hastaliklar
.GroupBy(x => x.CalisanId)
.OrderByDescending(grp => grp.Count())
.Select(grp => grp.Key)
.Take(10)
.ToList();
var result = from hastalik in context.Hastaliklar
join covid in context.Covids
on hastalik.CalisanId equals covid.CalisanId
where query.Contains(hastalik.CalisanId)
&& EF.Functions.DateDiffDay(covid.CovidYakalanmaTarih, covid.CovidBitisTarih)
select new SonBirAyCovidDto
{
CalisanId = covid.CalisanId,
CovidYakalanmaTarih = covid.CovidYakalanmaTarih,
CovidBitisTarih = covid.CovidBitisTarih
};
There is not direct translation to BETWEEN in EF Core, but you can make other condition. Also it is better to remove ToList() from first query, in this case you will have only one roundtrip to database.
var query = context.Hastaliklar
.GroupBy(x => x.CalisanId)
.OrderByDescending(grp => grp.Count())
.Select(grp => grp.Key)
.Take(10);
var result =
from hastalik in context.Hastaliklar
join covid in context.Covids
on hastalik.CalisanId equals covid.CalisanId
where query.Contains(hastalik.CalisanId)
&& covid.CovidYakalanmaTarih <= covid.CovidBitisTarih
&& EF.Functions.DateDiffDay(covid.CovidYakalanmaTarih, covid.CovidBitisTarih) <= 30
select new SonBirAyCovidDto
{
CalisanId = covid.CalisanId,
CovidYakalanmaTarih = covid.CovidYakalanmaTarih,
CovidBitisTarih = covid.CovidBitisTarih
};

How to write join query with multiple column - LINQ

I have a situation where two tables should be joined with multiple columns with or condition. Here, I have a sample of sql query but i was not able to convert it into linq query.
select cm.* from Customer cm
inner join #temp tmp
on cm.CustomerCode = tmp.NewNLKNo or cm.OldAcNo = tmp.OldNLKNo
This is how i have write linq query
await (from cm in Context.CustomerMaster
join li in list.PortalCustomerDetailViewModel
on new { OldNLKNo = cm.OldAcNo, NewNLKNo = cm.CustomerCode } equals new { OldNLKNo = li.OldNLKNo, NewNLKNo = li.NewNLKNo }
select new CustomerInfoViewModel
{
CustomerId = cm.Id,
CustomerCode = cm.CustomerCode,
CustomerFullName = cm.CustomerFullName,
OldCustomerCode = cm.OldCustomerCode,
IsCorporateCustomer = cm.IsCorporateCustomer
}).ToListAsync();
But this query doesn't returns as expected. How do I convert this sql query into linq.
Thank you
You didn't tell if list.PortalCustomerDetailViewModel is some information in the database, or in your local process. It seems that this is in your local process, your query will have to transfer it to the database (maybe that is why it is Tmp in your SQL?)
Requirement: give me all properties of a CustomerMaster for all CustomerMasters where exists at least one PortalCustomerDetailViewModel where
customerMaster.CustomerCode == portalCustomerDetailViewModel.NewNLKNo
|| customerMaster.OldAcNo == portalCustomerDetailViewModel.OldNLKNo
You can't use a normal Join, because a Join works with an AND, you want to work with OR
What you could do, is Select all CustomerMasters where there is any PortalCustomerDetailViewModel that fulfills the provided OR:
I only transfer those properties of list.PortalCustomerDetailViewModel to the database that I need to use in the OR expression:
var checkProperties = list.PortalCustomerDetailViewModel
.Select(portalCustomerDetail => new
{
NewNlkNo = portalCustomerDetail.NewNlkNo,
OldNLKNo = portalCustomerDetail.OldNLKNo,
});
var result = dbContext.CustomerMasters.Where(customerMaster =>
checkProperties.Where(checkProperty =>
customerMaster.CustomerCode == checkProperty.NewNLKNo
|| customerMaster.OldAcNo == checkProperty.OldNLKNo)).Any()))
.Select(customerMaster => new CustomerInfoViewModel
{
Id = customerMaster.Id,
Name = customerMaster.Name,
...
});
In words: from each portalCustomerDetail in list.PortalCustomerDetailViewModel, extract the properties NewNKLNo and OldNLKNo.
Then from the table of CustomerMasters, keep only those customerMasters that have at least one portalCustomerDetail with the properties as described in the OR statement.
From every remaining CustomerMasters, create one new CustomerInfoViewModel containing properties ...
select cm.* from Customer cm
inner join #temp tmp
on cm.CustomerCode = tmp.NewNLKNo or cm.OldAcNo = tmp.OldNLKNo
You don't have to use the join syntax. Adding the predicates in a where clause could get the same result. Try to use the following code:
await (from cm in Context.CustomerMaster
from li in list.PortalCustomerDetailViewModel
where cm.CustomerCode == li.NewNLKNo || cm.OldAcNo = li.OldNLKNo
select new CustomerInfoViewModel
{
CustomerId = cm.Id,
CustomerCode = cm.CustomerCode,
CustomerFullName = cm.CustomerFullName,
OldCustomerCode = cm.OldCustomerCode,
IsCorporateCustomer = cm.IsCorporateCustomer
}).ToListAsync();
var result=_db.Customer
.groupjoin(_db.#temp ,jc=>jc.CustomerCode,c=> c.NewNLKNo,(jc,c)=>{jc,c=c.firstordefault()})
.groupjoin(_db.#temp ,jc2=>jc2.OldAcNo,c2=> c2.OldNLKNo,(jc2,c2)=>{jc2,c2=c2.firstordefault()})
.select(x=> new{
//as you want
}).distinct().tolist();

Linq to SQL - Query with multiple joins, sum, grouping, having

I have the following query that I would like to translate to linq.
SELECT
SUM(Credits.CreditAmount)
,Transactions.Id
,Person.FullName
,Person.Id
FROM
Person
JOIN
Transactions
ON Person.AccountId = Transactions.AccountId
JOIN Credits
ON Transactions.Id = Credits.TransactionId
WHERE
Person.Type = 'AccountHolder'
AND Person.Status = 'Active'
AND Transactions.CancelledDate IS NULL
AND Credits.CancelledDate IS NULL
GROUP BY Transactions.AccountId, Person.FullName, Person.Id
HAVING SUM(Credits.CreditAmount) > 20
This is what I came up with. It's an absolute pig... The SQL it generates must be awful.
var query = from p in Person
join t in Transactions
on p.AccountId equalas t.AccountId
join c in Credits
on t.TransactionId = c.TransactionId
where p.Status == "Active" &&
p.Type = "AccountHolder" &&
t.CancelledDate == null &&
c.CancelledDate == null
group new { c.CreditAmount, t.AccountId, p.FullName, p.Id } by new { t.AccountId, p.FullName, p.SSN } into grp
let sumC = grp.Select(x => x.CreditAmount).Sum()
select new
{
TotalCredit = sumC,
AccountId = grp.Key.AccountId,
FullName = grp.Key.FullName,
Id = grp.Key.Id
};
query.Where(p => p.TotalServiceCredit > 20);
The SQL query runs in approximately 3 seconds but I have yet to find the patience to let the Linq query finish. I was wondering if there is something different I should be doing to accomplish this "group, sum, having" query I'm trying to write? Is there something I can do to help Linq generate more performat SQL?
UPDATE
Turns out sgmoore had the right idea. The key to the performance issue was in his answer.
The difference between this
let sumC = grp.Select(x => x.CreditAmount).Sum()
and this
TotalCredit = grp.Sum(x => x.CreditAmount)
was the difference between a query that finishes and one that does not.
See my revised LINQ query below which completes in about the same time as the SQL (5.3 seconds for SQL vs 5.6 seconds for LINQ).
var query = from p in Person
join t in Transactions
on p.AccountId equalas t.AccountId
join c in Credits
on t.TransactionId = c.TransactionId
where p.Status == "Active" &&
p.Type = "AccountHolder" &&
t.CancelledDate == null &&
c.CancelledDate == null
group new { c.CreditAmount, t.AccountId, p.FullName, p.Id } by new { t.AccountId, p.FullName, p.SSN } into grp
select new
{
TotalCredit = grp.Sum(x => x.CreditAmount),
AccountId = grp.Key.AccountId,
FullName = grp.Key.FullName,
Id = grp.Key.Id
};
query.Where(p => p.TotalServiceCredit > 20);
Thanks for all your help!
I don't disagree with WEI_DBA's comment but if you need to do this, then you might find it easier to break this into several queries, eg
var query1 = from p in Person
join t in Transactions on p.AccountId equals t.AccountId
join c in Credits on t.TransactionId equals c.TransactionId
where p.Status == "Active" &&
p.Type = "AccountHolder" &&
t.CancelledDate == null &&
c.CancelledDate == null
select new { c.CreditAmount, t.AccountID, p.FullName, p.Id};
var query2 = (from p in query1
group p by new { p.AccountId, p.FullName, p.Id } into grp
select new
{
TotalCredit = grp.Sum(x => x.CreditAmount),
AccountId = grp.Key.AccountId,
FullName = grp.Key.FullName,
Id = grp.Key.Id
};
var query3 = (from p in query2 where p.TotalCredit > 20 select p);
Then you can let LINQ combine this into one sql command.
As always, it is a good idea to check and verify the actual TSQL generated.

SQL Query to LINQ Lambda expression

I am trying to convert a bit of SQL containing a LEFT OUTER JOIN with a GROUP BY clause into a LINQ Lambda expression.
The SQL I need to convert is:-
SELECT m.MemberExternalPK
FROM Member.Member AS m LEFT OUTER JOIN Member.Account AS a ON m.MemberID = a.MemberID
GROUP BY MemberExternalPK
HAVING COUNT(AccountID) = 0
I have managed to get it working correctly with an INNER JOIN between Member and Accounts like this (for a count of Account = 1) but this does not work for Accounts with a count of 0 (hence the LEFT OUTER JOIN is required):-
Members.Join(Accounts, m => m.MemberID, a => a.MemberID, (m, a) => new {m, a})
.GroupBy(t => t.m.MemberExternalPK, t => t.a)
.Where(grp => grp.Count(p => p.AccountID != null) == 1)
.Select(grp => grp.Key)
I have been trying to experiment with the .DefaultIfEmpty() keyword but have so far been unsuccessful. Any help would be greatly appreciated :)
I think this is what you're after:
Query syntax
var members = new List<Member>
{
new Member {MemberId = 1, MemberExternalPk = 100},
new Member {MemberId = 2, MemberExternalPk = 200}
};
var accounts = new List<Account>
{
new Account {AccountId = 1, MemberId = 1}
};
var query =
from m in members
join a in accounts on m.MemberId equals a.MemberId into ma
from ma2 in ma.DefaultIfEmpty()
where ma2 == null
group ma2 by m.MemberExternalPk into grouped
select new {grouped.Key};
The result of this example is that only the number 200 is returned. As it will only return the MemberExternalPk for members that don't have an account. i.e. As MemberId 2 doesn't have a related Account object, it is not included in the reuslts.
var list = members.GroupJoin(accounts,m=>m.MemberId, a => a.MemberId,
(m,a) => new {m,a})
.Where(x=>x.a.Count()==0)
.Select(s=>s.m.MemberExternalPk);

How to convert T-SQL into LINQ

I have following code in a T-SQL query and I need to convert (rewrite) it into LINQ. Can somebody help me? Thanks
SELECT (select max(X.PocetDniPoPlatnosti)
from
(
select
(select top 1 datediff(day,datumplatnosti,getdate()) from planrealizace p
where p.cinnostsopidsop = cinnostsop.idsop and datumplatnosti <= getdate() and p.provest = 1 and p.datumprovedeni is null
order by p.datumplatnosti desc) as PocetDniPoPlatnosti
from cinnostsop
where cinnostSOP.LegislativneVyznamna = 1 and (CinnostSOP.ObjektId = 131476)) X) as PoPlatnosti
this should work, but I could not try it as I do not have any programming tools at home, so feel free to let me know if this doesn't work, and we can improve it together
var max = (from cp in cinnostsop.Where(c => c.LegislativneVyznamna = 1 && c.ObjektId = 131476)
join p in (
planrealizace.
Where(pz => pz.datumplatnosti <= DateTime.Now &&
pz.provest = 1 and pz.datumprovedeni is null).
GroupBy(pz => pz.cinnostsopidsop, pz => pz).
Select(g =>
new {
id = g.Key,
firstdate = (g.OrderByDescending(
pz => pz.datumplatnosti).
First().datumplatnosti - DateTime.Now
).Totaldays
})
) on cp.idsop equals p.id
select p.firstdate).Max(d => d);