I'm populating a FormView by setting the datasource to an IQueryable object that I get by doing a LINQ query. Basically it returns the number of employees that hold a certain "Position" within a certain "Shift".
int shiftID = 1;
var shiftCount = from x in context.Employees.Take(1)
select new
{
ManagerCount = ((from p in context.Persons
where p.PositionID == 1 && p.ShiftID == shiftID && p.IsEmployee == true
select p.PersonId).Count(),
PartTimeCount = ((from p in context.Persons
where (p.PositionID == 2 || p.PositionID == 3) && p.ShiftID == shiftID && p.IsEmployee == true
select p.PersonId).Count(),
etc, etc...
};
That part works fine. However, when I want to get the number of employees for all shifts, I can't quite figure out how to do it:
//Get all shifts 1, 2, and 3
var shiftCount = from x in context.Employees.Take(1)
select new
{
ManagerCount = ((from p in context.Persons
where p.PositionID == 1 && (p.ShiftID == 1 || p.ShiftID == 2 || p.ShiftID == 3) && p.IsEmployee == true
select p.PersonId).Count()
};
That doesn't work though because it of course returns 3 values and gives the Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression. error.
So I need to get the sum of the three values returned (it's not always three though, it depends on the position).
I've looked at various ways using Sum and LINQ grouping, but can't quite seem to work it out. Can anybody point me in the right direction?
I've got a few answers for you, but first it seems like you have something wrong with your first part of your query. You're doing .Take(1) on Employees which will give you only one x value. Since you don't use x in the remainder of your query then using Employees is redundant.
Now, since all of your queries are quite similar I've tried to remove repetition. The first thing to do is to get a common filter for the shifts you are filtering on.
If you have a single shift, use this:
int shiftID = 1;
var shifts = new [] { shiftID, };
If you have multiple shifts, use this:
var shifts = new [] { 1, 2, 3, };
Either way you end up with an array of integers representing the shifts you want to filter against. All of the below answers require this shifts array.
Then define a query for the employees in those shift, regardless of position for now.
var employeesInShifts =
from p in context.Persons
where p.IsEmployee
where shifts.Contains(p.ShiftID)
select p;
So you can then get the shift counts like so:
var shiftCount =
new
{
ManagerCount = employeesInShifts
.Where(p => p.PositionID == 1)
.Count(),
PartTimeCount = employeesInShifts
.Where(p => p.PositionID == 2 || p.PositionID == 3)
.Count(),
// etc
};
Perhaps a better alternative for you though, would be to turn the employeesInShifts query into a dictionary and then just pluck the values from the dictionary.
var employeesInShifts =
(from p in context.Persons
where p.IsEmployee
where shifts.Contains(p.ShiftID)
group p by p.PositionID into gps
select new
{
PositionID = gps.Key,
Count = gps.Count(),
})
.ToDictionary(pc => pc.PositionID, pc.Count);
var shiftCount =
new
{
ManagerCount = employeesInShifts[1],
PartTimeCount = employeesInShifts[2] + employeesInShifts[3],
// etc
};
The downside to this approach is that you really should check that the dictionary has values for each PositionID before getting the values.
That can be fixed by introducing an array of position ids that you want the dictionary to have and joining your results on that.
var positionIDs = new [] { 1, 2, 3, };
var employeesInShifts =
(from p in context.Persons
where p.IsEmployee
where shifts.Contains(p.ShiftID)
where positionIDs.Contains(p.PositionID)
select p).ToArray();
var allPositionEmployeesInShifts =
from pid in positionIDs
join p in employeesInShifts on pid equals p.PersonId into gps
select new
{
PositionID = pid,
Count = gps.Count(),
};
var countOfPositionID =
allPositionEmployeesInShifts
.ToDictionary(x => x.PositionID, x => x.Count);
var shiftCount =
new
{
ManagerCount = countOfPositionID[1],
PartTimeCount = countOfPositionID[2] + countOfPositionID[3],
// etc
};
Now that guarantees that your final dictionary will contain the counts of all of the position ids that you are wanting to query.
Let me know if this works or if you really needed to join on the Employees table, etc.
I would recommend looking at the Group By examples here:
Related
I am developing a query to grab and join some SQL tables in C# and am having some trouble with grouping and enumerables within the dataset. My query is below. This gives me the data in the format I'm looking for, but it takes way too long when I try to add the enumerated list as indicated below. When I look under the hood I can see it is executing way too many SQL queries. I'd like to get it to just one. Using LinqPad:
void Main()
{
var nightlyRuns = (from a in LoadTestSummaries
join b in LoadTestTestSummaryData
on a.LoadTestRunId equals b.LoadTestRunId
where a.TargetStack == "LoadEnv" &&
a.TestGuid != null &&
a.StartTime != null &&
a.LoadTestRunId != null
orderby a.StartTime
group new {a, b} by new
{
a.TestGuid,
a.Name,
a.Description,
a.StartTime,
a.Duration,
a.NumAgents,
a.NumHosts,
a.PassFail,
a.ResultsFilePath,
a.Splunk
}
into g
let scenarioStart = g.Min(s => s.a.StartTime) ?? g.Min(s => s.a.DateCreated)
let testCases = g.Select(s => s.b)
orderby scenarioStart
select new
{
TestGuid = g.Key.TestGuid,
ScenarioRun = new
{
Name = g.Key.Name,
Description = g.Key.Description,
StartTime = scenarioStart,
Duration = g.Key.Duration,
NumAgents = g.Key.NumAgents,
NumHosts = g.Key.NumHosts,
Result = g.Key.PassFail,
ResultsFilePath = g.Key.ResultsFilePath,
SplunkLink = g.Key.Splunk,
// PROBLEM: Causes too many queries:
TestRuns = from t in testCases select t.TestCaseId
}
}).ToLookup(g => g.TestGuid, g => g.ScenarioRun);
nightlyRuns["ba593f66-695f-4fd1-99c3-71253a2e4981"].Dump();
}
The "TestRuns" line is causing the excessive queries. Any idea what I am doing wrong here?
Thanks for any insight.
Tough answer to test but I think we can avoid the grouping and multiple queries with something like this: (https://msdn.microsoft.com/en-us/library/bb311040.aspx)
var nightlyRuns = (from a in LoadTestSummaries
join b in LoadTestTestSummaryData
on a.LoadTestRunId equals b.LoadTestRunId
where a.TargetStack == "LoadEnv" &&
a.TestGuid != null &&
a.StartTime != null &&
a.LoadTestRunId != null
into testGroup
select new
{
TestGuid = a.TestGuid,
ScenarioRun = new
{
Name = a.TestGuid,
Description = a.Description,
StartTime = a.StartTime ?? a.DateCreated,
Duration = a.Duration,
NumAgents = g.Key.NumAgents,
NumHosts = a.NumHosts,
Result = a.PassFail,
ResultsFilePath = a.ResultsFilePath,
SplunkLink = a.Splunk,
// PROBLEM: Causes too many queries:
TestRuns =testGroup
}
}).OrderBy(x=>x.StartTime).ToLookup(x => x.TestGuid, x => x.ScenarioRun);
nightlyRuns["ba593f66-695f-4fd1-99c3-71253a2e4981"].Dump();
I have a Linq to SQL query that I want to return three counts for number of rows. One for where one field where it does not equal zero (HaveCount), another for another field when it does not equal zero (WantCount), and another for another field when it does not equal zero (SaleCount).
Here is my query...
var counts = (from a in dc.tblMemberIssues
join b in dc.vwMembers on a.MemberID equals b.MemberID
where a.IssueID == issueID
group a by new { a.HaveCount, a.WantCount, a.SaleCount } into d
select new
{
HaveCount = d.Count(e => e.HaveCount != 0),
WantCount = d.Count(e => e.WantCount != 0),
SaleCount = d.Count(e => e.SaleCount != 0)
}).First();
However it does not return the expected results. I realise the grouping is wrong, but I'm not sure how to get the desired results.
For example if first part of the query (before the grouping) returned these two rows...
---------------------------------
HaveCount | WantCount | SaleCount
---------------------------------
1 | 0 | 1
1 | 1 | 0
My query now is returning... (ie. just one row)
1 | 0 | 1
But I want it to return counts from all rows...
2 | 1 | 1
What do I need to do to my query to make it work they way I need?
NB. Trying to do this with only a single database query.
You should not group on the columns in the count. If I understand you correctly you don't want to group on anything, but Linq does not allow that so put true as the group by. (or any other constant)
Change your query to:
var counts = (from a in dc.tblMemberIssues
join b in dc.vwMembers on a.MemberID equals b.MemberID
where a.IssueID == issueID
group a by true into d
select new
{
HaveCount = d.Count(e => e.HaveCount != 0),
WantCount = d.Count(e => e.WantCount != 0),
SaleCount = d.Count(e => e.SaleCount != 0)
}).First();
It looks like you need to save your counts to a list, then take the count of each type of count, like so:
var counts = (from a in dc.tblMemberIssues
join b in dc.vwMembers on a.MemberID equals b.MemberID
where a.IssueID == issueID).ToList();
var results = new
{
HaveCount = counts.Count(e => e.HaveCount != 0),
WantCount = counts.Count(e => e.WantCount != 0),
SaleCount = counts.Count(e => e.SaleCount != 0)
};
It's also possible you would want the sum instead:
var results = new
{
HaveCount = counts.Sum(e => e.HaveCount),
WantCount = counts.Sum(e => e.WantCount),
SaleCount = counts.Sum(e => e.SaleCount)
};
If you just want to count the number of non-zeroes even if some of the initial counts can be greater than 1, use the first query, otherwise, use the second query.
How can i select take 1 each data in column in linq that only return 1 row. Because when i put .Take(1) only 1 row result will appear but i want to have 2 row result with different entry
to make it clear here's what i mean
in c# here's my query
using (PharmacyDBEntities entities = new PharmacyDBEntities())
{
var positem = (from a in entities.POSEntries.Take(1)
where a.Invoice.AccountID == authLogin.userid && a.Invoice.InvoiceStatusID == 1
select new
{
a.Item.ItemCode,
a.Item.Name,
Quantity = (from b in entities.POSEntries where b.Invoice.AccountID == authLogin.userid && b.Invoice.InvoiceStatusID == 1 select b).Count(),
a.Item.SellingPrice
}).ToList();
gcItemList.DataSource = positem;
}
And the result
Is there any suggestion guys? will make big help to me. thanks
You can use Linq GroupBy
var positem = entities.POSEntries.Where( a=> a.Invoice.AccountID == authLogin.userid && a.Invoice.InvoiceStatusID == 1)
.GroupBy(x=>x.Item.ItemCode).Select(g=> new {
g.Key,
g.First().Item.Name,
Quantity = g.Count(),
g.First().Item.SellingPrice
}).ToList();
So, you want Distinct() instead of Take(1)? Something along these lines:
var positem = (from a in entities.POSEntries
where a.Invoice.AccountID == authLogin.userid && a.Invoice.InvoiceStatusID == 1
select new
{
a.Item.ItemCode,
a.Item.Name,
Quantity = (from b in entities.POSEntries where b.Invoice.AccountID == authLogin.userid && b.Invoice.InvoiceStatusID == 1 select b).Count(),
a.Item.SellingPrice
}).Distinct().ToList();
Take only returns first n elements. I think you need to try using the Distinct method.
I want to search through a user database and order my results according to how precise the match is. Exact matches on a users name should appear in the result before single word matches, as an example.
This is what i have (the variable 'value' contains a search term and 'query' contains an initial queryable i want to modify)
var values = value.Split(new [] {' ','\t', '\n', '\r'});
var q1 = query.Where(u => u.Id == valueAsInt || u.ExternalId == valueAsInt);
var q2 = query.Where(u => u.Name.Contains(value) || u.Username.Contains(value));
var q3 = query.Where(u => values.All(i => u.Name.Contains(i)) || values.All(i => u.Username.Contains(i)));
var q4 = query.Where(u => values.Any(i => u.Name.Contains(i)) || values.Any(i => u.Username.Contains(i)));
However, I now want to combine the results of q1 through q4 and have a new queryable which i can pass along. I also want to preserve the order of my queries, and frankly I have no idea how to go about doing this..
Answered by Silas Hansen in the comments
You should use ranking. e.g.
var result = query.Select(u =>
{
if (u.Id == valueAsInt || u.ExternalId == valueAsInt)
return new {Rank = 1, Item = u};
if (u.Name.Contains(value) || u.UserName.Contains(value) )
return new {Rank = 2, Item = u};
//Add the other conditions in here
return new {Rank = 3, Item = u};
}).OrderBy(u => u.Rank).Select(u => u.Item);
I have a table that has several columns: HarvestID, HarvestDate, UserID to mention the main ones.
For each date, there are going to be several HarvestID per day.
So far, I have the following linq query:
TheUserID and TheMonth are passed in as an int and a DateTime
var MyQuery = from h in MyDC.HarvestTable
where h.UserID == TheUserID
where h.HarvestDate.Month == TheMonth.Month
where h.HarvestDate.Year == TheMonth.Year
group h by h.HarvestDate.Day into TheDays
from d in TheDays
select new
{
TheDay = d.HarvestDate.Date,
TheDayCount = (from c in TheDay
select c.HarvestID).Count()
};
I'm looking to have the output be a list of counts per day. The query doesn't bug but the problem is that at the moment the query is not returning a unique row for each day. The grouping doesn't work and I'm not finding out why. What's wrong with this code?
Thanks.
It think your query should be
var MyQuery = from h in MyDC.HarvestTable
where h.UserID == TheUserID
where h.HarvestDate.Month == TheMonth.Month
where h.HarvestDate.Year == TheMonth.Year
group h by h.HarvestDate.Day into TheDays
select new
{
TheDay = TheDays.Key,
TheDayCount = TheDays.Count()
};
Here is a vry good refrence to above group by statement http://msdn.microsoft.com/en-us/vcsharp/aa336754.aspx#simple1
var MyQuery = from h in MyDC.HarvestTable
where h.UserID == TheUserID
&& h.HarvestDate.Month == TheMonth.Month
&& h.HarvestDate.Year == TheMonth.Year
group h by h.HarvestDate.Day into g
select new
{
TheDay = g.Key,
TheDayCount = g.Count()
};
This will not give you zeroes on the days where there is no data - but should give you a count where there is.
http://msdn.microsoft.com/en-us/vcsharp/aa336746 is my go-to page for LINQ examples.