How to write linq query for this SQL query? - sql

How to write linq query for this SQL Query
select c.Name, Count(cb.Id) as Total1, Count(cf.Id) as Total2
from Company c
left join CompanyBDetails CB on C.Id = CB.CompanyId
left join CompanyFDetails CF on CF.BankId = CB.Id
group by C.Name

So you have a table of Companies, where every Company has zero or more BDetails, every BDetail belongs to exactly one Company, namely the Company that the foreign key CompanyId refers to: a straightforward one-to-many relation.
Similarly, every BDetail has zero or more FDetails, and every FDetail belongs to exactly one BDetail, namely the BDetail that the foreign key BankId refers to.
For every company you want the Name of the Company and the number of BDetails of this company. It seems to me that you also want the total number of FDetails of all BDetails of this Company.
var result = dbContext.Companies.Select(company => new
{
Name = company.Name,
BDetailIds = dbContext.BDetails
.Where(bDetail => bDetail.CompanyId == company.Id)
.Select(bDetail => bDetail.Id),
})
Intermediate result: for every Company, you have created one object, that holds the name of the Company, and the Ids of all its BDetails
Continuing the Linq: calculate the totals:
.Select(company => new
{
Name = company.Name,
// Total1 is the number of BDetails of this Company
Total1 = company.BDetailIds.Count(),
// Total2 is the number of FDetails of all BDetails of this Company
// whenever you need the sub-items of a sequence of items, use SelectMany
Total2 = company.BDetailIds
.SelectMany(bDetailId => dbContext.FDetails.Where(fDetail => fDetail.BankId == bDetailId))
.Count();
});
If you want more properties than just the totals:
var result = dbContext.Companies.Select(company => new
{
// Select the company properties that you plan to use:
Id = company.Id,
Name = company.Name,
...
BDetail = dbContext.BDetails
.Where(bDetail => bDetail.CompanyId == company.Id)
.Select(bDetail => new
{
// Select only the bDetail properties of the company that you plan to use
Id = bDetail.Id,
...
// not needed, you know the value:
// CompanyId = bDetail.CompanyId,
FDetails = dbContext.FDetails
.Where(fDetail => fDetail.BankId == bDetail.Id)
.Select(fDetail => new
{
// you know the drill by now: only the properties that you plan to use
Id = fDetail.Id,
...
})
.ToList(),
})
.ToList(),
});

Related

Optimizing EF flattening

