I am moving an old ASP.net (C#) application from plain SQL queries to LINQ to SQL and am having some trouble with some of the more complex queries. In this case, I am trying to get a list of employees who have a certain set of skills. The user picks the skills to search for and the id's are supplied to the method that does the work. In the old SQL version, I just appended WHERE clauses to the string for each skill. Here's an example query:
SELECT DISTINCT e.firstname, e.lastname, e.username
FROM employees AS e
WHERE e.id IN (SELECT es.emp_id
FROM emp_skl AS es
WHERE es.skl_id = 6 OR es.skl_id = 11
GROUP BY es.emp_id
HAVING COUNT(es.emp_id) >= 2)
The key is the HAVING COUNT clause since that ensures the employees returned have ALL the skills and not just one. Anyway, can someone help me turn this into a solid LINQ query?
First of all, it's better if your tables don't end with an "S".
Now the code, asuming you have already a function yo get a list of skills:
IQueryable<skills> listSkills = getSkills();
IQueryable<employees> listEmployees = db.employees;
foreach(var skill in listSkills)
{
listEmployees=listEmployees
.Where(p=>p.emp_skls.Any(q=>q.skl_id==skill.id));
}
Edit:
for instance:
public IQueyable<skills> getSkills()
{
return db.skills.Where(p=>p.id==6 || p.id==1);
}
Here's the LINQ translation of your SQL query:
from e in Employees
where e.Emp_Skls.Count (es => es.Skl_id == 6 || es.skl_id == 11) >= 2
select new
{
e.FirstName, e.LastName, e.UserName
}
However, if your desired query is "give me employees who have both skills 6 and 11", your SQL query will fail if skill 6 or 11 appears twice for an employee (I take it this is possible because you have >= 2 rather than =2 in your having clause). In which case, the following query is a better fit:
from e in Employees
where e.Emp_Skls.Any (es => es.Skl_id == 6) &&
e.Emp_Skls.Any (es => es.Skl_id == 11)
select new
{
e.FirstName, e.LastName, e.UserName
}
Related
Consider the following PL/SQL query:
SELECT department_id, LISTAGG(last_name, '; ')
WITHIN GROUP (ORDER BY last_name) "Emp_list"
FROM employees
GROUP BY department_id;
Would would be LINQ equivalent of the above?
Thank you!
EDIT: Since you want other columns and want to group, you can't use the method I originally proposed as-is. Here's a new solution, though the generated SQL isn't exactly great:
from employee in employees
group employee by employee.department_id into grpEmployee
select new {
DepartmentId = grpEmployee.Key,
LastNames = string.Join(", ", employees.Where(e => e.department_id == grpEmployee.Key)
.OrderBy(e => e.last_name).Select(e => e.last_name))
}
Note that this method combines both the query-like and lambda syntax.
This should do the trick. We select all of the last names and then join them into a single string as the final result.
string.Join("; ", (from employee in employees
where employee.department_id == 30
orderby employee.last_name
select employee.last_name))
Lets say i have table with the Name Executions like this :
InvoiceID------ExecutionID-------IsSettled
123-----1-----0
123-----2-----1
345-----3-----1
345-----4-----1
567-----5-----0
567-----6-----0
My Question :
What is the query that retrieves only InvoiceIDs where all it's Executions have IsSettled=1.?
I mean the result of the query should be like this:
345-----3-----1
345-----4-----1
i want to execulde any invoices that has any executions with isSettled flog=0,in my question u will find tha invocieID=123 has 2 executions ,one with IsSettled flag=0 and another Execution with Issettled flag=1, so i dont want to include this invoice in my result set as it has one execution with isSettled flag =0
If anyone knows also if I have an Execution Object how can i get the same result using Linq.
The query can be either SQL or LINQ
Thanks
Make a list of invoices Id's that are not settled:
var notNeeded = ExecutionObject.Where(e => e.IsSettled == 0).Select(s => s.InvoiceId).ToList();
Then filter on the invoices that are settled and ensure the invoice id is in the not settled list.
var invoices = ExecutionObject.Where(e => e.IsSettled == 1 && !notNeeded.Contains(e.InvoiceId)).ToList();
The query can be either SQL or LINQ
Query
select * from Executions
where InvoiceID in
(
select InvoiceID from Executions
group by InvoiceID
having min(Issettled)=1
)
SQL FIDDLE
Consider this:
var invoices = (from execution in ExecutionObject
where execution.IsSettled == 1
&& !ExecutionObject.Where(x=>x.IsSettled == 0).Select(y=>y.InvoiceID).Contains(execution.InvoiceID)
select execution.InvoiceID).Distinct().ToList();
I haven't tested it, but the idea is that you filter first by IsSettled == 1 and then remove any that have a record where IsSettled == 0.
It'd be something as simple as the following in linq:
Executions.Where(e => e.IsSettled == 1)
After understanding the question and giving it a go in LinqPad the following Linq query will get what you need:
Executions.GroupBy(e => e.InvoiceId)
.Where(g => g.All(e => e.IsSettled == true))
.SelectMany(g => g)
The linq script is avaliable here: http://share.linqpad.net/fawl6l.linq
If this is a linq query (you haven't told us) then you could use: -
var settled = executions.GroupBy(id => id.invoiceid).Where(inv => inv.All(s => s.issettled)).Select(x => x).ToList();
I think the key point here is you want invoices where there is not a settled = 0?
select distinct InvoiceID
from executions
where invoiceID <> ALL(select invoice_id from executions where is_settled = 0)
or in linq
var q = from i in ctx.Invoices
where ctx.Invoices.All(x => is_settled == 1 || x.invoice_id != i.invoice_id)
select i.invoice_id;
var result = q.Distinct();
I am using ADO.NET query to select the employee id's of all the employees whose have a location x, y or z and are working under a supervisor.
This is the query that I am working with:
SELECT e.Employee_OID
FROM Employee e
WHERE EXISTS (SELECT 1
FROM Employee e1
WHERE e.Employee_OID = e1.Supervisor_OID
AND e1.Active_f = 'A')
AND e.Location_OID IN (123, 22)
AND e.Active_f = 'A'
I want to convert this is into a LINQ expression, I am a beginner to LINQ and EF, can someone guide me into writing this into LINQ?
This is what I have so far:
var supervisors = (from employee in Employee
where employee.location_OID == "???" //I have ID's in a list here
select employee.Employee_OID).Any();
If you're using EF 4.0 or higher, you can use Contains() to check if an item is in a list:
where yourList.Contains(employee.location_OID)
The exists subquery could be done with Any():
where employee.Any(e1 => e.Employee_OID == e1.Supervisor_OID &&
e1.Active_f == "A")
I'm just wondering if anyone can offer any advice on how to improve my query.
Basically, it'll be merging 2 rows into 1. The only thing the rows will differ by is a 'Type' char column ('S' or 'C') and the Value. What I want to do is select one row, with the 'S' value and the 'C' value, and calculate the difference (S-C).
My query works, but it's pretty slow - it takes around 8 seconds to get the results, which is not ideal for my application. I wish I could change the database structure but I can't sadly!
Here is my query:
var sales = (from cm in dc.ConsignmentMarginBreakdowns
join sl in dc.SageAccounts on new { LegacyID = cm.Customer, Customer = true } equals new { LegacyID = sl.LegacyID, Customer = sl.Customer }
join ss in dc.SageAccounts on sl.ParentAccount equals ss.ID
join vt in dc.VehicleTypes on cm.ConsignmentTripBreakdown.VehicleType.Trim() equals vt.ID.ToString() into vtg
where cm.ConsignmentTripBreakdown.DeliveryDate >= dates.FromDate && cm.ConsignmentTripBreakdown.DeliveryDate <= dates.ToDate
where (customer == null || ss.SageID == customer)
where cm.BreakdownType == 'S'
orderby cm.Depot, cm.TripNumber
select new
{
NTConsignment = cm.NTConsignment,
Trip = cm.ConsignmentTripBreakdown,
LegacyID = cm.LegacyID,
Costs = dc.ConsignmentMarginBreakdowns.Where(a => a.BreakdownType == 'C' && a.NTConsignment == cm.NTConsignment && a.LegacyID == cm.LegacyID && a.TripDate == cm.TripDate && a.Depot == cm.Depot && a.TripNumber == cm.TripNumber).Single().Value,
Sales = cm.Value ?? 0.00m,
Customer = cm.Customer,
SageID = ss.SageID,
CustomerName = ss.ShortName,
FullCustomerName = ss.Name,
Vehicle = cm.ConsignmentTripBreakdown.Vehicle ?? "None",
VehicleType = vtg.FirstOrDefault().VehicleTypeDescription ?? "Subcontractor"
});
A good place to start when optimizing Linq to SQL queries is the SQL Server Profiler. There you can find what SQL code is being generated by Linq to SQL. From there, you can toy around with the linq query to see if you can get it to write a better query. If that doesn't work, you can always write a stored procedure by hand, and then call it from Linq to SQL.
There really isn't enough information supplied to make an informed opinion. For example, how many rows in each of the tables? What does the generated T-SQL look like?
One thing I would suggest first is to take the outputted T-SQL, generate a query plan and look for table or index scans.
I have a table of Users and a one to many UserSkills table. I need to be able to search for users based on skills. This query takes a list of desired skills and searches for users who have those skills. I want to sort the users based on the number of desired skills they posses. So if a users only has 1 of 3 desired skills he will be further down the list than the user who has 3 of 3 desired skills.
I start with my comma separated list of skill IDs that are being searched for:
List<short> searchedSkillsRaw = skills.Value.Split(',').Select(i => short.Parse(i)).ToList();
I then filter out only the types of users that are searchable:
List<User> users = (from u in db.Users
where
u.Verified == true &&
u.Level > 0 &&
u.Type == 1 &&
(u.UserDetail.City == city.SelectedValue || u.UserDetail.City == null)
select u).ToList();
and then comes the crazy part:
var fUsers = from u in users
select new
{
u.Id,
u.FirstName,
u.LastName,
u.UserName,
UserPhone = u.UserDetail.Phone,
UserSkills = (from uskills in u.UserSkills
join skillsJoin in configSkills on uskills.SkillId equals skillsJoin.ValueIdInt into tempSkills
from skillsJoin in tempSkills.DefaultIfEmpty()
where uskills.UserId == u.Id
select new
{
SkillId = uskills.SkillId,
SkillName = skillsJoin.Name,
SkillNameFound = searchedSkillsRaw.Contains(uskills.SkillId)
}),
UserSkillsFound = (from uskills in u.UserSkills
where uskills.UserId == u.Id && searchedSkillsRaw.Contains(uskills.SkillId)
select uskills.UserId).Count()
} into userResults
where userResults.UserSkillsFound > 0
orderby userResults.UserSkillsFound descending
select userResults;
and this works! But it seems super bloated and inefficient to me. Especially the secondary part that counts the number of skills found.
Thanks for any advice you can give.
--r
I think that should do the trick:
(from u in users
where u.UserSkills.Any(skill => searchedSkillsRaw.Contains(skill.SkillId))
select new
{
u.Id,
u.FirstName,
u.LastName,
u.UserName,
UserPhone = u.UserDetail.Phone,
UserSkills = u.UserSkills,
UserSkillsFound = u.UserSkills.Where(skill => searchedSkillsRaw.Contains(skill.SkillId)).Count()
} into userResults
orderby userResults.UserSkillsFound descending
select userResult).ToList();
However, since this is a query that gets executed on SQL server I strongly recommend to remove the 'ToList()' call from the first query. Because that actually causes LINQ to run two separate queries on the SQL server. You should change it to IQueryable instead. The power of LINQ is to construct queries in several steps without having to actually execute it in between. So 'ToList' should be called only at the end when the entire query has been constructed. In fact what you currently do is running the second query in memory rather than on the database server.
In regards to your UserSkills one-to-many relation you do not need to do an explicity join in LINQ. You can just access the collection property instead.
Let me know if you need more explanation.
Michael
Why not just let people do, say, fUsers.UserSkills.Count()? It would reduce the amount of data retrieved from the server in the first place.
Alternatively, you could create a View that has a calculated field in it and then map that to a type. Would push the query for count down into the DB.