Dołącz / Gdzie z LINQ i Lambda

457

Mam problem z zapytaniem napisanym w LINQ i Lambda. Do tej pory otrzymuję wiele błędów, oto mój kod:

int id = 1;
var query = database.Posts.Join(database.Post_Metas,
                                post => database.Posts.Where(x => x.ID == id),
                                meta => database.Post_Metas.Where(x => x.Post_ID == id),
                                (post, meta) => new { Post = post, Meta = meta });

Jestem nowy w używaniu LINQ, więc nie jestem pewien, czy to zapytanie jest poprawne.

David
źródło
11
co próbujesz osiągnąć
Germán Rodríguez
4
co chcesz zrobić zapytanie w zdaniu?
myśliwy
6
Twój klucz selektory są sposób zbyt skomplikowane. Jeśli chcesz wybrać według id, wystarczy x => x.ID jest w porządku.
Eric Lippert
1
Chciałem uzyskać post z bazy danych i metadane dla tego postu.
David

Odpowiedzi:

1053

Uważam, że jeśli znasz składnię SQL, użycie składni zapytania LINQ jest znacznie wyraźniejsze, bardziej naturalne i ułatwia wykrywanie błędów:

var id = 1;
var query =
   from post in database.Posts
   join meta in database.Post_Metas on post.ID equals meta.Post_ID
   where post.ID == id
   select new { Post = post, Meta = meta };

Jeśli jednak naprawdę utknąłeś przy użyciu lambdas, twoja składnia jest dość odległa. Oto to samo zapytanie, przy użyciu metod rozszerzenia LINQ:

var id = 1;
var query = database.Posts    // your starting point - table in the "from" statement
   .Join(database.Post_Metas, // the source table of the inner join
      post => post.ID,        // Select the primary key (the first part of the "on" clause in an sql "join" statement)
      meta => meta.Post_ID,   // Select the foreign key (the second part of the "on" clause)
      (post, meta) => new { Post = post, Meta = meta }) // selection
   .Where(postAndMeta => postAndMeta.Post.ID == id);    // where statement
Daniel Schaffer
źródło
10
@Emanuele Greco, odnośnie twojej edycji: „Równość w polach identyfikatora jest ustawiona w stanie JOIN; nie musisz używać klauzuli WHERE!”: Klauzula WHERE nie testuje równości między polami ID, lecz testuje równość między identyfikatorem postu kolumna i parametr id zadeklarowany poza zapytaniem.
Daniel Schaffer
9
Niesamowity kawałek lambdacytatu jest łatwy w użyciu i zrozumiały
Piotr Kula,
1
niesamowity przykład
zabawka
1
Czasami wyjaśnienia lambda są zapisane w lambda. Dobrze wyjaśnione.
Pinch
80

Można to zrobić na dwa sposoby. Korzystając z LINQPad (nieoceniony, jeśli dopiero zaczynasz korzystać z LINQ) i sztucznej bazy danych, stworzyłem następujące zapytania:

Posts.Join(
    Post_metas,
    post => post.Post_id,
    meta => meta.Post_id,
    (post, meta) => new { Post = post, Meta = meta }
)

lub

from p in Posts
join pm in Post_metas on p.Post_id equals pm.Post_id
select new { Post = p, Meta = pm }

W tym konkretnym przypadku myślę, że składnia LINQ jest czystsza (zmieniam się między nimi w zależności od tego, który jest najłatwiejszy do odczytania).

Chciałbym jednak zauważyć, że jeśli masz odpowiednie klucze obce w bazie danych (między post a post_meta), prawdopodobnie nie potrzebujesz jawnego łączenia, chyba że próbujesz załadować dużą liczbę rekordów . Twój przykład wydaje się wskazywać, że próbujesz załadować pojedynczy post i jego metadane. Zakładając, że dla każdego postu istnieje wiele rekordów post_meta, możesz wykonać następujące czynności:

var post = Posts.Single(p => p.ID == 1);
var metas = post.Post_metas.ToList();

Jeśli chcesz uniknąć problemu n + 1, możesz jawnie powiedzieć LINQ do SQL, aby ładował wszystkie powiązane elementy za jednym razem (chociaż może to być zaawansowany temat, gdy lepiej znasz L2S). Poniższy przykład mówi „gdy ładujesz Post, załaduj również wszystkie powiązane z nim rekordy za pomocą klucza obcego reprezentowanego przez właściwość„ Post_metas ”:

var dataLoadOptions = new DataLoadOptions();
dataLoadOptions.LoadWith<Post>(p => p.Post_metas);

var dataContext = new MyDataContext();
dataContext.LoadOptions = dataLoadOptions;

var post = Posts.Single(p => p.ID == 1); // Post_metas loaded automagically

Możliwe jest wykonywanie wielu LoadWithpołączeń na jednym zestawie DataLoadOptionsdla tego samego typu lub wielu różnych typów. Jeśli robisz to dużo, możesz po prostu rozważyć buforowanie.

Damian Powell
źródło
1
LinqPad i CRM 2016 ?
Kiquenet,
49

Daniel ma dobre wyjaśnienie związków między składniami, ale złożyłem ten dokument dla mojego zespołu, aby ułatwić im zrozumienie. Mam nadzieję, że to komuś pomożewprowadź opis zdjęcia tutaj

