2010-04-02 3 views
2

Compte tenu de la structure de base de données suivante alt text http://dl.dropbox.com/u/26791/tables.pngLINQ to SQL joindre quand il n'y a pas

Je suis en train d'écrire une requête LINQ qui renverra des images regroupées par tags, il est associé. Jusqu'ici j'ai ceci:

var images = from img in db.Images 
    join imgTags in db.ImageTags on img.idImage equals imgTags.idImage 
    join t in db.Tags on imgTags.idTag equals t.idTag 
    where img.OCRData.Contains(searchText.Text) 
    group img by new { t.TagName } into aGroup 
    select new 
    { 
     GroupName = aGroup.Key.TagName, 
     Items = from x in aGroup 
     select new ImageFragment() 
     { 
      ImageID = x.idImage, 
      ScanDate = x.ScanTime 
     } 
    }; 

Qui fonctionne très bien. Toutefois, je souhaite également renvoyer les images qui ne sont associées à aucune balise dans un groupe de "(Untagged)" ou quelque chose. Je ne peux pas comprendre comment je ferais ceci sans insérer une étiquette par défaut pour chaque image et cela ne semble pas être une très bonne solution.

Répondre

0

C'est un peu difficile, mais vous pouvez le faire dans une grande requête si vous avez la possibilité d'instancier de nouvelles instances ImageTag et Tag pour linq pour travailler avec. Essentiellement, lorsque vous effectuez une jointure externe, vous devez utiliser le mot clé into avec la méthode DefaultIfEmpty(...) pour gérer les «écarts de jointure externes» (par exemple, lorsque le côté droit de la clé jointe est null dans un élément externe gauche de SQL standard joindre).

 
var images = from img in db.Images 
    join imgTags in db.ImageTags on img.idImage equals imgTags.idImage 
    into outerImageRef 
    from outerIR in outerImageRef.DefaultIfEmpty(new ImageTag() { idImage = img.idImage, idTag = -1 }) 
    join t in db.Tags on imgTags.idTag equals t.idTag 
    into outerRefTags 
    from outerRT in outerRefTags.DefaultIfEmpty(new Tag(){ idTag=-1, TagName ="untagged"}) 
    group img by outerRT.TagName into aGroup 
    select new { 
     GroupName = aGroup.Key, 
     Items = from x in aGroup 
      select new ImageFragment() { 
       ImageID = x.idImage, 
       ScanDate = x.ScanTime 
      } 
    }; 

Espérons que les compiles ci-dessus puisque je n'ai pas votre environnement exact, je construit ma solution en utilisant mes propres types de données, puis converti à la description de votre question. Fondamentalement, les parties clés sont les lignes supplémentaires into et DefaultIfEmpty qui aident essentiellement à ajouter les «rangées» supplémentaires dans la table massivement jointe qui est en mémoire si vous y pensez dans le sens traditionnel de sql.

Cependant, il y a une solution plus lisible qui ne nécessite pas la mémoire instanciation des entités LINQ (vous devrez convertir celui-ci vous à votre environnement):

 
//this first query will return a collection of anonymous types with TagName and ImageId, 
// essentially a relation from joining your ImageTags x-ref table and Tags so that 
// each row is the tag and image id (as Robert Harvey mentioned in his comment to your Q) 
var tagNamesWithImageIds = from tag in Tags 
     join refer in ImageTags on tag.IdTag equals refer.IdTag 
     select new { 
      TagName = tag.Name, 
      ImageId = refer.IdImage 
     }; 
//Now we can get your solution by outer joining the images to the above relation 
// and filling in the "outer join gaps" with the anonymous type again of "untagged" 
// and then joining that with the Images table one last time to get your grouping and projection. 
var images = from img in Images 
    join t in tagNamesWithImageIds on img.IdImage equals t.ImageId 
    into outerJoin 
    from o in outerJoin.DefaultIfEmpty(new { TagName = "untagged", ImageId = img.IdImage }) 
    join img2 in Images on o.ImageId equals img2.IdImage 
    group img2 by o.TagName into aGroup 
    select new { 
     TagName = aGroup.Key, 
     Images = aGroup.Select(i => i.Data).ToList() //you'll definitely need to replace this with your code's logic. I just had a much simpler data type in my workspace. 
    }; 

espoir qui fait sens. Bien sûr, vous pouvez toujours définir votre application pour tout marquer par défaut w/"non étiqueté" ou faire des requêtes LINQ beaucoup plus simples pour créer une liste d'ID d'image qui ne sont pas présents dans votre table ImageTag, puis union ou quelque chose.

+0

J'ai réussi à comprendre sans voir cela, mais cela aurait été utile jamais moins. – Boarder2

1

Si vous souhaitez des enregistrements d'images lorsqu'il n'y a pas d'enregistrements de tag correspondants, vous devez effectuer un
outer join dans le tableau des marqueurs d'image.

+0

Hmm, je ne sais pas comment faire syntaxiquement que cette requête. – Boarder2

+1

Si vous devez simplifier la requête, joignez d'abord les tables tag et imagetag, puis joignez le résultat à la table des images. –

+0

Il s'agit d'ajouter un 'dans' sur l'une des jointures et un .DefaultIfEmpty(). – SteadyEddi

0

Voici ce que j'ai fini par faire. Je n'ai pas encore vérifié quel genre de SQL cela génère, je suppose que ce n'est probablement pas très joli. Je pense que je serais mieux de faire une couple de requêtes et de regrouper les choses moi-même, mais en tout cas cela fonctionne:

var images = from img in db.Images 
        join imgTags in db.ImageTags on img.idImage equals imgTags.idImage into g 
        from imgTags in g.DefaultIfEmpty() 
        join t in db.Tags on imgTags.idTag equals t.idTag into g1 
        from t in g1.DefaultIfEmpty() 
        where img.OCRData.Contains(searchText.Text) 
        group img by t == null ? "(No Tags)" : t.TagName into aGroup 
        select new 
        { 
         GroupName = aGroup.Key, 
         Items = from x in aGroup 
             select new ImageFragment() 
             { 
              ImageID = x.idImage, 
              ScanDate = x.ScanTime 
             } 
        };