ChatGPT解决这个技术问题 Extra ChatGPT

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

I want to change some properties of a LINQ query result object without creating a new object and manually setting every property. Is this possible?

Example:

var list = from something in someList
           select x // but change one property
sorry about that! ihere's the correct address: robvolk.com/…
While this can be done, as the answers show, please note that this violates the nature of LINQ. LINQ methods are not supposed to cause side effects so doing this is not in keeping with the principle of least surprise. True to LINQ, you'd get the objects and then modify them.

J
JaredPar

I'm not sure what the query syntax is. But here is the expanded LINQ expression example.

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

What this does is use an anonymous method vs and expression. This allows you to use several statements in one lambda. So you can combine the two operations of setting the property and returning the object into this somewhat succinct method.


@Rob, Not easily. The syntax to get that working is ... unreadable at best. var query = from it in list select ((Func)(() => { it.x = 42; return it; }))();
I am getting "A lambda expression with a statement body cannot be converted to an expression tree" error. Its not for LINQ to SQL, any advice?
@surya That is exactly the message I got when trying it with Linq to SQL. Adding a ToList() before that seems to do the trick. Not sure why you get that though. If it is Entity Framework, it does not work with that either.
@surya when you call ToList() you're actually bringing the data back into memory. So its not actually Linq to SQL anymore then.
According to Eric Lippert this solution is "... inefficient, confusing, unmaintainable, unnecessary, wasteful, depends on implementation details of the query operators, does not generalize to non-LINQ-to-objects scenarios, and an abuse of an operator intended to answer a question, not execute a side effect.". See his comments to this anwer
M
Mahmoud Gamal

If you just want to update the property on all elements then

someList.All(x => { x.SomeProp = "foo"; return true; })

In EF (Entity Framework): to replace a property on all objects of a IEnumerable, the accepted answer worked for me. Working code for me: var myList = _db.MyObjects.Where(o => o.MyProp == "bar").AsEnumerable().Select(x => { x.SomeProp = "foo"; return x; });
J
Jan Zahradník

I prefer this one. It can be combined with other linq commands.

from item in list
let xyz = item.PropertyToChange = calcValue()
select item

Expression trees cannot contain assignments
@Daniel have you tried your hypothesis before posting?
Yes; I actually tried your solution. Here is the latest proposal on adding this functionality to c#: github.com/dotnet/csharplang/discussions/4727
J
Joshua Belden

There shouldn't be any LINQ magic keeping you from doing this. Don't use projection though that'll return an anonymous type.

User u = UserCollection.FirstOrDefault(u => u.Id == 1);
u.FirstName = "Bob"

That will modify the real object, as well as:

foreach (User u in UserCollection.Where(u => u.Id > 10)
{
    u.Property = SomeValue;
}

I like this, my question is in first example what if some u > 10 is not found in list? I added a null check and seems to work. Also LHS I named u to v. +1 though. Very concise.
D
Daniel Brückner

It is not possible with the standard query operators - it is Language Integrated Query, not Language Integrated Update. But you could hide your update in extension methods.

public static class UpdateExtension
{
    public static IEnumerable<Car> ChangeColorTo(
       this IEnumerable<Car> cars, Color color)
    {
       foreach (Car car in cars)
       {
          car.Color = color;
          yield return car;
       }
    }
}

Now you can use it as follows.

cars.Where(car => car.Color == Color.Blue).ChangeColorTo(Color.Red);

It's not that you want to update the base collection. We want the result collection property updated.
Well LINQ replaces SQL which stands for Structured Query Language, but it still exposes the ability ability to UPDATE, DELETE, and INSERT. So I wouldn't argue that semantics is the thing preventing this function.
P
Pierre

If you want to update items with a Where clause, using a .Where(...) will truncate your results if you do:

list = list.Where(n => n.Id == ID).Select(n => { n.Property = ""; return n; }).ToList();

You can do updates to specific item(s) in the list like so:

list = list.Select(n => { if (n.Id == ID) { n.Property = ""; } return n; }).ToList();

Always return item even if you don't make any changes. This way it will be kept in the list.


I
Informitics

We often run into this where we want to include a the index value and first and last indicators in a list without creating a new object. This allows you to know the position of the item in your list, enumeration, etc. without having to modify the existing class and then knowing whether you're at the first item in the list or the last.

foreach (Item item in this.Items
    .Select((x, i) => {
    x.ListIndex = i;
    x.IsFirst = i == 0;
    x.IsLast = i == this.Items.Count-1;
    return x;
}))

You can simply extend any class by using:

public abstract class IteratorExtender {
    public int ListIndex { get; set; }
    public bool IsFirst { get; set; } 
    public bool IsLast { get; set; } 
}

public class Item : IteratorExtender {}

V
Victor Wilson
S
Stefan R.

Since I didn´t find the answer here which I consider the best solution, here my way:

Using "Select" to modify data is possible, but just with a trick. Anyway, "Select" is not made for that. It just executes the modification when used with "ToList", because Linq doesn´t execute before the data is being needed. Anyway, the best solution is using "foreach". In the following code, you can see:

    class Person
    {
        public int Age;
    }

    class Program
    {
        private static void Main(string[] args)
        {
            var persons = new List<Person>(new[] {new Person {Age = 20}, new Person {Age = 22}});
            PrintPersons(persons);

            //this doesn't work:
            persons.Select(p =>
            {
                p.Age++;
                return p;
            });
            PrintPersons(persons);

            //with "ToList" it works
            persons.Select(p =>
            {
                p.Age++;
                return p;
            }).ToList();
            PrintPersons(persons);

            //This is the best solution
            persons.ForEach(p =>
            {
                p.Age++;
            });
            PrintPersons(persons);
            Console.ReadLine();
        }

        private static void PrintPersons(List<Person> persons)
        {
            Console.WriteLine("================");
            foreach (var person in persons)
            {
                Console.WriteLine("Age: {0}", person.Age);
            ;
            }
        }
    }

Before "foreach", you can also make a linq selection...


d
d219
var item = (from something in someList
       select x).firstordefault();

Would get the item, and then you could do item.prop1=5; to change the specific property.

Or are you wanting to get a list of items from the db and have it change the property prop1 on each item in that returned list to a specified value? If so you could do this (I'm doing it in VB because I know it better):

dim list = from something in someList select x
for each item in list
    item.prop1=5
next

(list will contain all the items returned with your changes)


While this will work, I think the OP was asking how to write the LINQ statement without having to write a for each loop.
a
alexDuty

This is the very easiest solution: list.Where(t => t.Id == 1).ToList().ForEach(t => t.Property = "something");

Tried and worked, quoted from here: https://visualstudiomagazine.com/articles/2019/07/01/updating-linq.aspx


J
James Poulose

I ran into the exact requirement (OP's question) and I don't see that being answered anywhere above using expressions.

Assuming x is of type MyType, you can call a method that returns MyType, which takes in x. Inside that method you can do all the modifications you want. The code will look something like this.

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

private MyType GetModifiedObject(MyType x)
{
   x.Property1 = "Changed Value";
   return x;
}

Well, basically this is identical to the accepted answer and others.
k
kazem
User u = UserCollection.Single(u => u.Id == 1);
u.FirstName = "Bob"

No need to duplicate the existing answers on this thread. If you have a comment about that answer, you can place it there.