Talspaugh27
źródło
To nie zadziała, gdy masz do czynienia z listą wartości takich jak my tutaj. Obiekt nie ma właściwości id.
Talspaugh27
Uznałem to za bardzo przydatne, ale dostałem błąd, który wymagał ode mnie dodania kolumny łączącej. Patrząc również na odpowiedź opublikowaną przez @Mark Byers, kolumna łącząca ma Post_IDpole w drugim aliasie meta => meta.Post_ID. W przykładzie na tej ilustracji g.idczęść oryginalnej instrukcji select JOIN gStatus g on g.idnie jest replikowana w końcowym wyrażeniu Lambda.
SausageFingers
3
Nie próbowałem opublikować tego jako odniesienia do faktycznego linq wymaganego do odpowiedzi wysłanego przez OP, było to raczej odniesienie do tego, jak przenieść SQL do formatu Linq, więc moje dane wejściowe były nieco inne niż pierwotne pytanie. Gdybym utworzył klasę dla wartości gStatus, umieściłbym na niej właściwość id, a następnie tak, połączyłby się za pomocą g => g.id. Użyłem listy wartości, aby spróbować maksymalnie uprościć kod.
Talspaugh27
@ Talspaugh27 Więc dlaczego w zapytaniu SQL łączy się z gStatus na g.id? Czy to pomyłka czy celowe?
Drammy,
@Drammy w tabeli sql każda kolumna musi mieć nazwę, więc ponieważ była to 1-kolumnowa tabela przeznaczona wyłącznie do przechowywania tych identyfikatorów, właśnie użyłem kolumny o nazwie id, lista <int> nie ma tego problemu. Gdybym ustawił go jako taki, public class IdHolder{ int id } a następnie użył tego obiektu w gStatus, List<IdHolder> gStatus = new List<IdHolder>(); gStatus.add(new IdHolder(){id = 7}); gStatus.add(new IdHolder(){id = 8}); wówczas zmieniłby Linqa na taki, t =>t.value.TaskStatusId, g=>g.id czy ta zmiana ma sens?
Talspaugh27
37

Twoje selektory kluczy są nieprawidłowe. Powinny wziąć obiekt typu tabeli, o której mowa, i zwrócić klucz do użycia w złączeniu. Myślę, że masz na myśli to:

var query = database.Posts.Join(database.Post_Metas,
                                post => post.ID,
                                meta => meta.Post_ID,
                                (post, meta) => new { Post = post, Meta = meta });

Możesz zastosować klauzulę where później, a nie jako część selektora kluczy.

Mark Byers
źródło
9

Publikowanie, ponieważ kiedy uruchomiłem LINQ + EntityFramework, patrzyłem na te przykłady przez jeden dzień.

Jeśli używasz EntityFramework i masz skonfigurowaną właściwość nawigacji nazwaną Metaw Postustawionym obiekcie modelu, jest to łatwe. Jeśli korzystasz z encji i nie masz tej właściwości nawigacji, na co czekasz?

database
  .Posts
  .Where(post => post.ID == id)
  .Select(post => new { post, post.Meta });

Jeśli najpierw tworzysz kod, możesz skonfigurować właściwość w następujący sposób:

class Post {
  [Key]
  public int ID {get; set}
  public int MetaID { get; set; }
  public virtual Meta Meta {get; set;}
}
Andy V.
źródło
5

Zrobiłem coś takiego;

var certificationClass = _db.INDIVIDUALLICENSEs
    .Join(_db.INDLICENSECLAsses,
        IL => IL.LICENSE_CLASS,
        ILC => ILC.NAME,
        (IL, ILC) => new { INDIVIDUALLICENSE = IL, INDLICENSECLAsse = ILC })
    .Where(o => 
        o.INDIVIDUALLICENSE.GLOBALENTITYID == "ABC" &&
        o.INDIVIDUALLICENSE.LICENSE_TYPE == "ABC")
    .Select(t => new
        {
            value = t.PSP_INDLICENSECLAsse.ID,
            name = t.PSP_INDIVIDUALLICENSE.LICENSE_CLASS,                
        })
    .OrderBy(x => x.name);
Mahib
źródło
4

To może być coś takiego

var myvar = from a in context.MyEntity
            join b in context.MyEntity2 on a.key equals b.key
            select new { prop1 = a.prop1, prop2= b.prop1};
pepitomb
źródło
1

1 oznacza 1 dwa różne łączenie stołu

var query = from post in database.Posts
            join meta in database.Post_Metas on 1 equals 1
            where post.ID == id
            select new { Post = post, Meta = meta };
Mtngunay
źródło
1

To zapytanie linq powinno działać dla Ciebie. Otrzyma wszystkie posty, które mają meta postu.

var query = database.Posts.Join(database.Post_Metas,
                                post => post.postId, // Primary Key
                                meta => meat.postId, // Foreign Key
                                (post, meta) => new { Post = post, Meta = meta });

Równoważne zapytanie SQL

Select * FROM Posts P
INNER JOIN Post_Metas pm ON pm.postId=p.postId
Ahamed Ishak
źródło
zamknąłeś nawiasy, w których po trzecim parametrze ... „żadne przeciążenie dla Join nie bierze trzech argumentów”
LastTribunal 23.08.18
3
Jest to identyczne z przyjętą odpowiedzią i 7 lat później -1
reggaeguitar