GroupBy

S

shapper

Hello,

I have the following lambda expression:

return _context.Regions.SelectMany(r => r.Centers, (r, l) => new
{ r.Id, r.Name, Centers = r.Centers.Where(c => c.Open == true) })
.Where(o => o.Centers.Count() > 0)
.OrderBy(o => o.Name)
.Select(o => new Models.Region {
Id = o.Id,
Name = o.Name
});

Basically I am trying to get all regions which are related to at least
one center.
But I want to group by Region ... And not have two regions if it is
related with two centers.

I tried to add .GroupBy(o => o.Id) after the Where but then I got the
error:

'System.Linq.IGrouping<int,AnonymousType#1>' does not contain a
definition for 'Name' and no extension method 'Name' accepting a first
argument of type 'System.Linq.IGrouping<int,AnonymousType#1>' could be
found (are you missing a using directive or an assembly reference?)

Does anyone knows how to solve this?

Thanks,
Miguel
 
P

Peter Duniho

shapper said:
Hello,

I have the following lambda expression:

return _context.Regions.SelectMany(r => r.Centers, (r, l) => new
{ r.Id, r.Name, Centers = r.Centers.Where(c => c.Open == true) })
.Where(o => o.Centers.Count() > 0)
.OrderBy(o => o.Name)
.Select(o => new Models.Region {
Id = o.Id,
Name = o.Name
});

Basically I am trying to get all regions which are related to at least
one center.
But I want to group by Region ... And not have two regions if it is
related with two centers.

I tried to add .GroupBy(o => o.Id) after the Where but then I got the
error:

'System.Linq.IGrouping<int,AnonymousType#1>' does not contain a
definition for 'Name' and no extension method 'Name' accepting a first
argument of type 'System.Linq.IGrouping<int,AnonymousType#1>' could be
found (are you missing a using directive or an assembly reference?)

Does anyone knows how to solve this?

You can start by following the advice that was provided previously when
you asked about this particular approach. The code you posted has all
the same problems you originally had back then, which undoubtedly is
making trouble as you try to extend the original code.

You also have apparently changed the problem statement since that
previous discussion, as I specifically asked if you could have more than
one region for a given center, and your implied answer (based on the
database description) was that you could not. Yet, now you seem to be
trying to handle that case that you suggested could not happen.

