LINQ: Wybierz obiekt i zmień niektóre właściwości bez tworzenia nowego obiektu

218

Chcę zmienić niektóre właściwości obiektu wynikowego zapytania LINQ bez tworzenia nowego obiektu i ręcznego ustawiania każdej właściwości. czy to możliwe?

Przykład:

var list = from something in someList
           select x // but change one property
Rob Volk
źródło
7
Napisałem metodę rozszerzenia opartą na odpowiedzi JaredPara poniżej, która pozwala to osiągnąć za pomocą składni deklaratywnej, tak jak w moim przykładzie. Sprawdź to: blog.robvolk.com/2009/05/…
Rob Volk
3
@RobVolk, link wydaje się martwy - masz nowy? Oto archiwum - LINQ: Wybierz obiekt, ale zmień niektóre właściwości bez tworzenia nowego obiektu
KyleMit
blog.robvolk.com/2009/05/… nie znaleziono Błąd DNS
Kiquenet
1
przepraszam za to! prawidłowy adres: robvolk.com/…
Rob Volk,
1
Zobacz także: stackoverflow.com/questions/47836019/…
Revious

Odpowiedzi:

380

Nie jestem pewien, jaka jest składnia zapytania. Ale oto przykład rozszerzonego wyrażenia LINQ.

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

To robi to użyć anonimowej metody vs i wyrażenia. Pozwala to na użycie kilku instrukcji w jednej lambda. Możesz więc połączyć dwie operacje ustawiania właściwości i zwracania obiektu do tej nieco zwięzłej metody.

JaredPar
źródło
4
@Rob, Nie łatwo. Składnia umożliwiająca to działanie jest ... w najlepszym razie nieczytelna. var query = z listy w liście select ((Func <Foo>) (() => {it.x = 42; return it;})) ();
JaredPar
35
Otrzymuję błąd „Nie można przekonwertować wyrażenia lambda z treścią instrukcji na drzewo wyrażeń”. To nie jest LINQ na SQL, jakaś rada?
surya
2
@spiderdevil +1 dla Ciebie - czy nie nienawidzisz różnic? To nie pierwszy raz, kiedy napotkałem sytuację, w której kod działa tylko dla Linq na Object, a nie Linq na SQL / EF. To może być rozwiązanie tutaj , ale nie próbowałem go jeszcze użyć, ponieważ nie mam czasu.
dyslexicanaboko
11
@surya To jest dokładnie wiadomość, którą otrzymałem, gdy próbowałem z Linq na SQL. Dodanie ToList()przed tym wydaje się załatwić sprawę. Nie jestem pewien, dlaczego to dostajesz. Jeśli jest to Entity Framework, to też z tym nie działa.
Raziel
5
@surya, kiedy wywołujesz ToList (), faktycznie przywracasz dane do pamięci. Więc to właściwie nie jest już Linq na SQL.
Oak
60

Jeśli chcesz po prostu zaktualizować właściwość we wszystkich elementach, to

someList.All(x => { x.SomeProp = "foo"; return true; })
Jon Spokes
źródło
3
W EF (Entity Framework): aby zastąpić właściwość na wszystkich obiektach IEnumerable, zaakceptowana odpowiedź działała dla mnie. Działający kod dla mnie: var myList = _db.MyObjects.Where (o => o.MyProp == "bar"). AsEnumerable (). Wybierz (x => {x.SomeProp = "foo"; return x;}) ;
firepol
32

Wolę tą. Można go łączyć z innymi poleceniami linq.

from item in list
let xyz = item.PropertyToChange = calcValue()
select item
Jan Zahradník
źródło
24

Nie powinna istnieć żadna magia LINQ powstrzymująca cię przed zrobieniem tego. Nie używaj jednak projekcji, która zwróci anonimowy typ.

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

To zmodyfikuje prawdziwy obiekt, a także:

foreach (User u in UserCollection.Where(u => u.Id > 10)
{
    u.Property = SomeValue;
}
Joshua Belden
źródło
Podoba mi się, moje pytanie jest w pierwszym przykładzie, co jeśli jakieś u> 10 nie znajduje się na liście? Dodałem czek zerowy i wydaje się, że działa. Również LHS nazwałem u do v. +1. Bardzo zwięzłe.
desaivv
11

Nie jest to możliwe w przypadku standardowych operatorów zapytań - jest to Zapytanie zintegrowane z językiem, a nie Aktualizacja zintegrowana z językiem. Ale możesz ukryć swoją aktualizację w metodach rozszerzenia.

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

Teraz możesz go używać w następujący sposób.

cars.Where(car => car.Color == Color.Blue).ChangeColorTo(Color.Red);
Daniel Brückner
źródło
1
To nie tak, że chcesz zaktualizować kolekcję podstawową. Chcemy zaktualizować właściwość kolekcji wyników.
Michael Brennt,
4
Dobrze LINQ zastępuje SQL, który stoi na Structured Query Language, ale wciąż naraża zdolność umiejętność UPDATE, DELETEi INSERT. Nie argumentowałbym więc, że semantyka jest tym, co zapobiega tej funkcji.
KyleMit,
5

Jeśli chcesz zaktualizować elementy za pomocą Whereklauzuli, użycie .Where (...) obetnie wyniki, jeśli:

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

Możesz dokonywać aktualizacji określonych pozycji na liście w następujący sposób:

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

Zawsze zwracaj przedmiot, nawet jeśli nie dokonasz żadnych zmian. W ten sposób będzie on przechowywany na liście.

Pierre
źródło
1

Często spotykamy się z tym, że chcemy dołączyć wartość indeksu oraz pierwszy i ostatni wskaźnik do listy bez tworzenia nowego obiektu. To pozwala poznać pozycję elementu na liście, wyliczenie itp. Bez konieczności modyfikowania istniejącej klasy, a następnie wiedzieć, czy jesteś na pierwszym elemencie na liście, czy na ostatnim.

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

Możesz po prostu rozszerzyć dowolną klasę, używając:

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

public class Item : IteratorExtender {}
Informatyka
źródło
0

Ponieważ nie znalazłem tutaj odpowiedzi, którą uważam za najlepsze rozwiązanie, oto moja droga:

Używanie „Wybierz” do modyfikowania danych jest możliwe, ale z pewną sztuczką. W każdym razie „Select” nie jest do tego stworzony. Po prostu wykonuje modyfikację, gdy jest używana z „ToList”, ponieważ Linq nie wykonuje się przed potrzebą danych. W każdym razie najlepszym rozwiązaniem jest użycie „foreach”. W poniższym kodzie widać:

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

Przed „foreach” możesz również dokonać wyboru linq ...

Stefan R.
źródło
0
var item = (from something in someList
       select x).firstordefault();

Otrzymałbym item, a następnie możesz zrobić, item.prop1=5;aby zmienić konkretną właściwość.

A może chcesz uzyskać listę elementów z bazy danych i zmienić tę właściwość prop1dla każdego elementu na tej zwróconej liście na określoną wartość? Jeśli tak, możesz to zrobić (robię to w VB, ponieważ wiem to lepiej):

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

( listbędzie zawierać wszystkie elementy zwrócone z Twoimi zmianami)

Solmead
źródło
Chociaż to zadziała, myślę, że OP pytał, jak napisać instrukcję LINQ bez konieczności zapisywania for eachpętli.
fujiiface
-3
User u = UserCollection.Single(u => u.Id == 1);
u.FirstName = "Bob"
kazem
źródło
1
Nie trzeba powielać istniejących odpowiedzi w tym wątku . Jeśli masz komentarz na temat tej odpowiedzi, możesz ją tam umieścić.
KyleMit