Reputation: 9488
The title is pretty terrible, so let me try to explain in code. I have a an entity called Change
and it has a collection of ChangeDetail
entities. I want to project a collection of Change
entities to a DTO called ChangeListItem
. I've been messing around with LINQPad and I actually have the projection done and working as I want, but what I can't get to work is to null out all ChangeListItem
s in the group except for the first one. Here's my code:
Change class
public class Change : {
public DateTime ChangedDateTime { get; set; }
#region Navigation Properties
public virtual ICollection<ChangeDetail> Details { get; private set; }
#endregion
public Change() {
this.Details = new List<ChangeDetail>();
}
}
ChangeDetail class
public class ChangeDetail {
public string Details { get; set; }
#region Navigation Properties
public int ChangeId { get; set; }
public virtual Change Change { get; set; }
#endregion
}
My LINQPad attempt
var changes =
Changes
.SelectMany(c => c.Details)
.OrderByDescending(c => c.Change.ChangedDateTime)
.Select(c => new ChangeListItem
{
ChangedDateTime = c.Change.ChangedDateTime,
Details = c.Details
})
.GroupBy(c => c.ChangedDateTime)
.ForEach(g =>
{
g
.Skip(1)
.ForEach(c =>
{
c.ChangedDateTime = null;
});
});
When I dump that into the results pane I still get a collection of groups of ChangeListItem
, but the ChangedDateTime
property is still filled out. I'm sure I'm missing a reassignment somewhere along the way, even though I thought the one I'm doing should be good enough. How can I get it to what I want?
I want to go from this:
To this:
Update As requested here's the code for the ForEach
extension:
public static class IQueryableExtensions {
public static IQueryable<T> ForEach<T>(
this IEnumerable<T> source,
Action<T> action) {
if (source == null) {
throw new ArgumentNullException("source");
}
if (action == null) {
throw new ArgumentNullException("action");
}
foreach (T t in source) {
action(t);
}
return source.AsQueryable<T>();
}
}
Upvotes: 2
Views: 541
Reputation: 113442
You're treating a LINQ query as though it were a collection. I assume your ForEach
extension looks like this:
foreach(var item in source)
{
SideEffect(item);
}
return source;
Here the results of the query are enumerated once, and each of its result items is mutated. But then when when you enumerate the query again, its results are recreated from scratch; consequently the mutations are 'lost'.
In theory, it would be possible to include some strategic ToList()
calls into your code to get the desired effect. But this isn't in the spirit of LINQ. Don't mix LINQ and side-effects. If you want to conditionally project some items and leave others untouched, do it this way:
changeGroup.Select( (c, index) => index == 0 ? c : new ChangeListItem
{
ChangedDateTime = null,
Details = c.Details
} );
Btw, you may need to call AsEnumerable()
before you perform this transformation as I don't think LINQ to Entities supports the overload of Select
that includes the item index (can't recall off-hand, though).
Upvotes: 5