Now, as far as the specific error goes, it's because when you call
GroupBy(), you don't get the original enumeration (e.g.
IEnumerable<AnonymousType#1>), but rather an
IGrouping(int,AnonymousType#1>. To then do any ordering or projection,
you need to flatten the IGrouping back to an enumeration of AnonymousType#1.

It's not really clear from your question what you really want and/or
need to happen, but it seems to me that if you want to do any grouping,
you should apply that grouping last. Alternatively, you can use the
SelectMany() method to flatting the IGrouping.

Pete
 
S

shapper

You can start by following the advice that was provided previously when
you asked about this particular approach.  

Yes, I would just also like to understand why my approach is not
working ... Just that.

The code you posted has all
the same problems you originally had back then, which undoubtedly is
making trouble as you try to extend the original code.

You also have apparently changed the problem statement since that
previous discussion, as I specifically asked if you could have more than
one region for a given center, and your implied answer (based on the
database description) was that you could not.  Yet, now you seem to be
trying to handle that case that you suggested could not happen.

Maybe I didn't understand your question.
A Region can have many Centers. But one center has only one region.
In the case I am testing I have:
- 40 regions. Only one has centers associated with it: RegionId = 32.
- Region with Id=32 has 2 centers associated with it.
So I ended up with two records containing that region. I want only
one.

Maybe I am missing something?
Now, as far as the specific error goes, it's because when you call
GroupBy(), you don't get the original enumeration (e.g.
IEnumerable<AnonymousType#1>), but rather an
IGrouping(int,AnonymousType#1>.  To then do any ordering or projection,
you need to flatten the IGrouping back to an enumeration of AnonymousType#1.

It's not really clear from your question what you really want and/or
need to happen, but it seems to me that if you want to do any grouping,
you should apply that grouping last.  Alternatively, you can use the
SelectMany() method to flatting the IGrouping.

Lost about SelectMany(). I am already using it ...

And applying the GroupBy at the end was exactly what I tried first:

public IQueryable<Models.Region> FindWithCenters() {
return _context.Regions.SelectMany(r => r.Centers, (r, l) => new
{ r.Id, r.Name, Centers = r.Centers.Where(c => c.Open ==
true) })
.Where(o => o.Centers.Count() > 0)
.OrderBy(o => o.Name)
.Select(o => new Models.Region {
Id = o.Id,
Name = o.Name
}).GroupBy(p => p.Id);
}

But I get the error:
Cannot implicitly convert type
'System.Linq.IQueryable<System.Linq.IGrouping<int,Models.Region>>' to
'System.Linq.IQueryable<Models.Region>'.
An explicit conversion exists (are you missing a cast?)


Thank You,
Miguel
 
P

Peter Duniho

shapper said:
Yes, I would just also like to understand why my approach is not
working ... Just that.

If you have not understood my reply in the thread for your previous
question, you should ask for clarification in that thread. Be sure to
be state specifically and clearly what it is about the suggestion I
provided that you do not understand.
Maybe I didn't understand your question.
A Region can have many Centers. But one center has only one region.
In the case I am testing I have:
- 40 regions. Only one has centers associated with it: RegionId = 32.
- Region with Id=32 has 2 centers associated with it.
So I ended up with two records containing that region. I want only
one.

Maybe I am missing something?

Yes. As I explained before, your use of SelectMany() is completely
inappropriate. It specifically flattens the several enumerations of
Center instances into a single enumeration of Center instances, which
you then project to an enumeration of an anonymous type that contains,
for every Center instance: the Id for the Center's Region; the Name for
the Center's Region; and a collection of every Center referenced by that
Region where the Center's Open property is true.

So your result from the SelectMany() method is an enumeration of an
anonymous type that effectively copies any given Region instance once
for every Center that Region owns. You then eliminate every element of
that enumeration where the Region's Center collection contains no open
Centers, but otherwise the remaining Region instances are duplicated for
each Center a given Region contains.

All that was bad enough in the example you gave in your previous
question, but now you are specifically asking for the results to be
grouped by the Region instance when your data ALREADY has grouped the
data by Region. It is incredibly wasteful for you to write code that
strips that grouping away from the data, only then to have to add it
back in.
[...]
It's not really clear from your question what you really want and/or
need to happen, but it seems to me that if you want to do any grouping,
you should apply that grouping last. Alternatively, you can use the
SelectMany() method to flatting the IGrouping.

Lost about SelectMany(). I am already using it ...

Just because you're using it, that doesn't mean you're using it
correctly. You can't just toss in method calls wherever you like. They
have to be the correct method call in the correct place for the result
you want.

If you had really needed the SelectMany() method, it would have belonged
at the end, to flatten the output before returning. But as it turns
out, that's not really what you want anyway, at least not according to
your latest post.
And applying the GroupBy at the end was exactly what I tried first:

public IQueryable<Models.Region> FindWithCenters() {
return _context.Regions.SelectMany(r => r.Centers, (r, l) => new
{ r.Id, r.Name, Centers = r.Centers.Where(c => c.Open ==
true) })
.Where(o => o.Centers.Count() > 0)
.OrderBy(o => o.Name)
.Select(o => new Models.Region {
Id = o.Id,
Name = o.Name
}).GroupBy(p => p.Id);
}

But I get the error:
Cannot implicitly convert type
'System.Linq.IQueryable<System.Linq.IGrouping<int,Models.Region>>' to
'System.Linq.IQueryable<Models.Region>'.
An explicit conversion exists (are you missing a cast?)

The error seems pretty clear to me. The GroupBy() method returns one
type, and you have declared your method to return a completely different
type. Your method's return statement MUST return a value having the
same type as that declared for the method's return value.

Frankly, until you state clearly exactly what output you are looking to
get, there's no way to suggest a specific answer. However, based on the
method prototype you show above, in which you are trying to return an
enumeration of Region instances, it seems to me that the method is as
simple as this:

public IEnumerable<Models.Region> FindWithCenters()
{
return _context.Regions
.Where(r => r.Centers.Any(c => c.Open))
.OrderBy(r => r.Name);
}

That's it. You wanted an enumeration of Region instances that have a
non-empty Centers collection, and that's what the above does (I'll leave
the question of IQueryable vs IEnumerable to you…the Queryable class
provides a means to convert if your data provider isn't already
supporting that type).

Note that this is practically the same solution I provided in your
previous question. The only difference is that since you have
specifically stated in this most recent question that you want as a
result an actual enumeration of Region instances, I have simply left out
the projection to the anonymous type that you seemed to have wanted earlier.

You can, of course, actually return an IEnumerable<IGrouping<Region,
Center>> from your method if you like. And the simplest way to go about
doing something like that would in fact be to use the GroupBy() method.
But given that every Region instance already has an exact way to map
directly from the Region instance to its Center instances, why bother
with that?

Now, I will point out: your own code examples do not actually filter the
Center instances themselves. They examine the Center instances to
determine what Region instances have Centers with the Open property set
to "true". But that's all. You simply wind up with a list of Region
instances and do nothing with the Center instances past that.

That's not necessarily a problem. After all, with a list of Region
instances in hand like that, you can easily filter each Centers
collection according to the Open property again, as needed. But, if you
really want a result that is _only_ the Center instances that are open,
grouped by Region, the GroupBy() method is in fact a nice way to do
that. It would look something like this:

public IEnumerable<IGrouping<Region, Center>> FindWithCenters()
{
return _context.Regions
.SelectMany(r => r.Centers)
.Where(c => c.Open)
.GroupBy(c => c.Region, new RegionComparer());
}

where:

class RegionComparer : IEqualityComparer<Region>
{
public bool Equals(Region r1, Region r2)
{
return r1.Id == r2.Id;
}

public int GetHashCode(Region r)
{
return r.Id.GetHashCode();
}
}

If Region implements IComparable or if you are guaranteed that for any
given region ID, you have exactly one Region object instance allocated,
then you don't need the IEqualityComparer:

public IEnumerable<IGrouping<Region, Center>> FindWithCenters()
{
return _context.Regions
.SelectMany(r => r.Centers)
.Where(c => c.Open)
.GroupBy(c => c.Region);
}

Pete
 
S

shapper

shapper said:
Yes, I would just also like to understand why my approach is not
working ... Just that.

If you have not understood my reply in the thread for your previous
question, you should ask for clarification in that thread.  Be sure to
be state specifically and clearly what it is about the suggestion I
provided that you do not understand.
Maybe I didn't understand your question.
A Region can have many Centers. But one center has only one region.
In the case I am testing I have:
- 40 regions. Only one has centers associated with it: RegionId = 32.
- Region with Id=32 has 2 centers associated with it.
  So I ended up with two records containing that region. I want only
one.
Maybe I am missing something?

Yes.  As I explained before, your use of SelectMany() is completely
inappropriate.  It specifically flattens the several enumerations of
Center instances into a single enumeration of Center instances, which
you then project to an enumeration of an anonymous type that contains,
for every Center instance: the Id for the Center's Region; the Name for
the Center's Region; and a collection of every Center referenced by that
Region where the Center's Open property is true.

So your result from the SelectMany() method is an enumeration of an
anonymous type that effectively copies any given Region instance once
for every Center that Region owns.  You then eliminate every element of
that enumeration where the Region's Center collection contains no open
Centers, but otherwise the remaining Region instances are duplicated for
each Center a given Region contains.

All that was bad enough in the example you gave in your previous
question, but now you are specifically asking for the results to be
grouped by the Region instance when your data ALREADY has grouped the
data by Region.  It is incredibly wasteful for you to write code that
strips that grouping away from the data, only then to have to add it
back in.
[...]
It's not really clear from your question what you really want and/or
need to happen, but it seems to me that if you want to do any grouping,
you should apply that grouping last.  Alternatively, you can use the
SelectMany() method to flatting the IGrouping.
Lost about SelectMany(). I am already using it ...

Just because you're using it, that doesn't mean you're using it
correctly.  You can't just toss in method calls wherever you like.  They
have to be the correct method call in the correct place for the result
you want.

If you had really needed the SelectMany() method, it would have belonged
at the end, to flatten the output before returning.  But as it turns
out, that's not really what you want anyway, at least not according to
your latest post.


And applying the GroupBy at the end was exactly what I tried first:
    public IQueryable<Models.Region> FindWithCenters() {
      return _context.Regions.SelectMany(r => r.Centers, (r, l)=> new
{ r.Id, r.Name, Centers = r.Centers.Where(c => c.Open ==
true) })
        .Where(o => o.Centers.Count() > 0)
        .OrderBy(o => o.Name)
        .Select(o => new Models.Region {
          Id = o.Id,
          Name = o.Name
        }).GroupBy(p => p.Id);
     }
But I get the error:
Cannot implicitly convert type
'System.Linq.IQueryable<System.Linq.IGrouping<int,Models.Region>>' to
'System.Linq.IQueryable<Models.Region>'.
An explicit conversion exists (are you missing a cast?)

The error seems pretty clear to me.  The GroupBy() method returns one
type, and you have declared your method to return a completely different
type.  Your method's return statement MUST return a value having the
same type as that declared for the method's return value.

Frankly, until you state clearly exactly what output you are looking to
get, there's no way to suggest a specific answer.  

Basically I am looking for:
1. Select the Entities.Regions (_context.Regions) which property
Centers contains at least one center where Center.Open == true.
2. And instead of returning a IQueryable<Entities.Region> return a
IQueryable<Models.Region>.
In this case the map is simple:
Models.Region has two properties: int Id and String Name
Models.Entities has the same two properties: int Id and String
Name.

So basically I want something like:

IList<Models.Region> regions = new List<Models.Region>;
foreach (Entities.Region r in _context.Regions) {

if (r.Centers.Where(c => c.Open == true).Count() > 0) {
regions.Add(new Models.Region { Id = r.Id, Name = r.Name });
}

}
return regions.AsQueryable();

So if _context.Regions I have 70 Regions I will end up with 0 to 70
Regions at the end ... All unique.

And not repeated regions as I am getting now.

Well something like this ... Does it make sense?

Thanks,
Miguel
 
P

Peter Duniho

shapper said:
Basically I am looking for:
1. Select the Entities.Regions (_context.Regions) which property
Centers contains at least one center where Center.Open == true.
2. And instead of returning a IQueryable<Entities.Region> return a
IQueryable<Models.Region>.
In this case the map is simple:
Models.Region has two properties: int Id and String Name
Models.Entities has the same two properties: int Id and String
Name.

That is basically exactly the solution for which I provided code the
last time you asked this question. It's not clear why you want an
but it's trivial to create one from my said:
[...]
Well something like this ... Does it make sense?

The main thing that doesn't make sense to me is why you ignored my reply
in the previous thread, a reply which would have solved your problem
directly.

I'm also a little confused by your description of "Models.Entities"; the
rest of your post seems to imply that the equivalent type to
"Models.Region" is not "Models.Entities", but rather "Entities.Region".
But hopefully that isn't a problem I need to worry about.

Pete
 
R

RayLopez99

{stuff deleted}

Dude why don't you use the built in support WPF has (and Silverlight)
for hit regions, if you're trying to check for mouse clicks in certain
areas clicked on?

If you're not checking for mouse clicks, ignore this message.

RL
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

Linq Query and Lambda 3

Top