From: shapper on
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
From: Peter Duniho on
shapper wrote:
> 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
From: shapper on
On Feb 16, 11:54 pm, Peter Duniho <no.peted.s...(a)no.nwlink.spam.com>
wrote:
> shapper wrote:
> > 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.  

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

From: Peter Duniho on
shapper wrote:
> 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
From: shapper on
On Feb 17, 1:18 am, Peter Duniho <no.peted.s...(a)no.nwlink.spam.com>
wrote:
> shapper wrote:
> > 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




> 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