LINQ Multiple table left join, distinct count not giving proper result - linq

I am sql query us producing correct result, but when i'm doing the same in LINQ. Output is incorrect. Please let me know where my making mistake.
Following linq query that i created.
LINQ Query:
List<UserModel> Model = (from users in db.UserM
join ct in db.CustT on users.UserId equals ct.UserID into group1
from g1 in group1.DefaultIfEmpty()
join ti in db.TestIn on g1.TestId equals ti.TestID into group2
from g2 in group2.DefaultIfEmpty()
where (users.CustomerId==CustomerId) && (users.RoleId == 4) && (users.Status == 1)
group new
{
g2.TestInvitationID,
g2.TestID,
}
by new
{
users.FirstName,
users.CreatedOn,
users.Email,
users.UserId
} into group4
select new UserModel
{
Name = group4.Key.FirstName,
CreatedOn = group4.Key.CreatedOn,
EmailId = group4.Key.Email,
UserId = group4.Key.UserId,
NoOfTestTaken = group4.Select(x=>x.TestID).Distinct().Count(),
NoOfInvitationsSent = group4.Count(x => x.TestInvitationID != 0)
}).ToList();
SQL Query:
SELECT IsNull(COUNT(distinct TS.TestId),0) AS NoOfTests,
IsNull(COUNT(TS.TestInvitationID),0) AS NoOfInvitations,
UM.Email,
UM.UserId,
UM.FirstName,
UM.CreatedOn
FROM UserM as UM
left JOIN CustT AS CT
ON UM.UserId=CT.UserId
left JOIN TestIn AS TS
ON TS.TestId = CT.TestId
WHERE UM.CustomerId=41
AND UM.RoleId=4
and UM.[Status]=1
GROUP BY UM.UserId, UM.Email, UM.FirstName, UM.CreatedOn
Tables:
"UserM" - columns: UserId, Email, FirstName, CreatedOn
"CustT" - columns: TestId, UserId,
"TestIn" - columns: TestInvitationId, TestId

