Сохранение в базу данных

У меня возникла проблема с сохранением значения отношения «один ко многим» в базе данных. Постоянно пишет, что GroupId отслеживается дважды, но у меня даже в репозитории отслеживания нет.

Если вы каким-либо образом можете помочь оптимизировать код, я буду очень признателен, но если нет или не можете, это тоже нормально.

DepartmentController:

[HttpGet]
public async Task<IActionResult> Details (string id)
{
    var department = await _repository.GetByIdAsync(id);

    if (department != null) 
    {
        var deparment = new EditDepartmentViewModel()
        {
            DepartmentId = department.DepartmentId,
            DepartmentName = department.DepartmentName,
            PeopleInDepartment = _repository.GetNamesByDepartment(id),
        };

        return View(deparment);
    }
    return RedirectToAction("Index");
}

[HttpPost]
public async Task<IActionResult> Details(EditDepartmentViewModel model)
{
    model.DepartmentId = await _repository.GetIdByNameAsync(model.DepartmentName);
    model.Persons = _repository.PersonsInDep(model.DepartmentId);

    var userId = _contextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);

    var user = await _userRepository.GetByIdAsync(userId);

    var department = await _repository.GetByIdAsync(model.DepartmentId);

    if (department != null)
    {
        department.DepartmentName = model.DepartmentName;
        department.Persons = model.Persons;

        if (!string.IsNullOrWhiteSpace(model.PeopleInDepartment))
        {
            var names = model.PeopleInDepartment.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
            var alreadyin = _repository.GetNamesByDepartment(model.DepartmentId);
            var alinasList = alreadyin.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries);

            if (names.Count() >= alinasList.Count())
            {
                foreach (var name in names)
                {
                    if (alreadyin.Contains(name) == false)
                    {
                        var person = new Person
                        {
                            PersonId = Guid.NewGuid().ToString(),
                            Name = name.Trim(),
                            DepartmentId = department.DepartmentId,
                            GroupPerDay = new List<PersonInGroupPerDay>(),
                        };

                        var dg = new DayGroup()
                        {
                            Id = Guid.NewGuid().ToString(),
                            Group = await _groupRepository.GetByDepartment(department),
                        };

                        var p = new PersonInGroupPerDay()
                        {
                            Id = Guid.NewGuid().ToString(),
                            Person = person,
                            DayGroup = dg,
                            Groups = new List<Group>(),
                        };

                        p.Groups.Add(dg.Group);
                        person.GroupPerDay.Add(p);
                        department.Persons.Add(person);
                    }

                    _repository.Update(department);
                }
            }
            else
            {
                foreach(var name in alinasList)
                {
                    if (model.PeopleInDepartment.Contains(name) == false)
                    {
                        var person = await _repository.GetByNameAsync(name);

                        if (person != null)
                        {
                            var ps = await _repository.GetByPerson(person);

                            foreach (var p in ps)
                            {
                                _repository.DeletePIGPD(p);
                            }

                            _repository.DeletePerson(person);
                        }
                    }

                    _repository.Update(department);
                }
            }
        }

        return RedirectToAction("Index");
    }

    return RedirectToAction("Index");
}

DepartmentRepository:

public bool Save()
{
     var saved = _context.SaveChanges();
     return saved > 0;
}

public bool Update(Department department)
{
     _context.Update(department);
     return Save();
}

public async Task<Department> GetByIdAsync(string id)
{
    var dep = new Department();
    dep.DepartmentId = id;
    dep.DepartmentName = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.DepartmentName).FirstOrDefaultAsync();
    dep.Persons = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(p => p.Persons).FirstOrDefaultAsync();
    dep.Users = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.Users).FirstOrDefaultAsync();
    dep.DayDepartments = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.DayDepartments).FirstOrDefaultAsync();
    dep.Group = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.Group).FirstOrDefaultAsync();
    dep.Days = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.Days).FirstOrDefaultAsync();
    dep.Months = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.Months).FirstOrDefaultAsync();
    return dep;
}

Детали отдела:

@model QLAnCaRedo.Web.ViewModels.EditDepartmentViewModel
<div class = "container mb-3">
    <div class = "row pt-4">
        <div class = "col-6">
            <h2 class = "text-primaary">
                Edit phòng ban
            </h2>
        </div>
    </div>
