C# Данные сводной таблицы в ViewModel с использованием Linq

У меня есть вариант использования, который содержит данные, хранящиеся в таблицах значений, как показано ниже, которые необходимо повернуть на основе JobLevel и отобразить в DataGridControl, однако для того, чтобы это работало, мне сначала нужно преобразовать таблицу данных, которую я получаю из базу данных в объект ViewModel, который может отображать результат в виде строки, чтобы пользователи могли изменять столбец значений в DataGridControl. В таблице хранится информация о сотрудниках на разных уровнях, количестве сотрудников на каждом уровне, их цвете кожи и поле. Если значение не имеет данных, по умолчанию оно должно быть равно нулю.

Перечисление уровня работы:

public enum JobLevel
{
    Top,
    Mid,
    Low
}

public enum Color
{
    Red,
    Blue,
    Green
}

public enum Gender
{
    Female,
    Male,
    Other
}

Класс сущности значений:

public class EmployeeGroupValue
{
    [Key, Column(Order = 0)]
    [Required]
    public JobLevel JobLevel { get;set; }
    [Key, Column(Order = 1)]
    [Required]
    public Gender Gender { get;set; }
    [Key, Column(Order = 2)]
    [Required]
    public Color Color { get;set; }
    [Required]
    public int Value { get;set; }
}

Пример данных «Значения»:

Идентификатор Уровень работы Пол Цвет Ценить 1 Вершина Мужской Красный 10 2 Вершина Другой Красный 5 3 Вершина Женский Синий 20 4 Середина Другой Синий 5 5 Середина Женский Зеленый 50 6 Низкий Мужской Зеленый 5 7 Низкий Другой Красный 7 8 Низкий Женский Зеленый 12

Представление DataGrid значений группы сотрудников:

Уровень работы Женский Мужской Другой
хххххххххх р Б г р Б г р Б г Вершина 0 20 0 10 0 0 5 0 0 Середина 0 0 50 0 0 0 0 5 0 Низкий 0 0 12 0 0 5 7 0 0

Предлагаемая модель просмотра:

public class EmployeeGroupValueViewModel
{
     public string JobLevel { get;set; }
     public int FemaleRed { get;set; }
     public int FemaleBlue { get;set; }
     public int FemaleGreen { get;set; }
     public int MaleRed { get;set; }
     public int MaleBlue { get;set; }
     public int MaleGreen { get;set; }
     public int OtherRed { get;set; }
     public int OtherBlue { get;set; }
     public int OtherGreen { get;set; }
}

Как я могу преобразовать свои данные с помощью linq в такую ​​модель представления и значение по умолчанию равное нулю, если в базе данных нет строк?

Вы не показываете дубликатов пола или цвета для определенного JobLevel - возможно ли наличие дубликатов на более низких уровнях?

NetMage 09.02.2023 00:30
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте следующий запрос:

var databaseData = context.EmployeeGroupValues
    .GroupBy(e => new { e.JobLevel, e.Gender, e.Color })
    .Select(g => new 
    {
        g.Key.JobLevel,
        g.Key.Gender,
        g.Key.Color,
        Value = g.Sum(x => x.Value)
    })
    .ToList();

var enrichedData = 
    from jobLevel in new [] { JobLevel.Top, JobLevel.Mid, JobLevel.Low }
    from gender in new [] { Gender.Female, Gender.Male, Gender.Other }
    from color in new [] { Color.Red, Color.Blue, Color.Green }
    join d in databaseData on 
        new { JobLevel = jobLevel, Gender = gender, Color = color}
        equals  new { d.JobLevel, d.Gender, d.Color } into gj
    from d in gj.DefaultIfEmpty(new { JobLevel = jobLevel, Gender = gender, Color = color, Value = 0 })
    group d by jobLevel into g
    select new EmployeeGroupValueViewModel
    {
        JobLevel = g.Key.ToString(),

        FemaleRed   = g.Sum(x => x.Gender == Gender.Female && x.Color == Color.Red   ? x.Value : 0),
        FemaleBlue  = g.Sum(x => x.Gender == Gender.Female && x.Color == Color.Blue  ? x.Value : 0),
        FemaleGreen = g.Sum(x => x.Gender == Gender.Female && x.Color == Color.Green ? x.Value : 0),
        MaleRed     = g.Sum(x => x.Gender == Gender.Male   && x.Color == Color.Red   ? x.Value : 0),
        MaleBlue    = g.Sum(x => x.Gender == Gender.Male   && x.Color == Color.Blue  ? x.Value : 0),
        MaleGreen   = g.Sum(x => x.Gender == Gender.Male   && x.Color == Color.Green ? x.Value : 0),
        OtherRed    = g.Sum(x => x.Gender == Gender.Other  && x.Color == Color.Red   ? x.Value : 0),
        OtherBlue   = g.Sum(x => x.Gender == Gender.Other  && x.Color == Color.Blue  ? x.Value : 0),
        OtherGreen  = g.Sum(x => x.Gender == Gender.Other  && x.Color == Color.Green ? x.Value : 0),
    };

