Saturday, May 16, 2009

LINQ: Select an object, but change some properties without creating a new object

Q: Using LINQ, if I wanted to perform some query and return the object from the query, but change only some of the properties in that object, how would I do this without creating a new object and manually set every property? Is this possible?

Example:

var list = from something in someList
           select x; // but change one property

This post is based off of my Stack Overflow post on the same topic.

You can do it via the LINQ extension methods pretty easily, as JaredPar answered:

var query = someList.Select(x => { x.SomeProp = "foo"; return x; })

But what if you wanted to use declarative syntax similar to my above example?  You could write a simple extension method to do that.   Example usage:

// select some monkeys and modify a property in the select statement
// instead of creating a new monkey and manually setting all properties
var list = from monkey in monkeys
           select monkey.Set(monkey1 =>
           {
               monkey1.FavoriteFood += " and banannas";
           });

This is much quicker and more concise than creating a new monkey object and manually setting all properties, like so:

// instead of
var list = from monkey in monkeys
           select new Monkey
           {
               Name = monkey.Name,
               FavoriteFood = monkey.FavoriteFood += " and banannas",
               FavoriteBeer = monkey.FavoriteBeer,
               FavoriteActivity = monkey.FavoriteActivity,
               EyeColor = monkey.EyeColor,
               HairColor = monkey.HairColor,
               LikesToFlingPoo = monkey.LikesToFlingPoo
               // etc...
           };

Answer: Write an extension method.  I wrote an extension method called Set that to facilitate this.  All it does is allow you to write a lambda expression inside your select statement that returns the same object that you pass it.  You can set any number of properties on that object without creating a new instance of it.  Here’s the source code:

namespace System.Linq
{
    public static class LinqExtensions
    {
        /// <summary>
        /// Used to modify properties of an object returned from a LINQ query
        /// </summary>
        public static TSource Set<TSource>(this TSource input, 
Action<TSource> updater) { updater(input); return input; } } }

18 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. this method doesn't work with LINQ to SQL rite?
    only work with an existing object?

    i got this error...
    "A lambda expression with a statement body cannot be converted to an expression tree"

    ReplyDelete
  3. I haven't tested it with LINQ to SQL, sorry. I designed it for use with LINQ to Objects primarily but I'm sure with some tweaks it will work with other flavors of LINQ. I'm pretty sure it works with LINQ to XML. Post a comment if you find a solution.

    ReplyDelete
  4. you could replace the delegate with the Action() generic delegate class.

    your method then becomes:
    public static TSource Set(this TSource input, Action updater)
    {
    updater(input);
    return input;
    }

    ReplyDelete
  5. Good call, Thiago. I wrote this before I completely understood Action<> & Func<> and haven't updated the code.

    ReplyDelete
  6. or use foreach method:

    monkeys.ForEach(x => { monkey1.FavoriteFood += " and banannas" } )

    ReplyDelete
  7. Very helpful! Thx, KK

    ReplyDelete
  8. Any solution found for LINQ to SQL? Pls provide the select statement for the same in linq

    ReplyDelete
  9. Congratulations! Very nice and clean technique!

    All the best,
    Cosmin

    ReplyDelete
  10. I have same problem as @y2kstephen.
    I also got error
    "A lambda expression with a statement body cannot be converted to an expression tree"
    while trying to use it in Linq to SQL.

    ReplyDelete
  11. Nice extension. I've been too lazy to do this myself, so I'm glad you did it for me. :)

    ReplyDelete
  12. On the first parameter of the Set method, I'm gettting the 'A lambda expression with a statement body cannot be converted to a an expression tree.'
    Any clue on how to resolve this?

    ReplyDelete
  13. Are you trying this using LINQ to SQL? This isn't supported by that because there's no logic to convert the method into SQL. Try enumerating your result set first like so:

    var result = db.Table.ToArray();

    then call my method.

    ReplyDelete