.Net 实体框架。更新对象的 ICollection

问题描述 投票:0回答:1

我已经寻找了类似问题的解决方案,并且取得了一些进展,但我陷入了无法取得进展的地步。 我正在开展一个实践项目,我可以在其中更新应用程序详细信息的成员。 我使用 EntityFramework 写入 sql 数据库,但更新 memebr 的 ICollection 属性时遇到问题 会员对象:

{
 Some properties: string.....
 ICollection<Interest> Interest
}

当我从前端发送和反对时,我会发送整个 Mmeber。我试图保存的更改是新添加到兴趣集合中的项目。 我的终点:

[HttpPut]
    public async Task<ActionResult> UpdateMember(MemberDto member)
    {   
        if (this.HttpContext.Request.Cookies.ContainsKey("access-token"))
            {
                var cookieValue = this.HttpContext.Request.Cookies["access-token"];
                var tokenHandler = new JwtSecurityTokenHandler();
                var token = tokenHandler.ReadJwtToken(cookieValue);
                var userName = token
                    .Claims.FirstOrDefault(claim =>
                        claim.Type == JwtRegisteredClaimNames.UniqueName
                    ).Value;
                var user = await this.userRepository.GetUserByUsernameAsync(userName.ToString());
                if (user == null) return NotFound();

                this.context.Entry(user).State = EntityState.Detached;
                var updatedUser = this.mapper.Map(member, user);
                
                this.context.Entry(user).State = EntityState.Modified;

                this.userRepository.Update(user, updatedUser);
                if (await this.userRepository.SaveAllAsync()) return NoContent();
                return BadRequest("Failed to update user");
            }
        return BadRequest("User nor recognised");
    }

我的存储库处理更新和保存:

public async Task<bool> SaveAllAsync()
        {
            return await context.SaveChangesAsync() > 0;
        }

        public void Update(AppUser user, AppUser updatedUser)
        {
            context.Entry(user).CurrentValues.SetValues(updatedUser);

            UpdateCollection<Interest>(user.Interests, updatedUser.Interests);

        }

        private void UpdateCollection<T>(ICollection<T> existingCollection, ICollection<T> updatedCollection) where T : class
        {
            var existingItems = existingCollection.ToList();
            var updatedItems = updatedCollection.ToList();

            foreach(var updatedItem in updatedItems)
            {
                if(!existingItems.Contains(updatedItem))
                {
                    existingCollection.Add(updatedItem);
                    context.Entry(updatedItem).State = EntityState.Added;
                }
            }

            foreach(var existingItem in existingItems)
            {
                if(!updatedItems.Contains(existingItem))
                {
                    existingCollection.Remove(existingItem);
                    context.Entry(existingItem).State = EntityState.Deleted;
                }
            }

            
        }

我正在使用范围服务作为上下文。 我也在使用 AutoMapper:

CreateMap<MemberDto, AppUser>()
                .ForMember(dest => dest.Interests, opt => opt.MapFrom(src => src.Interests))
                .ForMember(dest => dest.Photos, opt => opt.MapFrom(src => src.Photos))
                .ForMember(dest => dest.Languages, opt => opt.MapFrom(src => src.Languages))
                .ForMember(dest => dest.Settings, opt => opt.MapFrom(src => src.Settings));
            
            CreateMap<PhotoDto, Photo>();
            CreateMap<LanguageDto, Language>();
            CreateMap<InterestDto, Interest>();
            CreateMap<UserSettingDto, UserSetting>();

我还在学习,我已经尝试调试它。我可以看到,当 memberDTO 在兴趣属性中有 5 个对象并且从上下文检索的用户有 4 个对象时,但是当我循环现有属性时,用户兴趣已经有 5 个对象。我没有收到任何错误,但是当我检索回同一用户时,数据兴趣属性有 4 个对象。看来这些改变并没有拯救自己。

感谢任何帮助

完成上述所有代码后,我希望 ICollection 能够随着新的兴趣而更新。

.net entity-framework automapper
1个回答
0
投票

好吧,一开始你就做了太多太多的工作...读到你从 EF 中窃取的内容然后试图交回是令人困惑的。首先,像设置

EntityState
这样的代码并不是一件正常的事情,简单的更新场景不应该涉及它。使用
Update()
也是如此。这是为您与独立实体合作的情况提供的。您没有理由需要使用分离的实体,因为您是在现场加载实体。

第一步是大大简化您的代码。忽略在这里引入存储库的冲动,您不需要它。 EF 已经通过

