Как агрегировать соединения?

У меня есть три связанные таблицы:

Employee(EmployeeId, EmployeeName)

Skill(SkillId, SkillName)

EmployeeSkill(EmployeSkillId, EmployeeId, SkillId)

EmployeSkillId - это личность.

Строки в базе данных следующие:

Таблица сотрудников:

EmployeeId | EmployeeNumber | EmployeeName
---------- | -------------- | ------------
        1  |         10015  |  John Doe

Таблица умений:

SkillId | SkillName
------- | ---------
     1  |  .NET
     2  |  SQL
     3  |  OOD
     4  |  Leadership

Таблица умений сотрудников:

EmployeeSkillId | EmployeeId | SkillId
--------------- | ---------- | -------
             1  |         1  |      1
             2  |         1  |      2
             3  |         1  |      3
             4  |         1  |      4

Если у меня есть сотрудник с тремя навыками, зарегистрированными в EmployeSkill, я хотел бы получить следующий результат:

John Doe, "Skill-1, Skill2, Skill-3"

То есть объединить название навыков для этого сотрудника в одну строку.

Я пробовал следующее, но это не работает.

var query = from emp in Employee.All()
            from es in emp.EmployeeSkills
            join sk in Skill.All() on es.SkillId equals sk.SkillId
            group sk by new {emp.EmployeeName} into g
            select new TestEntity
            {
                Name = g.Key.EmployeeName,
                Skills = g.Aggregate(new StringBuilder(),
                                     (sb, grp_row) => sb.Append(grp_row.SkillName))
                          .ToString()
            };

Сводный список названий навыков возвращается пустым. Как я могу это сделать?


person GR7    schedule 22.02.2011    source источник
comment
Я отформатировал вопрос для вас. Должен дать вам представление о том, как получить ваши будущие вопросы, как вы этого хотите.   -  person Jeff Mercado    schedule 22.02.2011
comment
спасибо, Джефф М, я обязательно научусь это делать :)   -  person GR7    schedule 22.02.2011


Ответы (1)


Похоже, вы могли бы выполнить соединение как часть выбора:

var query = from emp in Employee.All()
            select new TestEntity {
                Name = emp.EmployeeName,
                Skills = string.Join(", ", 
                       (from es in emp.EmployeeSkills
                        join sk in Skill.All() on es.SkillId equals sk.SkillId
                        select sk.SkillName)) };

Теперь это будет выполнять соединение отдельно для каждого, что не очень эффективно. Другой вариант — сначала построить сопоставление идентификатора навыка с названием навыка:

var skillMap = Skill.All().ToDictionary(sk => sk.SkillId,
                                        sk => sk.SkillName);

тогда основной запрос прост:

var query = from emp in Employee.All()
            select new TestEntity {
                Name = emp.EmployeeName,
                Skills = string.Join(", ",
                       emp.EmployeeSkills.Select(sk => skillMap[sk.SkillId]))};

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

var query = from emp in Employee.All()
            from es in emp.EmployeeSkills
            join sk in Skill.All() on es.SkillId equals sk.SkillId
            group sk.SkillName by emp into g
            select new TestEntity
            {
                Name = g.Key.EmployeeName,
                Skills = string.Join(", ", g)
            };

На данный момент это очень похоже на ваш исходный запрос, только с использованием string.Join вместо Aggregate, конечно. Если все эти три подхода вернутся с пустым списком навыков, то я подозреваю, что с вашими данными что-то не так. Для меня не очевидно, почему ваш первый запрос «успешен», но с пустым списком навыков.

РЕДАКТИРОВАТЬ: Хорошо, вот короткий (-иш), но полный пример его работы:

using System;
using System.Collections.Generic;
using System.Linq;

public class Employee
{
    public int EmployeeId { get; set; }
    public string EmployeeName { get; set; }

    public static List<Employee> All { get; set; }

    public IEnumerable<EmployeeSkill> EmployeeSkills
    {
        get 
        { 
            return EmployeeSkill.All
                           .Where(x => x.EmployeeId == EmployeeId);
        }
    }
}

public class Skill
{
    public string SkillName { get; set; }
    public int SkillId { get; set; }

    public static List<Skill> All { get; set; }
}

public class EmployeeSkill
{
    public int SkillId { get; set; }
    public int EmployeeId { get; set; }

    public static List<EmployeeSkill> All { get; set; }
}

