LINQ: selecione um objeto e altere algumas propriedades sem criar um novo objeto

217

Desejo alterar algumas propriedades de um objeto de resultado da consulta LINQ sem criar um novo objeto e definir manualmente todas as propriedades. Isso é possível?

Exemplo:

var list = from something in someList
           select x // but change one property
Rob Volk
fonte
7
Eu escrevi um método de extensão baseado na resposta de JaredPar abaixo, que permite que você faça isso usando sintaxe declarativa, como no meu exemplo. Confira: blog.robvolk.com/2009/05/…
Rob Volk
3
@RobVolk, o link parece morto - ganhou um novo? Aqui está um arquivo - LINQ: selecione um objeto, mas altere algumas propriedades sem criar um novo objeto
KyleMit 4/16
blog.robvolk.com/2009/05/… não encontrado Erro DNS
Kiquenet 9/17/17
1
me desculpe por isso! ihere é o endereço correto: robvolk.com/…
Rob Volk

Respostas:

379

Não sei ao certo qual é a sintaxe da consulta. Mas aqui está o exemplo expandido da expressão LINQ.

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

O que isso faz é usar um método anônimo vs e expressão. Isso permite que você use várias instruções em um lambda. Portanto, você pode combinar as duas operações de configuração da propriedade e retorno do objeto nesse método um tanto sucinto.

JaredPar
fonte
4
@ Rob, não é fácil. A sintaxe para fazer isso funcionar é ... ilegível na melhor das hipóteses. var query = dele na lista select ((Func <Foo>) (() => {it.x = 42; retorne;})) ();
11119 JaredPar
35
Estou recebendo o erro "Uma expressão lambda com um corpo de instrução não pode ser convertida em uma árvore de expressões". Não é para LINQ to SQL, algum conselho?
surya
2
@spiderdevil +1 para você - você não odeia a disparidade? Esta não é a primeira vez que me deparei com uma situação em que o código funciona apenas para o Linq to Object e não para o Linq to SQL / EF. Pode ser uma solução aqui , mas ainda não tentei usá-lo porque não tenho tempo.
Dislexicanaboko
11
@surya Essa é exatamente a mensagem que recebi ao tentar com o Linq to SQL. Adicionando um ToList()antes que parece fazer o truque. Não sei por que você conseguiu isso. Se for o Entity Framework, também não funciona com isso.
Raziel
5
@surya, quando você chama ToList (), está realmente trazendo os dados de volta à memória. Então, na verdade, não é mais o Linq to SQL.
Oak
60

Se você deseja apenas atualizar a propriedade em todos os elementos,

someList.All(x => { x.SomeProp = "foo"; return true; })
Jon Spokes
fonte
3
No EF (Entity Framework): para substituir uma propriedade em todos os objetos de um IEnumerable, a resposta aceita funcionou para mim. Código de trabalho para mim: var myList = _db.MyObjects.Where (o => o.MyProp == "bar"). AsEnumerable (). Select (x => {x.SomeProp = "foo"; return x;}) ;
firepol
32

Eu prefiro este. Pode ser combinado com outros comandos linq.

from item in list
let xyz = item.PropertyToChange = calcValue()
select item
Jan Zahradník
fonte
24

Não deve haver mágica do LINQ impedindo você de fazer isso. Não use projeção, pois isso retornará um tipo anônimo.

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

Isso modificará o objeto real, bem como:

foreach (User u in UserCollection.Where(u => u.Id > 10)
{
    u.Property = SomeValue;
}
Joshua Belden
fonte
Eu gosto disso, minha pergunta é no primeiro exemplo, e se algum u> 10 não for encontrado na lista? Eu adicionei uma verificação nula e parece funcionar. Também LHS eu nomeei u para v. +1 embora. Muito conciso.
Desaivv
11

Não é possível com os operadores de consulta padrão - é Consulta Integrada ao Idioma, não Atualização Integrada ao Idioma. Mas você pode ocultar sua atualização nos métodos de extensão.

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;
       }
    }
}

Agora você pode usá-lo da seguinte maneira.

cars.Where(car => car.Color == Color.Blue).ChangeColorTo(Color.Red);
Daniel Brückner
fonte
1
Não é que você queira atualizar a coleção base. Queremos que a propriedade da coleção de resultados seja atualizada.
Michael Brennt
4
Bem LINQ substitui SQL que significa Estruturada de Consulta Língua, mas ainda expõe a capacidade capacidade de UPDATE, DELETEe INSERT. Portanto, eu não argumentaria que a semântica é a coisa que impede essa função.
precisa saber é o seguinte
5

Se você deseja atualizar itens com uma Wherecláusula, usar .Where (...) truncará seus resultados se você:

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

Você pode fazer atualizações em itens específicos da lista da seguinte maneira:

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

Sempre devolva o item, mesmo que você não faça nenhuma alteração. Dessa forma, ele será mantido na lista.

Pierre
fonte
1

Muitas vezes, encontramos isso onde queremos incluir o valor do índice e o primeiro e o último indicadores em uma lista sem criar um novo objeto. Isso permite que você saiba a posição do item na sua lista, enumeração etc. sem precisar modificar a classe existente e depois saber se você está no primeiro item da lista ou no último.

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;
}))

Você pode simplesmente estender qualquer classe usando:

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

public class Item : IteratorExtender {}
Informatics
fonte
0

Como não encontrei aqui a resposta que considero a melhor solução, aqui do meu jeito:

É possível usar "Selecionar" para modificar dados, mas apenas com um truque. De qualquer forma, "Selecionar" não é feito para isso. Ele apenas executa a modificação quando usado com "ToList", porque o Linq não é executado antes que os dados sejam necessários. De qualquer forma, a melhor solução é usar "foreach". No código a seguir, você pode ver:

    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);
            ;
            }
        }
    }

Antes de "foreach", você também pode fazer uma seleção linq ...

Stefan R.
fonte
0
var item = (from something in someList
       select x).firstordefault();

Receberia o item, e então você poderia fazer item.prop1=5;para alterar a propriedade específica.

Ou você deseja obter uma lista de itens do banco de dados e alterar a propriedade prop1de cada item da lista retornada para um valor especificado? Nesse caso, você poderia fazer isso (eu estou fazendo isso no VB porque eu o conheço melhor):

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

( listconterá todos os itens retornados com suas alterações)

Solmead
fonte
Enquanto isso funcionará, acho que o OP estava perguntando como escrever a instrução LINQ sem precisar escrever um for eachloop.
Fujiiface
-3
User u = UserCollection.Single(u => u.Id == 1);
u.FirstName = "Bob"
kazem
fonte
1
Não há necessidade de duplicar as respostas existentes neste segmento . Se você tiver um comentário sobre essa resposta, poderá colocá-la lá.
KyleMit