</div>
<form method = "post" action = "Details">
    <div class = "mb-3">
        <label for = "" class = "form-label">Tên phòng ban</label>
        <input type = "text" class = "form-control" asp-for = "DepartmentName" />
    </div>
    <div class = "mb-3">
        <label for = "" class = "form-label">Người trong phòng ban</label>
        <input type = "text" class = "form-control" asp-for = "@Model.PeopleInDepartment" />
    </div>
    <button type = "submit" class = "btn btn-primary">Submit</button>
    <a type = "button" class = "btn btn-danger" asp-controller = "Department" asp-action = "Delete" asp-route-id = "@Model.DepartmentId">
        Delete
    </a>

Модели

public class Department
{
    [Key]
    public string DepartmentId { get; set; }
    public string DepartmentName { get; set; }
    public List<Person> Persons { get; set; }
    public Group Group { get; set; }
}

public class Group
{
    [Key]
    public string GroupId { get; set; }
    [Required]
    public AppUser AppUser { get; set; }
    public Department Department { get; set; }
    [Required]
    public List<DayGroup> DayGroups { get; set; }
}

public class DayGroup
{
    public string Id { get; set; }
    public DateTime SubcriptionDate { get; set; }
    public Group Group { get; set; }
    public List<PersonInGroupPerDay> PersonInGroups { get; set; }
}

public class PersonInGroupPerDay
{
    public string Id { get; set; }
    public DayGroup DayGroup { get; set; }
    public Person Person { get; set; }
    [DefaultValue(0)]
    public int Set1 { get; set; }
    [DefaultValue(0)]
    public int Set2 { get; set; }
    [DefaultValue(0)]
    public int Set3 { get; set; }
    public List<Group> Groups { get; set; }
}

public class EditDepartmentViewModel
{
    public string DepartmentId { get; set; }
    public string DepartmentName { get; set; }
    public string PeopleInDepartment { get; set; }
    public List<Person> Persons { get; set; }
}

Он продолжает говорить, что GroupId отслеживается дважды, бесполезно. Какое полное и точное сообщение об ошибке вы получаете. Если вам нужна помощь, вам нужно предоставить реальную информацию, а не какой-то бессмысленный шум. Сообщение об ошибке находится на экране прямо перед вами, поэтому нет оправдания тому, что вы не указали его в своем сообщении.

Ken White 31.07.2024 06:26
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хорошо, помимо стандартного заявления об отказе от ответственности «Не пишите репозиторий, EF DbSet уже является репозиторием», вы делаете что-то очень, очень неправильно, и мне искренне любопытно, где вы сталкивались с чем-то вроде такого примера??

Для начала этот код должен идти:

public async Task<Department> GetByIdAsync(string id)
{
    var dep = new Department();
    dep.DepartmentId = id;
    dep.DepartmentName = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.DepartmentName).FirstOrDefaultAsync();
    dep.Persons = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(p => p.Persons).FirstOrDefaultAsync();
    dep.Users = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.Users).FirstOrDefaultAsync();
    dep.DayDepartments = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.DayDepartments).FirstOrDefaultAsync();
    dep.Group = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.Group).FirstOrDefaultAsync();
    dep.Days = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.Days).FirstOrDefaultAsync();
    dep.Months = await _context.Departments.AsNoTracking().Where(d => d.DepartmentId == id).Select(d => d.Months).FirstOrDefaultAsync();
    return dep;
}

В этом есть много неправильного. Сначала он делает 7 обращений к базе данных для заполнения 1 отдела. Во-вторых, он вернет отдел с указанным идентификатором и ничего больше, если отдел не найден.

Вот что вы должны вернуть:

public async Task<Department> GetByIdAsync(string id)
{
   var department = await _context.Departments
       .AsNoTracking()
       .Single(d => d.DepartmentId == id);
    return department;
}

Если есть связанные объекты, которые нужно включить, загрузите их, добавив .Include(x => x.{Insert Related Navigation Property}). При этом извлекается отсоединенная сущность, но если вы получаете сущность с намерением ее обновить, то вместо этого я бы удалил .AsNoTracking(), чтобы возвращаемую сущность можно было изменить, а средство отслеживания изменений создало подходящий SQL при вызове SaveChanges в DbContext. МНОГО путаницы и сложностей возникнет при попытке обернуть функциональность EF DbContext/DbSet в класс репозитория, поэтому я настоятельно рекомендую удалить репозиторий и сначала заставить все работать, используя DbContext и отделы DbSet, прежде чем пытаться что-либо абстрагировать.