DbSet<T>
提供了一个存储库,从这里开始。

因此,您有一个针对更新后的用户的 DTO,我假设它也有兴趣信息。如果没有看到 DTO,我建议 DTO 需要包含的所有兴趣信息都是 InterestId 的集合。 我们不需要 Automapper 将 DTO 和兴趣转换为实体对象。这对于插入场景很有用,但我会在更新时避免使用它。我在更新时避免使用它的原因是 DTO 应该只传递我们希望允许更改的详细信息。这可能意味着我们没有足够的信息来准确地组成完整的用户,我们也不应该尝试从客户端传递足够的信息来组成完整的用户。该信息可能会被篡改,导致我们覆盖客户没有业务变化的详细信息。如果客户端可以更新 3 列,则 DTO 应提供 UserId 和这 3 列。 (我们的服务器调用应该验证 UserId 是要更新的客户端会话的有效用户)

接下来,执行更新。更新具有相关数据集合的实体时,获取相关数据非常重要。这意味着急切地加载用户的兴趣:

[HttpPut]
public async Task<ActionResult> UpdateMember(MemberDto member)
{
    try
    {
        var userName = getCurrentUserName();
        
        var user = await _context.Users
            .Include(user => user.Interests)
            .SingleOrDefaultAsync(user => user.UserName == userName);
        if (user == null) 
            return NotFound();

        // TODO: Check that current user can alter user passed in member? 
        // (either that member is same user, or an authorization check?)

        // TODO: copy values from DTO to user....

        // Update the Interest references.
        var existingInterestIds = user.Interests.Select(i => i.InterestId).ToList();
        var updatedInterestIds = member.InterestIds;
        var interestIdsToAdd = updatedInterestIds.Except(existingInterestIds);
        var interestIdsToRemove = existingInterestIds.Except(updatedInterestIds);

        if(interestIdsToRemove.Any())
        {
            var interestsToRemove = user.Interests.Where(i => interestIdsToRemove.Contains(i.InterestId));
            foreach(var interest in interestsToRemove)
                user.Interests.Remove(interest);
        }
        if(interestIdsToAdd.Any())
        {
            var interestsToAdd = await _context.Interests
                .Where(i => interestIdsToAdd.Contains(i.InterestId))
                .ToListAsync();
            foreach(var interest in interestsToAdd)
                user.Interests.Add(interest); 
        }

        await _context.SaveChangesAsync();

        return NoContent();
    }
    catch(UnauthorizedAccessException)
    {
        return Unauthorized();
    }
    catch(Exception ex)
    {
        // TODO: Log exception
        return BadRequest();
    }
}

private string getCurrentUserName()
{
    if (!HttpContext.Request.Cookies.ContainsKey("access-token"))
        throw new UnauthorizedAccessException("No user session.");

    var cookieValue = this.HttpContext.Request.Cookies["access-token"];
    var tokenHandler = new JwtSecurityTokenHandler();
    var token = tokenHandler.ReadJwtToken(cookieValue);
    var userName = token.Claims
        .FirstOrDefault(claim => claim.Type == JwtRegisteredClaimNames.UniqueName)
        .Value;

   if (string.IsNullOrEmpty(userName))
        throw new UnauthorizedAccessException("No user session.");

   return userName;
}

在这里,我提取了获取当前用户会话的逻辑,这可能是需要做的事情,以便可以将其向下移动到基类控制器方法等。当我们获取要更新的用户时,我们使用

.Include(user => user.Interests)
预先加载兴趣。从这里开始,因为我假设我们正在处理用户和兴趣的关联,所以我们发现可能已添加或删除了哪些兴趣。然后,我们删除更新列表中没有的任何兴趣,对于要添加的任何兴趣,我们从 DbContext 中获取这些兴趣并将它们与用户关联。完成后,我们只需调用
SaveChanges
,因为 EF 默认情况下会跟踪用户和关联集合的所有更改。

要复制可以更改的用户详细信息,您可以逐个字段手动执行此操作,也可以设置 Automapper 将值从 DTO 复制到用户实体,并使用您正在使用的

Mapper.Map(src, dest)
方法。我通常建议使用手动复制或参数化 DDD 风格的方法,因为它很容易阅读和理解应该复制的内容,特别是当您正在调查问题并在属性设置器上查找用法时。映射器很有用,但可能有点像隐藏错误的黑匣子。

© www.soinside.com 2019 - 2024. All rights reserved.