class Test
{
    static void Main()
    {
        Skill.All = new List<Skill>
        {
            new Skill { SkillName = "C#", SkillId = 1},
            new Skill { SkillName = "Java", SkillId = 2},
            new Skill { SkillName = "C++", SkillId = 3},
        };

        Employee.All = new List<Employee>
        {
            new Employee { EmployeeName = "Fred", EmployeeId = 1 },
            new Employee { EmployeeName = "Ginger", EmployeeId = 2 },
        };

        EmployeeSkill.All = new List<EmployeeSkill>
        {
            new EmployeeSkill { SkillId = 1, EmployeeId = 1 },
            new EmployeeSkill { SkillId = 2, EmployeeId = 1 },
            new EmployeeSkill { SkillId = 2, EmployeeId = 2 },
            new EmployeeSkill { SkillId = 3, EmployeeId = 2 },
        };

        var query = from emp in Employee.All
            from es in emp.EmployeeSkills
            join sk in Skill.All on es.SkillId equals sk.SkillId
            group sk.SkillName by emp.EmployeeName into g
            select new
            {
                Name = g.Key,
                Skills = string.Join(", ", g)
            };

        foreach (var result in query)
        {
            Console.WriteLine(result);
        }
    }
}

Полученные результаты:

{ Name = Fred, Skills = C#, Java }
{ Name = Ginger, Skills = Java, C++ }
person Jon Skeet    schedule 22.02.2011
comment
привет Джон. спасибо, что так быстро ответили. Странно, но оба варианта возвращают свойство Skills пустым. И я уверен, что это не данные, так как будет работать простое использование соединения и возврат одной строки для каждого сотрудника/навыка. Любые идеи, что может привести к тому, что агрегация не работает? - person GR7; 22.02.2011
comment
@silverCORE: Это звучит очень странно... если вы добавите изменение, чтобы использовать что-то вроде Skills = g.Count().ToString(); Вы получаете нужное количество навыков? Если все подходы дают один и тот же странный результат, это похоже на проблему с данными. Если бы вы могли привести короткий, но полный пример, это действительно помогло бы. Попробую построить сам. - person Jon Skeet; 22.02.2011
comment
позвольте мне вставить данные, которые у меня есть, Джон, а также попробовать g.Count. с вашим последним примером, часть, которая говорит group sk.SkillName by emp.EmployeeName, по-видимому, делает попытку использовать g.Key.EmployeeName неосуществимой. По крайней мере, мой intellisense не улавливает это. - person GR7; 22.02.2011
comment
@silverCORE: Да, это должна была быть либо группировка по emp, либо группировка по emp.EmployeeName, а затем просто использование g.Key. Я отредактировал запрос, а также включил короткий, но полный пример, который действительно работает. Интересно, у вас просто не получается правильно отображать результаты? Я думаю, что запрос в порядке. - person Jon Skeet; 22.02.2011
comment
Джон, ты мужчина. Спасибо, что нашли время, чтобы помочь мне. Действительно ценится. - person GR7; 22.02.2011
comment
@Jon, обновление: обнаружил, что g.Count.Tostring() с использованием любого из ваших вариантов действительно работает, однако при использовании третьего варианта с string.Join(,, g) он возвращает значение null. но проблема, похоже, связана с тем, что я использую Subsonic, а не LINQ To Objects как таковой... вместо использования Employee.All() и emp.EmployeSkills и Skills.All(), я принес все эти записи как List‹› перед запросом и использовал эти списки вместо запроса дозвуковой активной записи, и это работает. Любая идея, почему тот же запрос не будет работать при непосредственном запросе дозвукового сигнала? - person GR7; 22.02.2011
comment
@silverCORE: Ну, если вы не используете LINQ to Objects, это совершенно другое дело. Это может быть ошибка Subsonic или какой-то неподдерживаемый сценарий. Все, что выполняет преобразование запросов в SQL, будет больше похоже на LINQ to SQL, чем на LINQ to Objects. - person Jon Skeet; 22.02.2011
comment
@Джон: попался. Я все еще осваиваю LINQ. Кстати, завтра заказываю углубленное изучение C#. Это было в моем списке дел в течение нескольких месяцев, и теперь это кажется более справедливым :) Спасибо! - person GR7; 22.02.2011
comment
@silverCORE: Круто — надеюсь, поможет! Кстати, вы также можете ознакомиться с моей серией блогов Edulinq: msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx - person Jon Skeet; 22.02.2011