I have a similar case to the following:
Say there's a number of jobs to be done and for each job there's a history of workers where only one worker is active per job. There's three tables: the Job itself, a mapping table JobWorkers which holds the history of workers for a given job (including a datetime "To" which indicates whether still active (null) or when assignment was cancelled (end date)) and Workers which have a first and last name.
I'd like to query a list of all jobs and the first and last name of the currently assigned worker as flat model. This is the code I'm executing:
var jobExample = dbContext.Jobs.Select(j => new
{
j.JobId,
// ...some other columns from jobs table
j.JobWorker.FirstOrDefault(jw => jw.To == null).Worker.FirstName, // first name of currently assigned worker
j.JobWorker.FirstOrDefault(jw => jw.To == null).Worker.LastName // last name of currently assigned worker
}).First();
The following SQL query is generated:
SELECT TOP (1)
[Extent1].[JobId] AS [JobId],
[Extent3].[FirstName] AS [FirstName],
[Extent5].[LastName] AS [LastName]
FROM [tables].[Jobs] AS [Extent1]
OUTER APPLY (SELECT TOP (1)
[Extent2].[WorkerId] AS [WorkerId]
FROM [tables].[JobWorkers] AS [Extent2]
WHERE ([Extent1].[JobId] = [Extent2].[JobId]) AND ([Extent2].[To] IS NULL) ) AS [Limit1]
LEFT OUTER JOIN [tables].[Workers] AS [Extent3] ON [Limit1].[WorkerId] = [Extent3].[WorkerId]
OUTER APPLY (SELECT TOP (1)
[Extent4].[WorkerId] AS [WorkerId]
FROM [tables].[JobWorkers] AS [Extent4]
WHERE ([Extent1].[JobId] = [Extent4].[JobId]) AND ([Extent4].[To] IS NULL) ) AS [Limit2]
LEFT OUTER JOIN [tables].[Workers] AS [Extent5] ON [Limit2].[WorkerId] = [Extent5].[WorkerId]
As one can see there're two outer apply/left outer joins that are identical. I'd like to get rid of one of those to make the query more performant.
Note that the select statement is dynamically generated based on what information the user actually wants to query. But even if this didn't apply I'm not sure how to do this without having a hierarchic structure and then only afterwards flatten it in .NET
Thanks for your help and if I can improve this question in any way please comment.
You've probably seen that there are two types of LINQ methods: the ones that return IQueryable<...>, and the other ones.
Methods of the first group use deferred execution. This means, that the query is made, but not executed yet. Your database is not contacted.
Methods of the second group, like ToList(), FirstOrDefault(), Count(), Any(), will execute the query: they will contact the database, and fetch the data that is needed to calculate the result.
This is the reason, that you should try to postpone any method of the second group to as last as possible. If you do it earlier, and you do something LINQy after it, changes are that you fetch to much data, or, as in your case: that you do execute the same code twice.
The solution is: move your FirstOrDefault to a later moment.
var jobExample = dbContext.Jobs.Select(job => new
{
Id = job.JobId,
... // other job properties
ActiveWorker = job.JobWorkers
.Where(jobWorker => jobWorker.To == null)
.Select(worker => new
{
FirstName = worker.FirstName,
LastName = worker.LastName,
})
.FirstOrDefault(),
})
.FirstOrDefault();
The result is slightly different than yours:
Id = 10;
... // other Job properties
// the current active worker:
ActiveWorker =
{
FirstName = "John",
LastName = "Doe",
}
If you really want an object with Id / FirstName / LastName, add an extra Select before your final FirstOrDefault:
.Select(jobWithActiveWorker => new
{
Id = jobWithActiveWorker.Id,
... // other Job properties
// properties of the current active worker
FirstName = jobWithActiveWorker.FirstName,
LastName = jobWithActiveWorker.LastName,
})
.FirstOrDefault();
Personally I think that you should not mix Job properties with Worker properties, so I think the first solution: "Job with its currently active worker" is neater: the Job properties are separated from the Worker properties. You can see why that is important if you also wanted the Id of the active worker:
.Select(job => new
{
Id = job.JobId,
... // other job properties
ActiveWorker = job.JobWorkers
.Where(jobWorker => jobWorker.To == null)
.Select(jobworker => new
{
Id = jobworker.Id,
FirstName = jobworker.FirstName,
LastName = jobworker.LastName,
})
.FirstOrDefault(),
})
.FirstOrDefault();
Try rewriting your query like this:
var query =
from j in dbContext.Jobs
let ws = j.JobWorker
.Where(jw => jw.To == null)
.Select(jw => jw.Worker)
.Take(1)
from w in ws.DefaultIfEmpty()
select new
{
j.JobId,
// other properties
w.FirstName,
w.LastName,
};
The query processor probably could not have optimized any further to know it could use the subquery once.

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

Linq query using list output as input

I am using Linqpad and have odata connection setup.
I have a query as follows
QUERY1
void Main()
{var a = from cpuid in Computers
where cpuid.DnsHostName == "xyz"
select new {
ID = cpuid.TechnicalProductsHosted.Select (x => new { Id = x.Id }),
System_Dept = cpuid.SystemDepartment,
};
Console.WriteLine(a);
}
The output : it returns 4 ids but one department which is common among all four id's. When i query otherway round i.e
QUERY2
var a = from id in TechnicalProducts
where id.Id == "ID-15784"
select new
{System_Dept = id.Computers.Select(x => x.SystemDepartment),
Support_Team = id.Computers.Select(x => x.SupportTeam)
};
Console.WriteLine(a);
The output : 4 departments for the id. I wish to have the whole list of departments in the first case. How is it possible? In query 1 Can i take id as input for System Department and query it somehow?
the output samples

Listing data from 3 different but associated table into one dropdown (using linq-to-sql and mvc3)