The difference between SQL COUNT(expr) and LINQ Count is that the former excludes NULL values, which produces a difference when used on right side of a left outer join with no matching records (SQL will produce 0 while LINQ 1). The closest LINQ equivalent is Count(expr != null).
So the direct translation of your SQL query would be like this (note that the generated SQL query could and most likely will be different):
(A side note: When converting SQL query to LINQ, it's good to use the same aliases to make it easier to see the mappings)
var query =
from um in db.UserMasters
join ct in db.CustTests on um.UserId equals ct.UserID
into ctGroup from ct in ctGroup.DefaultIfEmpty() // left outer join
join ts in db.TestInvitaions on ct.TestId equals ts.TestID
into tsGroup from ts in tsGroup.DefaultIfEmpty() // left outer join
where um.CustomerId == UserSession.CustomerId
&& um.RoleId == 4
&& um.Status == 1
group ts by new { um.UserId, um.Email, um.FirstName, um.CreatedOn } into g
select new UserModel
{
Name = g.Key.FirstName,
CreatedOn = g.Key.CreatedOn,
EmailId = g.Key.Email,
UserId = g.Key.UserId,
NoOfTestTaken = g.Where(ts => ts != null).Select(ts => ts.TestID).Distinct().Count(),
NoOfInvitationsSent = g.Count(ts => ts != null)
};
var result = query.ToList();

I suspect that the following row is the problem because is not the same like in your sql:
Linq:
NoOfInvitationsSent = group4.Count(x => x.TestInvitationID != 0)
SQL:
IsNull(COUNT(TS.TestInvitationID),0) AS NoOfInvitations
Due to counting items from a left join Linq should be instead:
NoOfInvitationsSent = group4.Where(i => i != null).Count()
To put it all together, with a bit of better formatting:
var model = (from users in db.UserMasters
join ct in db.CustTests on users.UserId equals ct.UserID into group1
from ct in group1.DefaultIfEmpty()
join ti in db.TestInvitaions on ct.TestId equals ti.TestID into group2
from ct in group2.DefaultIfEmpty()
where users.CustomerId == UserSession.CustomerId &&
users.RoleId == 4 &&
users.Status == 1
group new { ct.TestInvitationID, ct.TestID }
by new
{
users.FirstName,
users.CreatedOn,
users.Email,
users.UserId
} into grouping
select new UserModel
{
Name = grouping.Key.FirstName,
CreatedOn = grouping.Key.CreatedOn,
EmailId = grouping.Key.Email,
UserId = grouping.Key.UserId,
NoOfTestTaken = grouping.Where(i => i != null).Select(x => x.TestID).Distinct().Count(),
NoOfInvitationsSent = grouping.Where(i => i != null).Count()
}).ToList();

Related

How to read data from a nested select query linq

I have a query like the following which is of type linq.
var querymiangin = (from t1 in _context.Apiapplicant
join t2 in _context.ApiApplicantHistory on t1.Id equals t2.ApiApplicantId
join t3 in _context.EntityType on t2.LastReqStatus equals t3.Id
where t1.IsDeleted == false && t1.LastRequestStatus == t2.Id && t3.Name == "granted"
select new { A = t1, B = t2, Year = t1.ApiRequestDate.Substring(0, 4), Month = t1.ApiRequestDate.Substring(5, 2) } into joined
group joined by new { joined.Year, joined.Month, joined.B.LastReqStatus } into grouped
select grouped.Select(g => new { ApiReqDate = g.A.ApiRequestDate, ApiDate = g.B.Date, ApiLastReqStatus = g.B.LastReqStatus, ApiYear = g.Year, ApiMonth = g.Month })).ToList();
In the select part, ApiReqDate and ApiDate has multiple records. Now my problem is for each group of month and year, I have multiple ApiDate and ApiReqDate records and I want for each group based on a condition (t1.LastRequestStatus == t2.Id && t3.Name == "granted") by using GetPersianDaysDiffDate() method, obtain the difference between ApiReqDate and its related ApiDate records for each month and then find their average in that month.
For doing that, I have written code like this:
var avgDateDiff = querymiangin.DefaultIfEmpty()
.GroupBy(x => new { x.ApiYear, x.ApiMonth }, (key, g) => new
{
key.ApiYear,
key.ApiYear,
Avg = g.Average(y => GetPersianDaysDiffDate(y.ApiReqDate,y.ApiDate))
})
.ToList();
But the problem is each parameter x.ApiYear, x.ApiMonth,y.ApiReqDate,y.ApiDate are unknown and it shows me error. I appreciate if anyone can suggest me a solution for that.
1 - For the first request querymiangin, you don't need to group by statement, change little the code to :
var querymiangin = (from t1 in Apiapplicant
join t2 in ApiApplicantHistory on t1.Id equals t2.ApiApplicantId
join t3 in EntityType on t2.LastReqStatus equals t3.Id
where t1.IsDeleted == false && t1.LastRequestStatus == t2.Id && t3.Name == "granted"
select new
{
ApiReqDate = t1.ApiRequestDate,
ApiDate = t2.Date,
ApiYear = t1.ApiRequestDate.Substring(0, 4),
ApiMonth = t1.ApiRequestDate.Substring(5, 2)
}).ToList();
2 - For the second query avgDateDiff, use GroupBy by ApiYear and ApiMonth and calculate the Average, like :
var avgDateDiff = querymiangin
.GroupBy(x => new { x.ApiYear, x.ApiMonth }, (key, g) => new
{
key.ApiYear,
key.ApiMonth,
Avg = g.Average(y => GetPersianDaysDiffDate(y.ApiReqDate, y.ApiDate))
}).ToList();
I hope you find this helpful.

How to write this LINQ Query in a better way

I have one Linq Query. When I run the query, Only for 10 records its taking 13 seconds to extract the data to the model. I need to know the query which I wrote is good for performance or not. Please guide me what i am doing wrong.
Code
var stocktakelist = (from a in Db.Stocktakes
select new ExportStock
{
Id = a.Id,
ItemNo = a.ItemNo,
AdminId = (from admin in Db.AdminAccounts where admin.Id == a.Id select admin.Name).FirstOrDefault(),
CreatedOn = a.CreatedOn,
Status = (from items in Db.Items where items.ItemNo == a.ItemNo select items.ItemStatu.Description).FirstOrDefault(),
Title = (from tit in Db.BibContents where tit.BibId == (from bibs in Db.Items where bibs.ItemNo == a.ItemNo select bibs.BibId).FirstOrDefault() && tit.TagNo == "245" && tit.Sfld == "a" select tit.Value).FirstOrDefault() // This line of Query only makes the performance Issue
}
).ToList();
Thanks
The reason this is so slow is because it is running the 3 inner LINQ statements for every item in the outer LINQ statement.
Using LINQ joins will run only 4 queries and then link them together, which is faster.
To find out how to join, there are plenty of resources on the Internet depending on the type of LINQ you are using.
If you're retrieving this data from a SQL server, perhaps consider doing this intensive work in SQL - this is what SQL was designed for and it's much quicker than .NET. EDIT: As highlighted below, the work is done in SQL if using LINQ to SQL/Entities and using the correct join syntax.
I was trying to create the corresponding query with some joins for practice.
I cannot test it and i'm not 100% sure that this query will you get the result
you are hoping for but maybe at least it will give you a hint on how to write
joins with linq.
from a in Db.Stocktakes
join admin in Db.AdminAccounts
on a.Id equals admin.Id
into adminJoinData
from adminJoinRecord in adminJoinData.DefaultIfEmpty( )
join items in Db.Items
on a.ItemNo equals items.ItemNo
into itemsJoinData
from itemsJoinRecord in itemsJoinData.DefaultIfEmpty( )
join title in Db.BibContents
(
from subQuery in Db.BibContents
where subQuery.TagNo == "245"
where subQuery.Sfld == "a"
select subquery
)
on title.BibId equals itemsJoinRecord.BidId
into titleJoinData
from titleJoinRecord in titleJoinData.DefaultIfEmpty( )
select new ExportStock( )
{
Id = a.Id,
ItemNo = a.ItemNo,
AdminId = adminJoinRecord.Name,
CreatedOn = a.CreatedOn,
Status = itemsJoinRecord.ImemStatu.Description,
Title = titleJoinRecord.Value
}
As others have said, you should use Left Outer Joins in your LINQ just as you would if writing it in SQL.
Your query above will end up looking roughly like this once converted (this is untested, but gives the basic idea):
var a = from a in Db.Stocktakes
join admin in Db.AdminAccounts on admin.Id equals a.Id into tmpAdmin
from ad in tmpAdmin.DefaultIfEmpty()
join item in Db.Items on item.ItemNo equals a.ItemNo into tmpItem
from it in tmpItem.DefaultIfEmpty()
join title in Db.BibContents on bib.BibId equals items.BibId into tmpTitle
from ti in tmpTitle.DefaultIfEmpty()
where ti.TagNo == "245"
&& ti.Sfld == "a"
select new ExportStock
{
Id = a.Id,
ItemNo = a.ItemNo,
AdminId = ad == null ? default(int?) : ad.Id,
CreatedOn = a.CreatedOn,
Status = it == null ? default(string) : it.ItemStatus.Description,
Title = ti == null ? default(string) : ti.Value
};
Using lambda expressions your query will look like this:
Db.Stocktakes
.Join(Db.AdminAccounts, a => a.Id, b => b.Id, (a,b) => new { a, AdminId = b.Name })
.Join(Db.Items, a => a.ItemNo, b => b.ItemNo, (a,b) => new { a, Status = b.ItemStatus.Description, BidId = b.BibId })
.Join(Db.BibContents, a => a.BibId, b => b.BibId, (a,b) => new { a, Value = b.Value, TagNo = b.TagNo, Sfld = b.Sfld })
.Where(a => a.TagNo == "245" && a.Sfld == "a")
.Select(a =>
new ExportStock { Id = a.Id,
ItemNo = a.ItemNo,
AdminId = a.AdminId,
CreatedOn = a.CreatedOn,
Status = a.Status,
Title = a.Value
}
).ToList();

Combining two tables using linq

i have two linq - sql queries, and im wondering how to join them..
First Query
var ab = from a in Items_worker.getCEAItems()
where a.ProjectCode == lbl_projectCode.Text
select new
{
a.ID
};
Second Query
var j = from c in tblInc_worker.get(c => c.MarginID == MarginID && c.IncTypeID == "CAPEX")
orderby c.DateCreated
select c.ID;
First Query would return:
fasf-1212-1212-1212-1212
afaa-1414-1414-1414-1414
Second Query would return:
fasf-1212-1212-1212-1212
afaa-1414-1414-1414-1414
0000-0000-0000-0000-0000
1111-1111-1111-1111-1111
question is how can i possibly join the two table. Wherein the second query should return all of the records with the same ID found in the first query plus the id containing "0000-0000-0000-0000-0000" second query..
The result should be:
fasf-1212-1212-1212-1212
afaa-1414-1414-1414-1414
0000-0000-0000-0000-0000
You can use union to join the both queries, for example split your second query in two with conditions like :
var ab = from a in Items_worker.getCEAItems()
where a.ProjectCode == lbl_projectCode.Text
select new
{
a.ID
};
var j = from c in tblInc_worker.get(c => c.MarginID == MarginID && c.IncTypeID == "CAPEX")
orderby c.DateCreated
select c.ID where c.ID.Equals("0000-0000-0000-0000-0000");
var j1 = from c in tblInc_worker.get(c => c.MarginID == MarginID && c.IncTypeID == "CAPEX")
orderby c.DateCreated
select c.ID where !(c.ID.Equals("0000-0000-0000-0000-0000"));
var result = ab.Union(j.Union(j1));
Hope this helps..

linq nested query

I have a query that has the following
var myvar = from table in MyDataModel
where.....
select new MyModel
{
modelvar1 = ...,
modelvar2 = (from..... into anothervar)
}
What I want to do is have modelvar2 be a join between the result I currently get from anothervar with another table in MyDataModel.
Thanks
The parenthesis looks more like a subquery than a join. This is how you do a join.
Example tables from the AdventureWorks database.
using (DataClasses1DataContext context = new DataClasses1DataContext())
{
// If you have foreign keys correctly in your database you can
// join implicitly with the "dot" notation.
var myvar = from prod in context.Products
where prod.ListPrice < 10
select new
{
Name = prod.Name,
Category = prod.ProductSubcategory.ProductCategory.Name,
};
// If you don't have foreign keys you need to express the join
// explicitly like this
var myvar2 = from prod in context.Products
join prodSubCategory in context.ProductSubcategories
on prod.ProductSubcategoryID equals prodSubCategory.ProductSubcategoryID
join prodCategory in context.ProductCategories
on prodSubCategory.ProductCategoryID equals prodCategory.ProductCategoryID
where prod.ListPrice < 10
select new
{
Name = prod.Name,
Category = prodCategory.Name,
};
// If you REALLY want to do a subquery, this is how to do that
var myvar3 = from prod in context.Products
where prod.ListPrice < 10
select new
{
Name = prod.Name,
Category = (from prodSubCategory in context.ProductSubcategories
join prodCategory in context.ProductCategories
on prodSubCategory.ProductCategoryID equals prodCategory.ProductCategoryID
select prodCategory.Name).First(),
};
// If you want to get a list from the subquery you can do like this
var myvar4 = from prodCategory in context.ProductCategories
select new
{
Name = prodCategory.Name,
Subcategoreis = (from prodSubCategory in context.ProductSubcategories
where prodSubCategory.ProductCategoryID == prodCategory.ProductCategoryID
select new { prodSubCategory.ProductSubcategoryID, prodSubCategory.Name }).ToList(),
};
}
SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPostalCode], [t0].[ShipCountry]
FROM [Orders] AS [t0]
INNER JOIN ([Order Details] AS [t1]
INNER JOIN [Products] AS [t2] ON [t1].[ProductID] = [t2].[ProductID]) ON [t0].[OrderID] = [t1].[OrderID]
can be write as
from o in Orders
join od in (
from od in OrderDetails join p in Products on od.ProductID equals p.ProductID select od)
on o.OrderID equals od.OrderID
select o

