A cumbersome linq query - c#

I have the following setup: Tasks, Accounts and Groups tables. Tasks can be assigned to both individual accounts and groups. I've made two supporting tables: TasksAndAccounts and AccountsInGroups. TasksAndAccounts table has the following fields: TaskId, AccountId, GroupId and AccountsInGroups has AccountId and GroupId fields. I'm trying to write a query that will return all tasks assigned to a given account id. The culprit here is the query should look first in TasksAndAccounts table to see if there are tasks related directly to the given account and then also look for any groups that the given account is associated with.
The basic algorithm is simple:
Get all tasks from TasksAndAccounts where TasksAndAccounts.AccountId == accountId
Get all groups from AccountsInGroups where AccountsInGroups.AccountId == accountId
Get all tasks from TasksAndAccounts where TasksAndAccounts.GroupId is in the result set from step 2.
Merge steps 1 and 3.
I've tried to tackle the issue in a few different ways but wasn't able to get any satisfactory result.
Any ideas on writing an elegant single query solution?

This should translate into an EXISTS subquery:
var tasks = from task in dc.TasksAndAccounts
where task.AccountId == accountId || (
from g in dc.AccountsInGroups
where g.AccountId == accountId && g.GroupId == task.GroupId
select g.GroupId
).Any()
select task;

Ugh. Looks like you'll need a sub-select.
var ids = from task in context.TaskAndAccounts
where task.AccountId == accountId ||
(from grp in context.AccountsInGroups
where grp.AccountId == accountId
select grp.GroupId).Contains(task.GroupId)
select task;

Personally, I'd do it something like this:
var tasks = db.TasksAndAccounts.Where(x => x.AccountId == accountId);
var groups = db.AccountsInGroups.Where(x => x.AccountId == accountId);
var groupIDs = groups.Select(x => x.GroupId);
var groupTasks = db.TasksAndAccounts.Where(x => groupIDs.Contains(x.GroupId));
var allTasks = tasks.Union(groupTasks);
It's more than one line, but it's a lot clearer than trying to cram the whole thing into one line, in my opinion. Since LINQ uses deferred execution, you still won't be executing anything until you're actually using the allTasks result set.

Should be something like:
var tasks = from taa in TasksAndAccounts.Where(t => t.AccountId == accountId)
join aig in AccountsInGroups.Where(a => a.AccountId == accountId) on taa.GroupId equals aig.GroupId
select taa;

Related

How to convert a lambda expression to LINQ?

How can I convert this expression to LINQ?
var result = users.FirstOrDefault(x => x.Name == userName)?
.Groups.FirstOrDefault(x => x.Group == userGroup);
I've started with:
var result = (from u in users
where u.Name == userName
select u).FirstOrDefault()?
My class is:
public class User
{
public string Name { get; set; }
public IEnumerable<Group> Groups { get; set; }
}
When creating this query, I don't have a separate groups list with which I can make a join on 2 tables.
But that's how far I managed to go. Is it possible to do a join within the same query?
I guess you want this:
var result = (from g in ((from u in users
where u.Name == userName
select u).FirstOrDefault().Groups)
where g == userGroup
select g).FirstOrDefault();
What you mean is how to convert method (or fluent) syntax to query (or comprehension) syntax. The first thing to note though is there's not one LINQ expression. The statement consists of two LINQ statements...
var user = users.FirstOrDefault(x => x.Name == userName);
var result = user?.Groups.FirstOrDefault(x => x.Group == userGroup);
...both of which can be written in query syntax, of which your starting point would be the first one.
However, the statement can be rewritten as one LINQ statement using SelectMany:
var result = users.Where(x => x.Name == userName)
.SelectMany(u => u.Groups.Where(g => g.Group == userGroup))
.FirstOrDefault();
This statement can be rewritten in one query-syntax statement:
var result = (from u in users
where u.Name == userName
from g in u.Groups
where g.Group == userGroup
select g).FirstOrDefault();
The advantage is that you don't need the null-propagation operator, which, by the way, you didn't apply sufficiently in your own statement.
One possible issue is that the results aren't necessarily identical. Originally you query a first user meeting a condition and of its groups a first group meeting another condition. The alternative query queries all users meeting a condition and from their groups the first one that meets another condition. So the first query may not return a result where the second does (if the matching group is not from the first user).
This may be an improvement or a flaw, I don't know. If the first condition uniquely identifies users it doesn't matter; the results will be the same. If it doesn't you may have to question its value because in a way it will return you a "random" user. You may want to use a lambda expression that narrows down the search to one specific user.

Filtering Related Entites with Entity Framework

According to this StackOverflow answer:
Linq to Entities - how to filter on child entities
you should be able to filter down the list of related entities in Entity Framework by utilizing a projection, like I've done here:
Company company = _context.Company
.Where(g => g.CompanyId == id)
.Select(comp => new
{
group = comp,
operators = comp.Operator,
formFamilies = comp.FormFamily.Where(ff => ff.IsActive ?? false)
}).AsEnumerable().Select(i => i.group).FirstOrDefault();
To give a quick overview of what I'm trying to obtain here, I'm trying to get a list of all of the active form families associated with this company object, however, whenever I restrict the results in any way, the result set is empty.
If the line were formFamilies = comp.FormFamily then it returns two results, one active one inactive
If the line is formFamilies = comp.FormFamily.Where(ff => true) then it returns nothing
If the line is formFamilies = comp.FormFamily.OrderBy(ff => ff.FormFamilyId) then it returns nothing.
Any sort of modification that I do to comp.FormFamily means the result set returns nothing, I've dug through the deepest sections of SA to try to find a solution, and tried every solution I've found, but nothing seems to cause this list to return anything.
Assuming that Company and FormFamily entities has one to many relationship I would suggest to use a join statement.Something like this should give you what you are looking for.
var company = from c in _context.Company
join f in _context.FormFamily
on c.Id equals f.CompanyId
where c.Id == id
select new Company()
{
Id = c.Id,
operators = c.Operator.ToList(),
formFamilies = c.FormFamily.Where(x=>x.IsActive ==
false).ToList()
} .FirstOrDefault();
Hope this helps.
I didn't quite understand what is your query is supposed to do. But it seems to me that you cannot just call Select method on another Select result method.
Anyway, you could simply use Include methods instead of projecting.
var company = _context.Company
.Where(c => c.Id == id)
.Include(c => c.FormFamily).Where(ff => ff.IsActive ?? false)
.ToList();
Did not test it. To prove it works or not be sure put an entity model in the question. Then I may produce more accurate answer.