Если по какой-либо причине вы хотите исключить поля и т. д. при чтении объектов, которые необходимо перемещать, вы можете использовать один .Select(), чтобы заполнить новую модель для возврата. Я не рекомендую использовать для этого сущность Department, если у вас есть DTO/ViewModel отдела для заполнения:

public async Task<DepartmentViewModel> GetByIdAsync(string id)
{
    var departmentVM = await _context.Departments
        .Where(d => d.DepartmentId == id)
        .Select(d => new DepartmentViewModel
        {
            DepartmentId = d.DepartmentId,
            // .. populate remaining fields...
        }).Single();
    return departmentVM;
}

Не заполняйте сущность «Отдел» частично, чтобы она служила моделью представления или результатом DTO транспорта данных. Проблема с выполнением чего-то подобного заключается в том, что методы, ожидающие сущность Department, всегда должны ожидать правильную, отслеживаемую сущность, не рискуя получить какой-то неотслеживаемый, частично заполненный заполнитель.

Суть ошибки: EF предназначен для работы со ссылками. Когда вы создаете сущность, связанную с другими сущностями, существует два основных типа ссылок. Ассоциации и дети. Ассоциации — это связи между этой сущностью и другими существующими записями в базе данных. Дети — это предметы, которые, по сути, будут жить или умирать в зависимости от своего Родителя. Что-то вроде «Группы», скорее всего, будет ассоциативным отношением, что означает, что когда вы добавляете или обновляете отдел и связываете его с группой, вам необходимо убедиться, что EF работает с единственной ссылкой на то, что, как он знает, является существующей записью. , а не что-то, что он собирается рассматривать как новую группу.

Например, если вы хотите создать отдел, связанный с записью группы с идентификатором 10, вам не нужно делать что-то вроде:

department.Group = new Group { GroupId = 10; }

или даже:

var group = _context.Groups.AsNoTracking().Single(g => g.GroupId == 10);
department.Group = group;

Вместо этого вы используете:

var group = _context.Groups.Single(g => g.GroupId == 10);
department.Group = group;

Ключевое отличие состоит в том, что последний пример извлекает отслеживаемую ссылку на группу, где AsNoTracking() вернет неотслеживаемый новый экземпляр. EF будет рассматривать это как «новую» группу, если она будет добавлена ​​в Департамент, а не как ассоциацию с существующей строкой.

Если у вас уже есть ссылка на группу, которая может или не может отслеживаться/прикрепляться, вам необходимо проверить и заменить эту ссылку, если DbContext уже отслеживает эту группу:

var trackedGroup = _context.Groups.Local.FirstOrDefault(g => g.GroupId = group.GroupId);
if (trackedGroup == null)
{
    _context.Attach(group);
    trackedGroup = group;
}   
department.Group = trackedGroup;

Это проверка .Local, которая ищет только локальный кэш отслеживания, но не попадает в базу данных. Если мы не найдем отслеживаемую копию, мы будем отслеживать отсоединенную копию (группу). Таким образом отдел привязывается к отслеживаемой ссылке.

Другие элементы, которые необходимо исправить, — это удалить все свойства навигации по коллекции настроек кода. Например:

public List<Person> Persons { get; set; }

Использовать:

public List<Person> Persons { get; protected set; } = new [];

Защитите установщик, удалив любой код, который пытается его установить. Список лиц будет инициализирован и готов к использованию в любом новом отделе, который вы создадите. Код никогда не должен вызывать установщик свойств навигации по коллекции, поскольку это нарушит отслеживание изменений в EF. Если вы хотите заменить коллекцию в сущности, это не так просто, как выгрузить коллекцию и воссоздать ее заново. Вам нужно выяснить, какие элементы удалить, а какие добавить, чтобы система отслеживания изменений знала, что нужно удалять строки, которые необходимо удалить, и вставьте строки, которые необходимо добавить. Сеттеры должны быть доступны только для ссылок на отдельные объекты, например public Group Group { get; set; }.

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

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