27

I have the following Entity Framework query:

var results = from r in db.Results
              select r;

I'm using AutoMapper to map to another type:

var mapped = Mapper.Map<IEnumerable<Database.Result>, IEnumerable<Objects.Result>>(results);

In my Objects.Result type, I have a property called reason that is not coming from the database. It is coming from another source that I need to basically populate back into my mapped type:

var reasons = new List<Reason>
{
    new Reason { Id = 1, Reason = "asdf..." }
};

I need to join the reasons with my mapped collection and set the Reason property in my mapped collection using the value from my reasons collection. Is this possible?

 // need something like this:
 mapped = from m in mapped
          join r in reasons on m.Id equals r.Id
          update m.Reason = r.Reason
          select m;

Obviously the above code doesn't compile, but is there code I can write that does what I want?

Dismissile
  • 32,564
  • 38
  • 174
  • 263

4 Answers4

30

Do the mutation in a loop. Optimally, Linq should be free of mutations to the collection(s) it operates against. Use Linq to filter, order, project your data, use traditional techniques to modify.

var joinedData = from m in mapped 
                 join r in reasons on m.Id equals r.Id 
                 select new { m, r };

foreach (var item in joinedData)
{
    item.m.Reason = item.r.Reason;
}
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
  • Thanks. I guess performance-wise it's not a huge hit since I'm paging the data before I map, so iterating over a second time won't be a problem. – Dismissile Nov 14 '11 at 20:21
  • 1
    If you measure and find that this is a performance bottleneck, come back and address it, we *can* make a mutation if we *have* to. However, I wouldn't do that until I knew that this is what is slowing me down. – Anthony Pegram Nov 14 '11 at 20:23
  • I page about 10 items at a time. I highly doubt it would cause any performance issues :) – Dismissile Nov 14 '11 at 20:25
  • Just thought I would point this out since it became clear to me is that the referential integrety of the individual objects in the collections stays intact. In other words after getting through the joining and then into the looping the code still knows which objects you are working with. When you update the values its the original objects from the original collections that get updated! Linq is awesome! – Colin Pear Jan 11 '13 at 01:08
14

This may save lot of your time. Below code is for Join two collections and to set property value of first collection.

class SourceType
{
    public int Id;
    public string Name;
    public int Age { get; set; }
    // other properties
}

class DestinationType
{
    public int Id;
    public string Name;
    public int Age { get; set; }
    // other properties
}
    List<SourceType> sourceList = new List<SourceType>();
    sourceList.Add(new SourceType { Id = 1, Name = "1111", Age = 35});
    sourceList.Add(new SourceType { Id = 2, Name = "2222", Age = 26});
    sourceList.Add(new SourceType { Id = 3, Name = "3333", Age = 43});
    sourceList.Add(new SourceType { Id = 5, Name = "5555", Age = 37});

    List<DestinationType> destinationList = new List<DestinationType>();
    destinationList.Add(new DestinationType { Id = 1, Name = null });
    destinationList.Add(new DestinationType { Id = 2, Name = null });
    destinationList.Add(new DestinationType { Id = 3, Name = null });
    destinationList.Add(new DestinationType { Id = 4, Name = null });


    var mapped= destinationList.Join(sourceList, d => d.Id, s => s.Id, (d, s) =>
    {
        d.Name = s.Name;
        d.Age = s.Age;
        return d;
    }).ToList();
Towhidul Islam Tuhin
  • 1,051
  • 13
  • 10
  • 2
    You should explain what it is this is doing. – Matt R Oct 20 '15 at 17:05
  • 2
    Exactly what I needed... what it is doing is building a statement lambda on the joined data, where the statements are modifying and returning the destination object from the join. Note: this only works directly as is with LINQ to Objects and not LINQ to SQL because the latter expects an expression tree type (Expresson) whereas the former expects a delegate type (Func). I needed to join an EF DbSet with a POCO based DTO and was able to do it by forcing the IQueryable (LINQ to SQL) to be IEnumerable (LINQ to Objects) by adding ".AsEnumerable" before the Join – PillowMetal Aug 11 '20 at 23:50
  • One more thing... @anthony-pegram has a point in his answer that ideally, LINQ collections should be immutable, but I like this more concise approach better – PillowMetal Aug 11 '20 at 23:53
1

Linq shouldn't be used to mutate the object. Having said that, from the performance standpoint, I don't like the extra loop that the "foreach" solution requires either.

So, this is my solution:

Func<ObjectType, AnotherType, ObjectType> Fill = delegate (ObjectType x, AnotherType a)
{
    x.SomeProperty = a;
    x.Date = DateTime.Now;
    return x;
};

var result = from source in sources
             join anotherSource in otherSource on source.Id equals anotherSource.Id
             select Fill(source, anotherSource);

Although it's not pure linq, I think it's pretty clear that a side effect will be performed and there is not extra looping and no extra unnecessary new objects instantiated.

Marisol Gutiérrez
  • 1,046
  • 8
  • 6
  • Doesn't the call to the delegate causes a foreach anyway, since the delegate Invoke is a Collection behind the curtains anyway? why not just make the method normally? i know you will lose flexibility, but at least you avoid a call to a foreach of 1 object :-\ – Zorkind Jan 05 '21 at 17:07
  • If you want to sink your teeth into my reasoning, this thread might illuminate why i am asking you about this. https://stackoverflow.com/questions/16220151/net-single-vs-multicast-delegates – Zorkind Jan 05 '21 at 17:19
1

One brute force method would be:-

foreach(var m in mapped)
{
    m.Reason = reasons.Single(r=> r.Id == m.Id).Reason;
}

In fact, this is implementation is quite close to your pseudo-code.

Adam Ralph
  • 29,453
  • 4
  • 60
  • 67