Use of where in multiple joins to remove rows - linq

I have a table of orders. the status is on the soilorders which is joined to the orders.
I only want to return orders where the joined soilorder does not have status "Removed".
I had thought that
join sso in db.SoilSamplingOrders on ord.order_id equals sso.order_id
where sso.status.Equals("Removed")!=true
but then no records are returned!
thanks for any help (query below)
var query =
from ord in db.Orders
join sso in db.SoilSamplingOrders on ord.order_id equals sso.order_id
where sso.status.Equals("Removed")!=true
join cust in db.Customers on ord.customer_id equals cust.customer_id
select new Listing
{
assigned_to = sso.assigned_to,
company = cust.company,
order_id = ord.order_id,
order_created = ord.order_created,
customer_id = ord.customer_id,
order_created_by_employ_id = ord.order_created_by_employ_id,
first_farm_on_order = (from f in db.SoilSamplingSubJobs
where f.order_id == ord.order_id
select new ListingSubJob { first_farm_on_order = f.farm }).
AsEnumerable().First().first_farm_on_order,
total_fields = (from f in db.SoilSamplingSubJobs
where f.order_id == ord.order_id
select new { f.sssj_id }).AsEnumerable().Count(),
total_area = (float?) (from f in db.SoilSamplingSubJobs
where f.order_id == ord.order_id && f.area_ha != null
select f.area_ha ).Sum() ?? 0 ,
total_area_ph_density = (float?)(from f in db.SoilSamplingSubJobs
where f.order_id == ord.order_id && f.ph != null
select f.ph).Sum() ?? 0,
};
DOH! Just as nature abhors a vacuum, anything Null cannot be included in the select. Added values to the status field and bom it works.

Resources