tags:

views:

92

answers:

4

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:

  1. Get all tasks from TasksAndAccounts where TasksAndAccounts.AccountId == accountId
  2. Get all groups from AccountsInGroups where AccountsInGroups.AccountId == accountId
  3. Get all tasks from TasksAndAccounts where TasksAndAccounts.GroupId is in the result set from step 2.
  4. 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?

A: 

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;
Reed Copsey
No, it doesn't work. Keep in mind that task record in TasksAndAccounts doesn't necessarily has both AccountId and GroupId specified. But even changing the inner join to outer doesn't help (in the generated sql query).
Roman R.
+1  A: 

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;
Jacob Proffitt
+1  A: 

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;
dahlbyk
I went for an IN rather than EXISTS. Cool. No idea which would be more performant, but interesting to see the difference.
Jacob Proffitt
A quick trial in SSMS shows an identical execution plan for equivalent IN and EXISTS queries. The bigger question is how the query provider translates the query. I suspect the EXISTS is easier to get right, but I could be wrong.
dahlbyk
Interesting. I wondered if it would because they *are* essentially equivalent (in this query case) and that's more or less what query planning is all about. The set operations IN and EXISTS are such useful set operations that I wish more query providers took the time to get them right.
Jacob Proffitt
+1  A: 

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.

Kyralessa
I would be more inclined to just define tasks and groupTasks with query syntax and then Union them together, but you're right that having a single expression isn't necessary. It would be interesting to compare the SQL and execution plan generated from the Union versus the subquery.
dahlbyk
The ease with which a single SQL statement can be composed of smaller statements like this is one of the best things about LINQ. It's easy to see whether the small pieces are correct or not, instead of grinding away at a really long statement that's harder to comprehend. At any rate, it's simple enough to convert any of these statements to query syntax; I just like the lambda syntax better, personally.
Kyralessa