tags:

views:

34

answers:

2

I had the following Linq code:

    var allRequests = model.GetAllRequests();

    var unsatisifedRequests = (from request in allRequests
                               where request.Satisfied == false
                               select request)
                               .OrderBy(r => r.RequestedOn)
                               .GroupBy(r => r.RequestedCountryId);

After which I then did a foreach over unsatifiedRequests building a new TOARequestListSummary object for each. This meant if I "returned" 4 items from the query, it would make 4 calls to the DB, once per loop of the foreach to grab the individual rows.

This seems to be the wrong way to use Linq, so I tries to convert this query to one which used projections to return the TOARequestListSummary objects directly and I came up with:

var summaries = (from request in allRequests
                             where request.Satisfied == false
                             group request by request.RequestedCountryId into requestGroups
                             select new TOARequestListSummary
                             {
                                 CountryName = requestGroups.First().RequestedCountry.Description,
                                 RequestCount = requestGroups.Count(),
                                 FirstRequested = requestGroups.First().RequestedOn
                             });

But when I run this, I get the following exception:

Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.

I have got as far as knowing that the Linq equivalent to EXISTS is Contains, but I have no idea how to indroduce this into the query.

+2  A: 

This should work for you:

var summaries = (from request in allRequests
                 where request.Satisfied == false
                 group request by request.RequestedCountry into g
                 select new TOARequestListSummary
                 {
                     CountryName = g.Key.Description,
                     RequestCount = g.Count(),
                     FirstRequested = g.Min(i => i.RequestedOn)
                 });

In your original version of this query (the second one you posted), your group's key was the RequestedCountryId. Though this will technically be grouping on that, you actually want to use the associated object. This way you'll have easy access to the needed properties and won't need to worry about grabbing the first item.

Ryan Versaw
Actually, this isn't quite correct. In my example `RequestedOn` is assumed to be on the `RequestedCountry` object, while yours has it on the `request` itself. Is there a certain way you want to determine the `FirstRequested`? If not, you can go ahead and use `requestGroups.First().RequestedOn` as you had above. If you want the smallest or largest (or some other way), I can make changes to correct it.
Ryan Versaw
RequestedOn is a DateTime column and I was trying to get them ordered so the ones requested first were at the top of the list.When I tried the code in your answer, it looks g is a collection of Country obejcts (the type of RequestedCountry) so .Requested on does not compile. When I tried g.First().Requested on, I get the following excpetion:The member 'Logica.Aurora.Data.Entities.TOARequest.RequestedCountry' has no supported translation to SQL.Which, I presume, means that it can't be grouped on?
Colin Desmond
Is `RequestedCountry` a property LinqToSql auto-generated from your DB relationships? So should `FirstRequested` be the oldest `request` for that group, or should it be the date of the oldest `request`?
Ryan Versaw
RequestedCountry is autogenerated, and FirstRequested is the earliest date of the group of requests.
Colin Desmond
A: 

Sorry, this is an answer, rather than an additional comment to Ryan's answer, but it is too long to fit...

This gets very strange. In LinqPad the following works a treat:

from request in TOARequests
where request.Satisfied == false
&& request.Active == true
orderby request.RequestedOn
group request by request.RequestedCountry into g
select new 
{
    CountryName = g.Key.Description,
    RequestCount = g.Count(),
    FirstRequested = g.First().RequestedOn
}

But the following throws the same translation exception in C#

var summaries = (from request in context.Repository<TOARequest>()
                             where request.Satisfied == false
                             && request.Active == true
                             orderby request.RequestedOn
                             group request by request.RequestedCountry into g
                             select new
                             {
                                 CountryName = g.Key.Description,
                                 RequestCount = g.Count(),
                                 FirstRequested = g.First().RequestedOn
                             }).ToList();

The only difference I can see if the ToList(), but even without that when I try to enumerate the list, it throws the exception.

Colin Desmond
You'll probably want to use `FirstRequested = g.Min(i => i.RequestedOn)` and get rid of the orderby (like I did in my recent edit). I'm still not sure why you're getting that Sql translation exception though, as it should work fine (as shown in LinqPad).
Ryan Versaw
Thanks, the Min works great. I've no idea why it doesn't work in code, as it works fine in LinqPad. Thanks for your time.
Colin Desmond
I have marked your previous answer as correct as it works in LinqPad, there must be something strange with my code.
Colin Desmond
Okay, cool. The only thing I can think of right now is to regenerate your dbml's designer, but I have no idea if that will actually fix it for you.
Ryan Versaw