Отсортировать отделы и должности
Предположим, есть такая БД:
Как можно создать коллекцию Dictionary, где в роли key будет отдел, а в роли value — должность?
Вот скриншот того, чего пытаюсь добиться:

То есть, нужно как-то отсортировать.
Что я пытался сделать:
public class MultiDictionary
{
public static Dictionary<string, List<string>> MyDict = new Dictionary<string, List<string>>();
public static void AddEx(string department, string post)
{
var listPost = new List<string> { post };
if (MyDict.ContainsKey(department))
{
MyDict[department].Add(post);
}
else
{
MyDict.Add(department, listPost);
}
}
}
MultiDictionary.AddEx("Руководители", "Зам....");
И это работает для меня, но не в случае с загрузкой из БД, т.к отдел и должность это две разные модели.
Вот такой код:
foreach(var depart in Department.DisplayName())
{
foreach(var post in Post.DisplayName())
{
MultiDictionary.AddEx(depart, post);
}
}
но из-за того, что у меня цикл в цикле, получается каша. А как по другому выполнить AddEx?
В общем, подскажите, пожалуйста, как грамотно это можно реализовать? А то меня что-то не в ту степь понесло.
UPD:
Модель:
[Table("employees")]
public class Employees
{
[Key]
public int Id { get; set; }
[DisplayName("Отдел")]
public string Department { get; set; }
[DisplayName("Должность")]
public string Post { get; set; }
public Employees() { }
public Employees(string department, string post)
{
Department = department;
Post = post;
}
public static void AddEmployees(Employees employees)
{
Singlet.db.Employees.Add(employees);
Singlet.db.SaveChanges();
}
}
Контекст:
public class ApplicationContext : DbContext
{
public ApplicationContext() : base("DBConnection") { }
public DbSet<Employees> Employees { get; set; }
}
Заполнение данными:
var employees = new Employees()
{
Department = "Руководители",
Post = "Директор"
};
Employees.AddEmployees(employees);
Ответы (2 шт):
Первое, что хотелось-бы отметить - используйте для каждой записи какое-то уникальное значение. Как я писал выше в комментариях, можно использовать как простой тип int, так и Guid. Представьте, что это число - ссылка на объект. Таким образом, например, EmployeeData (таблица в БД) будет выглядеть следующим образом:
[Table("EmployeeData")]
internal class EmployeeData
{
[Key]
public Guid Id { get; set; }
[DisplayName("Имя")]
public string Name { get; set; }
[DisplayName("Отдел")]
public Guid DepartmentId { get; set; }
[DisplayName("Должность")]
public Guid PositionId { get; set; }
public EmployeeData(Guid id, string name, Guid deparmentId, Guid positiionId)
{
Id = id;
Name = name;
DepartmentId = deparmentId;
PositionId = positiionId;
}
protected EmployeeData() { }
}
По аналогии создаём DepartmentData (свойства Guid Id, string Name) и PositionData (свойства Guid Id, string Name).
DepartmentData:
[Table("DepartmentData")]
internal class DepartmentData
{
[Key]
public Guid Id { get; set; }
[DisplayName("Название отдела")]
public string Name { get; set; }
public DepartmentData(Guid id, string name)
{
Id = id;
Name = name;
}
protected DepartmentData() { }
}
PositionData:
[Table("PositionData")]
internal class PositionData
{
[Key]
public Guid Id { get; set; }
[DisplayName("Название должности")]
public string Name { get; set; }
public PositionData(Guid id, string name)
{
Id = id;
Name = name;
}
protected PositionData() { }
}
Но дело в том, что пользоваться такими данными - не очень удобно. Давайте представим их в виде моделей, с которыми будет в дальнейшем работать.
Employee:
internal class Employee
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public Department Department { get; private set; }
public Position Position { get; private set; }
public Employee(Guid id, string name, Department department, Position position)
{
Id = id;
Name = name;
Department = department;
Position = position;
}
protected Employee() { }
}
Department:
internal class Department
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public Department(Guid id, string name)
{
Id = id;
Name = name;
}
protected Department() { }
}
Position:
internal class Position
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public Position(Guid id, string name)
{
Id = id;
Name = name;
}
protected Position() { }
}
Но в чём загвостка - нам нужно конвертировать из одного типа в другой. В этом нам поможет Factory. Она позволит избежать большого количества повторений кода. В дальнейшем нужно будет лишь воспользоваться ею, чтоб конвертировать что-либо из одного типа в другой. К примеру, если у вас есть коллекция IEnumerable<DepartmentData>, которую нужно конвертировать в IEnumerable<Department- вам нужно написать нечто подобное:depatmentData.Select(x => DepartmentFactory.Create(x));`.
DeparmentFactory:
internal static class DeparmentFactory
{
public static Department Create(DepartmentData department) =>
new(department.Id, department.Name);
public static DepartmentData CreateData(Department department) =>
new(department.Id, department.Name);
}
PositionFactory:
internal class PositionFactory
{
public static Position Create(PositionData position) =>
new(position.Id, position.Name);
public static PositionData CreateData(Position position) =>
new(position.Id, position.Name);
}
Дело с EmployeeFactory чуть сложнее - нам нужно в нашей базе или в кэше (речь о нём дальше) найти данные по их Id. Из-за этого добавим 2 дополнительных параметра.
internal static class EmployeeFactory
{
public static Employee Create(EmployeeData employee, Department department, Position position) =>
new(employee.Id, employee.Name, department, position);
public static EmployeeData CreateData(Employee employee) =>
new(employee.Id, employee.Name, employee.Department.Id, employee.Position.Id);
}
Далее необходимо определиться, где наши данные будут храниться. Реализаций очень много. Я выбрал наиболее простую и понятную - кэш. Итак, создадим общий интерфейс, от которого будут наследоваться все остальные наши кэши (вообще, интерфейсы много где можно создать для упрощения своей жизни в будущем - для factory, например):
internal interface IBaseCache<T>
{
public IReadOnlyList<T> Values { get; }
public T GetById(Guid id);
public void Add(T entity);
public bool TryAdd(T entity);
public bool Remove(Guid id);
}
Далее создадим классы, в которых будем хранить кэш наших сотрудников и тд:
EmployeesCache:
internal sealed class EmployeesCache : IBaseCache<Employee>
{
private readonly Dictionary<Guid, Employee> _employees;
public IReadOnlyList<Employee> Values => _employees.Values.ToList();
public void Add(Employee entity) =>
_employees.Add(entity.Id, entity);
public bool TryAdd(Employee entity) =>
_employees.TryAdd(entity.Id, entity);
public Employee GetById(Guid id) =>
_employees.TryGetValue(id, out var employee) is true
? employee
: throw new KeyNotFoundException(nameof(id));
public bool Remove(Guid id) =>
_employees.Remove(id);
}
DepartmentCache:
internal sealed class DepartmentCache : IBaseCache<Department>
{
private readonly Dictionary<Guid, Department> _demartments;
public IReadOnlyList<Department> Values => _demartments.Values.ToList();
public void Add(Department entity) =>
_demartments.Add(entity.Id, entity);
public bool TryAdd(Department entity) =>
_demartments.TryAdd(entity.Id, entity);
public Department GetById(Guid id) =>
_demartments.TryGetValue(id, out var department) is true
? department
: throw new KeyNotFoundException(nameof(id));
public bool Remove(Guid id) =>
_demartments.Remove(id);
}
PositionCache:
internal sealed class PositionCache : IBaseCache<Position>
{
private readonly Dictionary<Guid, Position> _positions;
public IReadOnlyList<Position> Values => _positions.Values.ToList();
public void Add(Position entity) =>
_positions.Add(entity.Id, entity);
public bool TryAdd(Position entity) =>
_positions.TryAdd(entity.Id, entity);
public Position GetById(Guid id) =>
_positions.TryGetValue(id, out var position) is true
? position
: throw new KeyNotFoundException(nameof(id));
public bool Remove(Guid id) =>
_positions.Remove(id);
}
Далее создадим некоторые контроллеры, которые будут помогать нам взаимодействовать с кэшем. Я реализую всего-лишь один. Остальные можно сделать по аналогии:
internal class EmployeeController
{
public EmployeesCache EmployeesCache { get; }
public EmployeeController(EmployeesCache employeesCache)
{
EmployeesCache = employeesCache;
}
public IEnumerable<Employee> GetEmployeesByDepartmentId(Guid departmentId) =>
EmployeesCache.Values.Where(x => x.Department.Id.Equals(departmentId));
}
Для контроллер - некоторое расширение вашего кэше, которое позволяет взаимодействовать с ним.
Таким образом, с помощью контроллера мы можем как-либо отредактировать данные в нашем кэше (Пример: EmployeesCache.Add(....)). Так же, мы можем прописать тут необходимые методы для сортировки и прочего. Такая реализация позволит структурировать нашу программу и сделать его понятной для будущего Васи, который будет копаться в вашем коде
Имеем сущность:
public class Employee
{
public int Id { get; set; }
public string Department { get; set; }
public string Post { get; set; }
}
Имеем контекст:
public class ApplicationContext : DbContext
{
// ...
public DbSet<Employee> Employees { get; set; }
}
Создаём БД и заполняем таблицу данными:
using var db = new ApplicationContext();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.Employees.Add(new Employee { Department = "Руководители", Post = "Генеральный директор" });
db.Employees.Add(new Employee { Department = "Руководители", Post = "Зам. генерального директора" });
db.Employees.Add(new Employee { Department = "Центр питания", Post = "Повар" });
db.Employees.Add(new Employee { Department = "Центр питания", Post = "Помощник повара" });
db.Employees.Add(new Employee { Department = "Руководители", Post = "Главный бухгалтер" });
db.SaveChanges();
Теперь одним запросом получаем данные в необходимом виде:
using var db = new ApplicationContext();
var dict = db.Employees
.GroupBy(e => e.Department, e => e.Post)
.Select(g => new { g.Key, List = g.ToList() })
.ToDictionary(x => x.Key, x => x.List);
foreach (var pair in dict)
Console.WriteLine(pair.Key + ": " + string.Join(", ", pair.Value));
Рассмотрим, как может выглядеть вариант, когда для отделов и должностей существуют отдельные таблицы.
Имеем сущности:
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Department Department { get; set; }
public Post Post { get; set; }
}
Имеем контекст:
public class ApplicationContext : DbContext
{
// ...
public DbSet<Employee> Employees { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Post> Posts { get; set; }
}
В данном случае таблица Employees связана с таблицами Departments и Posts связями один-к-одному (one-to-one).
Создаём БД и заполняем таблицы отделов и должностей:
using var db = new ApplicationContext();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var department1 = new Department { Name = "Руководители" };
var department2 = new Department { Name = "Центр питания" };
db.Departments.Add(department1);
db.Departments.Add(department2);
var post1 = new Post { Name = "Генеральный директор" };
var post2 = new Post { Name = "Зам. генерального директора" };
var post3 = new Post { Name = "Повар" };
var post4 = new Post { Name = "Помощник повара" };
var post5 = new Post { Name = "Главный бухгалтер" };
db.Posts.AddRange(post1, post2, post3, post4, post5);
db.SaveChanges();
Теперь, когда мы хотим добавить работника, мы сперва должны получить из БД записи должности и отдела для него.
После чего создаём сотрудников, назначаем им нужные свойства и сохраняем в БД.
using var db = new ApplicationContext();
var managersDepartment = db.Departments.First(d => d.Name == "Руководители");
var nutritionCenterDepartment = db.Departments.First(d => d.Name == "Центр питания");
var ceoPost = db.Posts.First(p => p.Name == "Генеральный директор");
var deputyCeofPost = db.Posts.First(p => p.Name == "Зам. генерального директора");
var chefPost = db.Posts.First(p => p.Name == "Повар");
var cookfPost = db.Posts.First(p => p.Name == "Помощник повара");
var chiefAccountantPost = db.Posts.First(p => p.Name == "Главный бухгалтер");
var employee1 = new Employee { Department = managersDepartment, Post = ceoPost, Name = "Вася" };
var employee2 = new Employee { Department = managersDepartment, Post = deputyCeofPost, Name = "Петя" };
var employee3 = new Employee { Department = nutritionCenterDepartment, Post = chefPost, Name = "Джон Сильвер" };
var employee4 = new Employee { Department = nutritionCenterDepartment, Post = cookfPost, Name = "Реми" };
var employee5 = new Employee { Department = managersDepartment, Post = chiefAccountantPost, Name = "Изольда Паллна" };
db.Employees.AddRange(employee1, employee2, employee3, employee4, employee5);
db.SaveChanges();
Запрос на получение сгруппированных по отделам должностей будет практически такой же, как и раньше. Только нужно брать названия Name.
using var db = new ApplicationContext();
var dict = db.Employees
.GroupBy(e => e.Department.Name, e => e.Post.Name)
.Select(g => new { g.Key, List = g.ToList() })
.ToDictionary(x => x.Key, x => x.List);
foreach (var pair in dict)
Console.WriteLine(pair.Key + ": " + string.Join(", ", pair.Value));

