Rejoindre / Où avec LINQ et Lambda

458

Je rencontre des problèmes avec une requête écrite en LINQ et Lambda. Jusqu'à présent, je reçois beaucoup d'erreurs, voici mon code:

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

Je suis nouveau dans l'utilisation de LINQ, donc je ne sais pas si cette requête est correcte.

David
la source
11
Qu'est-ce que vous essayez d'accomplir?
Germán Rodríguez
4
que voulez-vous que la requête fasse dans une phrase?
chasseur
6
Vos sélecteurs clés sont beaucoup trop compliqués. Si vous voulez sélectionner par id, juste x => x.ID est très bien.
Eric Lippert
1
Je voulais obtenir un article de la base de données et des métadonnées de cet article.
David

Réponses:

1057

Je trouve que si vous connaissez la syntaxe SQL, l'utilisation de la syntaxe de requête LINQ est beaucoup plus claire, plus naturelle et facilite la détection des erreurs:

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

Si vous êtes vraiment coincé à utiliser des lambdas, votre syntaxe est un peu différente. Voici la même requête, en utilisant les méthodes d'extension 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
la source
10
@Emanuele Greco, concernant votre modification, "L'égalité sur les champs ID est définie dans la condition JOIN; vous n'avez pas besoin d'utiliser la clause WHERE!": La clause WHERE ne teste pas l'égalité entre les champs ID, elle teste l'égalité entre l'ID du post et le paramètre id déclaré en dehors de la requête.
Daniel Schaffer
9
Un morceau génial lambdaet une citation facile à utiliser et à comprendre
Piotr Kula
1
exemple génial
jouet
1
Parfois, les explications de lambda sont écrites en lambda. Bien expliqué.
Pinch
80

Vous pouvez aller de deux façons avec cela. En utilisant LINQPad (inestimable si vous êtes nouveau sur LINQ) et une base de données factice, j'ai construit les requêtes suivantes:

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

ou

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

Dans ce cas particulier, je pense que la syntaxe LINQ est plus propre (je change entre les deux en fonction de ce qui est le plus facile à lire).

La chose que je voudrais souligner cependant, c'est que si vous avez des clés étrangères appropriées dans votre base de données (entre post et post_meta), vous n'avez probablement pas besoin d'une jointure explicite, sauf si vous essayez de charger un grand nombre d'enregistrements . Votre exemple semble indiquer que vous essayez de charger une seule publication et que ce sont des métadonnées. En supposant qu'il existe de nombreux enregistrements post_meta pour chaque publication, vous pouvez effectuer les opérations suivantes:

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

Si vous voulez éviter le problème n + 1, vous pouvez explicitement dire à LINQ to SQL de charger tous les éléments associés en une seule fois (bien que cela puisse être un sujet avancé lorsque vous êtes plus familier avec L2S). L'exemple ci-dessous dit "lorsque vous chargez une publication, chargez également tous ses enregistrements qui lui sont associés via la clé étrangère représentée par la propriété '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

Il est possible de faire de nombreux LoadWithappels sur un même ensemble de DataLoadOptionspour le même type, ou de nombreux types différents. Si vous faites cela cependant, vous voudrez peut-être simplement envisager la mise en cache.

Damian Powell
la source
1
LinqPad et CRM 2016 ?
Kiquenet
50

Daniel a une bonne explication des relations syntaxiques, mais j'ai rassemblé ce document pour mon équipe afin de le rendre un peu plus simple à comprendre. J'espère que cela aide quelqu'unentrez la description de l'image ici

Talspaugh27
la source
Cela ne fonctionnera pas lorsque vous traitez simplement une liste de valeurs comme nous sommes ici. Il n'y a pas de propriété id sur l'objet.
Talspaugh27
J'ai trouvé cela vraiment utile, mais j'ai eu une erreur qui m'a obligé à ajouter la colonne de jointure. En regardant également la réponse publiée par @Mark Byers, la colonne de jointure a le Post_IDchamp dans le deuxième alias meta => meta.Post_ID. Dans l'exemple de cette illustration, la g.idpartie de l'instruction select d'origine JOIN gStatus g on g.idn'est pas répliquée dans l'expression Lambda finale.
SausageFingers
3
Je n'essayais pas de poster cela comme une référence au linq réel requis pour répondre posté par l'OP, c'était plus une référence pour savoir comment déplacer SQL vers un format Linq, donc mes entrées étaient un peu différentes de la question d'origine. Si j'avais créé une classe pour les valeurs gStatus, j'aurais mis une propriété id dessus et alors oui, elle se serait jointe à g => g.id J'ai utilisé une liste de valeurs pour essayer de garder le code aussi simple que possible.
Talspaugh27
@ Talspaugh27 Alors pourquoi dans la requête SQL se joint-elle à gStatus sur g.id? Est-ce une erreur ou une intention?
Drammy
@Drammy dans une table sql, chaque colonne doit avoir un nom, donc comme il s'agissait d'une table à 1 colonne strictement pour contenir ces identifiants, je viens d'utiliser une colonne nommée id, la liste <int> n'a pas ce problème. Si je l'avais configuré comme tel, public class IdHolder{ int id } puis utilisé cet objet dans le gStatus, List<IdHolder> gStatus = new List<IdHolder>(); gStatus.add(new IdHolder(){id = 7}); gStatus.add(new IdHolder(){id = 8}); il aurait changé le Linq pour t =>t.value.TaskStatusId, g=>g.id que ce changement soit-il logique?
Talspaugh27
37

Vos sélecteurs de clés sont incorrects. Ils doivent prendre un objet du type de la table en question et retourner la clé à utiliser dans la jointure. Je pense que vous voulez dire ceci:

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

Vous pouvez appliquer la clause where par la suite, pas dans le cadre du sélecteur de clé.

Mark Byers
la source
9

Publier parce que quand j'ai commencé LINQ + EntityFramework, j'ai regardé ces exemples pendant une journée.

Si vous utilisez EntityFramework et que vous avez une propriété de navigation nommée Metadans la Postconfiguration de votre objet modèle, c'est très simple. Si vous utilisez une entité et n'avez pas cette propriété de navigation, qu'attendez-vous?

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

Si vous faites d'abord du code, vous devez configurer la propriété comme suit:

class Post {
  [Key]
  public int ID {get; set}
  public int MetaID { get; set; }
  public virtual Meta Meta {get; set;}
}
Andy V
la source
5

J'ai fait quelque chose comme ça;

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
la source
4

Cela pourrait être quelque chose comme

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
la source
1

1 est égal à 1 deux jointures de table différentes

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
la source
1

Cette requête linq devrait fonctionner pour vous. Il obtiendra tous les messages contenant des méta-messages.

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

Requête SQL équivalente

Select * FROM Posts P
INNER JOIN Post_Metas pm ON pm.postId=p.postId
Ahamed Ishak
la source
vous avez fermé les parenthèses où après le troisième param ... "aucune surcharge pour Join ne prend trois arguments"
LastT Tribunal
3
Ceci est identique à la réponse acceptée et 7 ans plus tard -1
reggaeguitar