LINQ to SQL присоединяется, когда нет результатов - PullRequest
2 голосов
/ 02 апреля 2010

Учитывая следующую структуру базы данных альтернативный текст http://dl.dropbox.com/u/26791/tables.png

Я пытаюсь написать запрос LINQ, который будет возвращать изображения, сгруппированные по тегам, с которыми он связан. Пока у меня есть это:

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

Который прекрасно работает. Тем не менее, я также хочу вернуть изображения, которые не имеют каких-либо тегов, связанных с ними, в группе «(без тегов)» или что-то еще. Я не могу понять, как бы это сделать, не вставляя тег по умолчанию для каждого изображения, и это, как правило, не очень хорошее решение.

Ответы [ 3 ]

1 голос
/ 02 апреля 2010

Если вам нужны записи изображений, когда нет соответствующих записей тегов, вам необходимо выполнить внешнее соединение
в таблице тегов изображений.

0 голосов
/ 03 апреля 2010

Вот что я в итоге сделал. На самом деле я еще не проверил, какой SQL это генерирует, полагаю, что это не совсем красиво. Я думаю, что было бы лучше сделать пару запросов и собрать их самостоятельно, но в любом случае это работает:

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
                                        }
                    };
0 голосов
/ 03 апреля 2010

Это немного сложно, но вы можете сделать это одним большим запросом, если у вас есть возможность создавать новые экземпляры ImageTag и Tag для работы с linq. По сути, когда вы выполняете внешнее соединение, вы должны использовать ключевое слово into с методом DefaultIfEmpty(...), чтобы справиться с «пробелами внешнего соединения» (например, когда правая сторона соединенного ключа равна нулю в типичное SQL левое внешнее соединение).

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

Надеюсь, что приведенные выше компилируются, поскольку у меня нет вашей точной среды, я построил свое решение, используя свои собственные типы данных, а затем преобразовал его в описание вашего вопроса. По сути, ключевыми частями являются дополнительные строки into и DefaultIfEmpty, которые по существу помогают добавить дополнительные "строки" в массивную таблицу, находящуюся в памяти, если вы думаете об этом в традиционном смысле sql.

Однако есть более удобочитаемое решение, которое не требует инстанцирования в памяти сущностей linq (вам придется преобразовать это самостоятельно в вашу среду):

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

Надеюсь, это имеет смысл. Конечно, вы всегда можете просто установить в своем приложении тег по умолчанию на все без тегов или выполнить несколько более простых запросов LINQ, чтобы создать список идентификаторов изображений, которых нет в вашей таблице ImageTag, а затем объединить или что-то еще.

...