代码之家  ›  专栏  ›  技术社区  ›  Trung Đặng Đức

保存到数据库

  •  -2
  • Trung Đặng Đức  · 技术社区  · 1 年前

    我在将一对多关系的值保存到数据库时遇到问题。它一直在说 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; }
    }
    
    1 回复  |  直到 1 年前
        1
  •  3
  •   Steve Py    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. 部门。其次,如果找不到部门,它将返回具有提供ID的部门,而不会返回其他任何内容。

    这是你应该返回的:

    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 它被调用到Contoso上。A. 很多 试图包裹EF会引起混乱和并发症 DbContext / DbSet Repository类中的功能,因此我强烈建议删除该存储库,并首先使用 和部门 DbSet 在试图抽象任何东西之前。

    如果出于任何原因,您想在读取要穿梭的实体时排除字段等,那么您可以使用单个 .Select() 填充新模型以传递回去。如果您需要填充Department DTO/ViewModel,我不建议使用Department实体:

    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结果。这样做的问题是,期望部门实体的方法应该始终期望一个适当的、被跟踪的实体,而不是冒着得到一些未被跟踪的、部分填充的占位符的风险。

    错误的症结在于:EF被设计为与引用一起工作。当您创建与其他实体相关的实体时,有两种主要类型的引用。协会和儿童。关联是此实体与数据库中其他现有记录之间的链接。孩子是基本上会根据父母而生或死的物品。类似“组”的东西很可能是一种关联关系,这意味着当你添加或更新一个部门并将其与一个组相关联时,你需要确保EF使用的是它所知道的现有记录,而不是它将视为新组的东西。

    因此,例如,如果你想创建一个与ID为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将把其视为“新”组,而不是与现有行的关联。

    如果您已经有一个组引用,该引用可能会被跟踪/附加,也可能不会被跟踪/附着,那么如果Contoso碰巧已经在跟踪该组,您需要检查并替换该引用:

    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 [];
    

    保护设置器,删除任何试图设置它的代码。人员列表将被初始化,并准备好进入您创建的任何新部门。代码永远不应该在集合导航属性上调用setter,因为这会破坏EF中的更改跟踪。如果你想替换实体中的一个集合,这并不像转储集合并重新创建它那么简单,你需要弄清楚要删除和添加哪些项目,以便变更跟踪器知道删除要删除的行并插入要添加的行。设置器应仅在单个对象引用上可用,例如 public Group Group { get; set; }

    希望这能帮助您在插入和更新数据时开始跟踪正确关联实体的问题。可能还有更多的领域会导致问题,但请尝试这些更改,并逐步减少您试图测试和测试的代码量,以便更快地发现问题。