У меня есть вариант использования, который содержит данные, хранящиеся в таблицах значений, как показано ниже, которые необходимо повернуть на основе 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; }
}
Пример данных «Значения»:
Представление DataGrid значений группы сотрудников:
Предлагаемая модель просмотра:
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 в такую модель представления и значение по умолчанию равное нулю, если в базе данных нет строк?
Попробуйте следующий запрос:
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
.
У меня были некоторые ошибки при попытке добиться этого, но, тем не менее, это также хорошее решение и прояснило, чего я пытался достичь.
@svyatoslav-danyliv Любая идея, почему я получаю ошибку компиляции с предложенным вами ответом. Он выдает «Неверный тип одного из выражений в предложении соединения». Не удалось определить тип при вызове GroupJoin.
Что-то не так с типами в анонимных классах. Будет проверено.
Не беспокойтесь, у меня все заработало, это было вызвано моей сущностью из базы данных 'context.EmployeeGroupValues'. Спасибо. @SvyatoslavDanyliv
Я нашел ошибку и исправил ответ.
Используя настраиваемый расширенный 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();
Вы не показываете дубликатов пола или цвета для определенного JobLevel - возможно ли наличие дубликатов на более низких уровнях?