Linq2SQL choose IDs where join returns negative

I am attempting to query for an ID from table that needs to be joined with a negative result. In other words choose all the ScacIDs where that ScacID is not present in the joined table ScacSetup. This query returns no result.
var tasksNotAssociated = from scac in db.Scacs
where !db.ScacSetupTasks.Any(s => s.ScacTaskID == taskID)
group scac by scac.ScacCode into scacNotAssociated
select scacNotAssociated.FirstOrDefault();
Great and yes my solution was to use the left join that was provided by my navigation property; ScacSetupTasks. Guess I should have posted my navigation property and tagged with Entity Framework. OK, here is what I found to work and is essentially a left join where leftside == null as suggested by MarcinJeraszek, thanks buddy.
var scacsNotAssociated = db.Scacs.Where(s => s.ScacSetupTasks.Count(sst => sst.ScacTaskID == taskID) == 0);

LINQ: Generate "AND" Expression instead of "OR" when using "CONTAINS"

I have this List:
string[] countries = {
"USA",
"CANADA"
};
When I run this query :
query = (from user in db where
user.Orders.Any(order => order.Price > 10 &&
countries.Contains(order.DestinationCountry)))
Output is a list of users that have Orders sent to "USA" OR "Canada".
but I want the list of users that have Orders sent to both "USA" AND" "CANADA".
I can do this using below code but i'm searching for a pure linq solution without any ForEach:
foreach (country in countries) {
query = (from user in query where
user.Orders.Any(order => order.Price > 10 &&
order.DestinationCountry == country));
}
Answers:
A. Using .Aggregate()
Generated query is just like For Each.
B.where countries.All(c => user.Orders.Any(o => o.Price > 10 && o.DestinationCountry == c))
When there is no element in Countries List (When I want all users based only on Price parameter), the result is not correct and other parameter is not considered!
Update 1:
I have tried .All() instead of .Contains() before posting and it returns 0 users.
Update 2:
I have updated my question to make it closer to the real problem.
lets say Country is not the only parameter.
Update 3:
Checked some answers and added the result to my question.
So you want a list of the users such that all the countries in the list are present in the set of order destinations?
Logically, that would be:
query = from user in db
where countries.All(c => user.Orders.Any(o => o.DestinationCountry == c))
select ...;
However, I'm not confident that EF will do what you want with that. It's not clear to me what the right SQL query would be to start with - in a simple way, at least.
query =
db.Users.Where(user =>
countries.All(country =>
user.Orders.Any(order =>
order.DestinationCountry == country)))
You can do it like this:
query = (from user in db where
user.Orders
.Where(o => countries.Contains(o.DestinationCountry))
.GroupBy(o => o.DestinationCountry)
.Count() == countries.Count
);
The idea is to keep only the orders going to countries of interest, then group by country, and check that the number of groups equals the number of countries.
It's possible using Enumerable.Aggregate:
query = countries.Aggregate(query,
(q, c) =>
from user in q
where user.Orders.Any(order => order.DestinationCountry == c)
select user);
but really, this is harder to understand than your foreach loop, so I'd just go with that.
Note that although I refer to a member of Enumerable, that member of Enumerable is actually building up an IQueryable<User> query chain just like your foreach loop, so this will not cause the filtering to move to the client.

Linq query with multiple objects in parent child relationship

I have a schema like this
Package -> Lists -> Users
All 'one to many' down the line...
So I want to run a query where I get all packages that a userID matches in users.
var pck = (from pk in context.Package
where pk.Lists[here's my problem]
I would assume the navigation properties here would be: pk.Lists. *Users.UserId* == MyUserId, however I'm not seeing the navigation properties at the Lists level.
I haven't gotten to more complex EF queries like this yet. I've looked around the web but haven't found anything to make it click. I turn to you stack. Somebody help me see the light!
EDIT: Thanks again stack, I will do my best to pay it forward!
Also, all of these answers enlightened me to the power of ef4!
I assume that a package contains multiple lists, and a list contains multiple users? You could try:
var pck = content.Package
// Outdented just for Stack Overflow's width
.Where(pk => pk.Lists.Any(list => list.Any(u => u.UserId == myUserId)));
Or use a cross-join:
var pck = from pk in content.Package
from list in pk.Lists
from user in list.Users
where user.UserId == myUserId
select ...; // Select whatever you're interested in
context.Packages.Where(p => p.Lists.Any(l => l.Users.Contains(MyUserId)))
or, if your user is something other then just a user id,
context.Packages.Where(p => p.Lists.Any(l => l.Users.Any(u => u.Id == MyUserId)))
var packages =
context.Package.Where(p =>
p.Lists.Any(l =>
l.Users.Any(u => u.UserId == MyUserId
)
);
If the links are non-nullable and direction is package has many lists and list has many users, then the query is pretty easy.
var pck = from user in context.Users
where user.UserId == userId
select user.List.Package;
try this:
pk.Lists.Any(l => l.Users.Any(u => u.UserId == MyUserId))

Categories