i ve 3 tables in my database : item,group,groupdetail;
in table item there are 2 fields : id,item_name (bigint"long",string)
in table group there are 2 fields : id,group_name (bigint,string)
in table group_details there are 3 fields : id,group_id,item_id (bigint,bigint,bigint)
group_id is nullable cause some items have no groups.
what i want to do is listing the names of groups where the items that has a group "group_name" ordered by item_id and also in the same list i wanna show the item names which has no group and both of them should be ordered by item_id
i can also fill dropdowns with jquery if required for this action but i'm brain freezed with listing part.
controller
var data = from guru in dataContext.group_details select guru;
var itemsWithoutGroup = data.Where(c => c.group_id == null).OrderBy(c => c.item_id).Where(c => c.item_id == c.item.id).Select( c=>c.item.item_name);
var groups = data.Where(c => c.group_id != null).OrderBy(c => c.item_id).Where(c => c.group_id == c.group.id).Select( c=>c.group.group_name);
var grandlist = // i wanna do smt here to list both in one dropdown ordered by item_id
view
#Html.DropDownList("GroupDetails", new SelectList((System.Collections.IEnumerable)ViewData["GroupDetailedList"], "id", "item_id"))
EDIT:
To be more specific about it..
Group-1
Group-2
Item-9
Group-3
Item-15
Group-4
So this is how the dropdown should look like! Items with no groups should be listed too with the item groups and be ordered by item_id..
The statement i would use is:
var groupswithItems = (from groupdetail in group_details select groupdetail.group_id
join group in groups groupdetail.group_id equals group.id
orderby groupdetail.item_id
select new SelectListItem {Text= group.name, Value= group.id.ToString() }).ToList()
var itemswithotgroups = (from item in items orderby item.id select new SelectListItem { Text= item.name, Value= item.id.ToString() }).ToList()
ViewBag.Combined = groupswithItems.union(itemswithotgroups);
This would give you the list from all groups that are in group detail (those contain items i presume) and convert it to object that Dropdownlist can handle
#Html.DropDownList("GroupDetails", ViewBag.groups as List<SelectListItem>))

Joining tables in LINQ/SQL

I have below a collection of rows and each row consists of productid, unitid, countryid.
I need to find the details for each row in the corresponding tables (products, units, country)
for product - select product name, updatedby
for unitid - select unit name , updatedby
for countryid - select countryname, uploadby
and returning the rows which has the same format
Id = product id or unitid or countryid
name = proudct name or unit name or countryname
modified = product updatedby or unit updated by or country uploadby
So, in summary -
1. For a Collection of rows
a. use the id to get the extra details from the respective table
b. return the same type of collection for the results
2. do step 1 for
2.a For RegularToys (Run this logic on TableBigA)
2.b For CustomToys(Run this logic on TableB)
3. Return all the rows
by adding 2.a and 2.b
How to write an sql/linq query for this? thanks
If I'm understanding correctly, you want to use a given ID to find either a product, a unit or a country but you're not sure which. If that's the case, then you can build out deferred queries like this to find the given entity:
var prod = from p in db.Products
where p.ProductId = id
select new { Id = p.ProductId, Name = p.ProductName, Modified = p.UpdatedBy };
var unit = from u in db.Units
where p.UnitId = id
select new { Id = u.UnitId, Name = u.UnitName, Modified = p.UpdatedBy };
var ctry = from c in db.Countries
where c.CountryId = id
select new { Id = c.CountryId, Name = c.CountryName, Modified = c.UploadBy };
And then execute the queries until you find an entity that matches (with ?? being the null-coalesce operator that returns the right value if the left result is null).
var res = prod.SingleOrDefault() ??
unit.SingleOrDefault() ??
ctry.SingleOrDefault() ??
new { Id = id, Name = null, Modifed = null };
Without any further details I can't be more specific about the condition below, but I think you are asking for something along these lines. I'm assuming your Id's are int's (but this can be easily changed if not) and you already have an Entity Data Model for the tables you describe.
Create a class for your common data:
class RowDetail
{
public int Id { get; set; }
public string Name { get; set; }
public string Modified { get; set; }
}
Pull the information out of each of the sub tables into a new record:
IEnumerable<RowDetail> products =
from p in db.Products
where <<CONDITION>>
select
new RowDetail()
{
Id = p.ProductId,
Name = p.ProductName,
Modified = p.UpdatedBy
};
IEnumerable<RowDetail> units =
from u in db.Units
where <<CONDITION>>
select
new RowDetail()
{
Id = u.UnitId,
Name = u.UnitName,
Modified = u.UpdatedBy
};
IEnumerable<RowDetail> countries =
from c in db.Countries
where <<CONDITION>>
select
new RowDetail()
{
Id = c.CountryId,
Name = c.CountryName,
Modified = c.UploadBy
};
Finally pull all the records together in a single collection:
IEnumerable<RowDetail> results = products.Union(units).Union(countries);
I'm not sure if this is exactly what you are looking for, so feel free to give feedback and/or more details if further assistance is required.