var result = enrichedData.ToList();

Вы можете поиграть с этим в dotnetfiddle

опечатка: Genger => Gender.

NetMage 09.02.2023 00:02

У меня были некоторые ошибки при попытке добиться этого, но, тем не менее, это также хорошее решение и прояснило, чего я пытался достичь.

Donald N. Mafa 14.02.2023 07:32

@svyatoslav-danyliv Любая идея, почему я получаю ошибку компиляции с предложенным вами ответом. Он выдает «Неверный тип одного из выражений в предложении соединения». Не удалось определить тип при вызове GroupJoin.

Donald N. Mafa 15.02.2023 22:49

Что-то не так с типами в анонимных классах. Будет проверено.

Svyatoslav Danyliv 16.02.2023 04:59

Не беспокойтесь, у меня все заработало, это было вызвано моей сущностью из базы данных 'context.EmployeeGroupValues'. Спасибо. @SvyatoslavDanyliv

Donald N. Mafa 16.02.2023 09:01

Я нашел ошибку и исправил ответ.

Svyatoslav Danyliv 16.02.2023 10:10
Ответ принят как подходящий

Используя настраиваемый расширенный Dictionary, который возвращает default(TValue) для отсутствующих записей, вы можете повторно сопоставить исходные данные с деревом словарей и избежать повторного сканирования данных для каждого значения свойства.

Вот расширенный Dictionary и некоторые методы расширения, облегчающие его использование с LINQ:

#region Enhanced Dictionaries
public static class DictExt {
    // DefaultDictionary that returns default(TValue) for missing entries
    public static DefaultDictionary<TKey, TValue> ToDefaultDictionary<T, TKey, TValue>(this IEnumerable<T> items, Func<T, TKey> keyFn, Func<T, TValue> valFn) {
        var nd = new DefaultDictionary<TKey, TValue>();
        foreach (var item in items)
            nd.Add(keyFn(item), valFn(item));
        return nd;
    }
    public static DefaultDictionary<TKey, TValue> ToDefaultDictionary<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> keyFn) {
        var nd = new DefaultDictionary<TKey, TValue>();
        foreach (var item in items)
            nd.Add(keyFn(item), item);
        return nd;
    }
    public static DefaultDictionary<TKey, TValue> ToDefaultDictionary<TKey, TValue>(this IDictionary<TKey, TValue> srcd)
        => new DefaultDictionary<TKey, TValue>(srcd);
 }

//***
// Enhanced Dictionary that returns default(TValue) for missing entries
//***
public class DefaultDictionary<TKey, TValue> : Dictionary<TKey, TValue> {
    public DefaultDictionary(IDictionary<TKey, TValue> d) : base() {
        foreach (var kvp in d)
            Add(kvp.Key, kvp.Value);
    }
    public DefaultDictionary() : base() { }

    public new TValue this[TKey key] {
        get {
            TryGetValue(key, out var val);
            return val;
        }
        set => base[key] = value;
    }
}

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

var jobLevelDict = 
    values
        .GroupBy(v => v.JobLevel)
        .ToDefaultDictionary(
            vg => vg.Key,
            vjlg => vjlg
                    .GroupBy(v => v.Gender)
                    .ToDefaultDictionary(
                        vgg => vgg.Key,
                        vgg => vgg
                                .GroupBy(v => v.Color)
                                .ToDefaultDictionary(
                                    vcg => vcg.Key,
                                    vcg => vcg.Sum(v => v.Value))));

Имея дерево словарей, можно построить полную List<EmployeeGroupValueViewModel> для сетки:

var ans = Enum.GetValues<JobLevel>()
             .Select(jl => new EmployeeGroupValueViewModel {
                     JobLevel = jl.ToString(),
                     FemaleRed = jobLevelDict?[jl]?[Gender.Female]?[Color.Red] ?? 0,
                     FemaleBlue = jobLevelDict?[jl]?[Gender.Female]?[Color.Blue] ?? 0,
                     FemaleGreen = jobLevelDict?[jl]?[Gender.Female]?[Color.Green] ?? 0,
                     MaleRed = jobLevelDict?[jl]?[Gender.Male]?[Color.Red] ?? 0,
                     MaleBlue = jobLevelDict?[jl]?[Gender.Male]?[Color.Blue] ?? 0,
                     MaleGreen = jobLevelDict?[jl]?[Gender.Male]?[Color.Green] ?? 0,
                     OtherRed = jobLevelDict?[jl]?[Gender.Other]?[Color.Red] ?? 0,
                     OtherBlue = jobLevelDict?[jl]?[Gender.Other]?[Color.Blue] ?? 0,
                     OtherGreen = jobLevelDict?[jl]?[Gender.Other]?[Color.Green] ?? 0
                 })
                 .ToList();

Другие вопросы по теме