【问题标题】:How to update a Object Collection with Automapper and EF Core 2如何使用 Automapper 和 EF Core 2 更新对象集合
【发布时间】:2018-05-25 04:07:43
【问题描述】:

我很难弄清楚如何更新集合。

这是我的模特

    public class ProgramacionSemanal
    {
      public int ProgramacionSemanalId { get; set; }
      public int Semana { get; set; }
      public DateTime FechaInicio { get; set; }
      public DateTime FechaFin { get; set; }
      public ICollection<ProgramacionSemanalDetalle> ProgramacionSemanalDetalles { get; set; }
    }

    public class ProgramacionSemanalDetalle
    {
      [Key]
      public int ProgramacionSemanalDetalleId { get; set; }
      public int ProgramacionSemanalId { get; set; }
      [Column("ClienteId")]
      public Entidad Cliente { get; set; }
      public int UbicacionId { get; set; }
      public Ubicacion Ubicacion { get; set; }
      public int ProductoId { get; set; }
      public Producto Producto { get; set; }
      public int Lunes { get; set; }
      public int Martes { get; set; }
      public int Miercoles { get; set; }
      public int Jueves { get; set; }
      public int Viernes { get; set; }
      public int Sabado { get; set; }
      public int Domingo { get; set; }
      public AppUsuario Usuario { get; set; }
    }  

这是我的 api 中的路由。

  [HttpPut("{programacionsemanalId}")]
  public async Task<IActionResult> Put(int programacionSemanalId, [FromBody] ProgramacionSemanalViewModel model)
  {
     try
     {
        var oldProgramacionSemanal = await _repository.GetProgramacionSemanalAsync(programacionSemanalId);
        if (oldProgramacionSemanal == null) return NotFound($"No se encontro la Programación Semanal.");

        _mapper.Map(model, oldProgramacionSemanal);

        var currentUser = await _userManager.FindByNameAsync(User.Identity.Name);
        foreach (var item in oldProgramacionSemanal.ProgramacionSemanalDetalles)
        {
           var producto = await _repository.GetProductoAsync(item.ProductoId);
           if (producto == null) return BadRequest();
           item.Producto = producto;

           var ubicacion = await _repository.GetUbicacionAsync(item.UbicacionId);
           if (ubicacion == null) return BadRequest();
           item.Ubicacion = ubicacion;
           item.Usuario = currentUser;
        }

        if (await _repository.SaveAllAsync())
        {
           return Ok(_mapper.Map<ProgramacionSemanalViewModel>(oldProgramacionSemanal));
        }
     }
     catch (Exception ex)
     {
        _logger.LogError($"Threw exception while updating: {ex}");
     }

     return BadRequest("Couldn't update");
  }

我认为一切正常,

1) 我收到了模型对象中定义的所有信息。

2) 我从对象 oldProgramacionSemanal 中的 db 获取所有信息

3) 调用 _mapper.map 后,对象信息 oldProgramacionSemanal 被修改,关系值设置为 null,但 Cliente

4) 我遍历集合对象并更新关系值

5) 当它试图保存对象时,我得到这个错误

{System.InvalidOperationException:无法跟踪实体类型“ProgramacionSemanalDetalle”的实例,因为已在跟踪另一个具有键值“{ProgramacionSemanalDetalleId: 38}”的实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap1.ThrowIdentityConflict(InternalEntityEntry entry) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap1.Add(TKey 键,InternalEntityEntry 条目,布尔更新重复) 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry 条目) 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges) 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Nullable1 forceStateWhenUnknownKey) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node, Boolean force) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode node, TState state, Func3 handleNode) 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry,EntityState entityState,布尔 forceStateWhenUnknownKey) 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.NavigationCollectionChanged(InternalEntityEntry 条目,INavigation 导航,IEnumerable1 added, IEnumerable1 已删除) 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationCollectionChanged(InternalEntityEntry 条目,INavigation 导航,IEnumerable1 added, IEnumerable1 已删除) 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavigationChange(InternalEntityEntry 条目,INavigation 导航) 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(InternalEntityEntry 条目) 在 Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager) 在 Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.DetectChanges() 在 Microsoft.EntityFrameworkCore.DbContext.TryDetectChanges() 在 Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(布尔型 acceptAllChangesOnSuccess,CancellationToken cancelToken) 在 C:\gleintech\GasApp\GasApp.Data\GasRepository.cs:line 37 中的 GasApp.Data.GasRepository.SaveAllAsync() 在 GasApp.Controllers.ProgramacionSemanalController.Put(Int32 programacionSemanalId, ProgramacionSemanalViewModel 模型) 在 C:\gleintech\GasApp\GasApp\Controllers\ProgramacionSemanalController.cs:line 171}

有人可以就我做错了什么给我任何建议吗?

阿尔伯托

【问题讨论】:

  • 在 ProgramacionSemanalI 的 ProgramacionSemanalId 上放置 [key] 属性有帮助吗?
  • 您能找出问题所在吗?如果有,你是怎么处理的?我面临着类似的问题。谢谢

标签: asp.net-core automapper ef-core-2.0


【解决方案1】:

您的 AutoMapper 配置在这里很重要。我猜您的问题是您的实体类型和模型类型中的导航属性。如果有集合,则 AutoMapper 不会按键更新现有项;相反,它只是清除目标集合并向其中填充新项目。即使是现在,这也是 EF Core 不喜欢的。 您必须使用包 AutoMapper.Collection 和 AutoMapper.Collection.EntityFrameworkCore。然后您必须将以下内容添加到您的映射器配置中,例如

using AutoMapper;
using AutoMapper.EquivalencyExpression;
...
         var config = new MapperConfiguration(
             cfg =>
             {
                 cfg.AddProfile<AutoMapperProfile>();
                 cfg.AddCollectionMappers();
                 // And AutoMapper's source for key information:
                 cfg.UseEntityFrameworkCoreModel<MyDbContext>();
             });
         var mapper = config.CreateMapper();

这样 AutoMapper 开始比较两侧的项目,并且可以更新项目而不是删除/插入。并通过 UseEntityFrameworkCoreModel AutoMapper 被告知如何比较项目。 您可以通过在映射中声明相等比较来告诉 AutoMapper 如何比较项目,而不是 UseEntityFrameworkCoreModel,例如

using AutoMapper;
using AutoMapper.EquivalencyExpression;
...
    public class AutoMapperProfile : Profile
    {
        public AutoMapperProfile()
        {
            CreateMap<CustomerModel, CustomerDto>()
                .EqualityComparison((model, dto) => model.Primkey == dto.Primkey)
                .ReverseMap();
        }
    }

无论如何,您仍然需要 AddCollectionMappers。

我们刚刚提出了一个解决方案,以解决在 AutoMapper 配置(或 EqualityComparisons)中繁琐地提及 DbContexts。如果您的 EF Core 实体类在键属性上使用 Key 属性,则可以告诉 AutoMapper 使用该信息,而不是询问 DbContext 实例。稍后我可能会发布代码。

【讨论】:

    【解决方案2】:

    您遇到的错误与 EF Core 2.0 中的问题有关 - 请参阅 https://github.com/aspnet/EntityFrameworkCore/issues/7340 。如果您尝试将实体类替换为具有相同主键的新实体类,则会发生这种情况。

    这发生在您的代码中,因为您更改了 producto/ubicacion 导航属性,它告诉 EF 更新它们。 EF 通过(尝试)删除指向的现有实体并将其替换为您刚刚加载的实体来执行此操作。然后 EF Core 2.0 中的限制会引发您发现的错误。

    我不完全确定您要做什么,因为我不知道您的存储库在做什么 - 是否正在读取集合,是否跟踪所有实体,您是否包括 producto/ubicacion?因此我不能给你一个明确的答案,但至少你现在知道为什么会出现错误。

    注意:#7340 问题(我报告的)已在 2.1 中修复,因此可以正常工作,但效率低下,因为您删除/添加了没有或几乎没有变化的关系。如果 producto/ubicacion 没有变化,那么您可以考虑使用 IsModified 方法告诉 EF 它们没有变化,它将保留原始数据,例如

    context.Entry(entity).Navigation("PropertyName").IsModified = false;
    

    我相信这会有所帮助。

    【讨论】:

      【解决方案3】:

      当您查询稍后要更新的实体时会触发此异常。例如,您在 Get api 中检索了一个带有 id 38ProgramacionSemanalDetalle 实例,但在 Put api 中,您正在更改从 viewmodel 映射的模型,而不是 EF 跟踪的实际模型。

      foreach (var item in oldProgramacionSemanal.ProgramacionSemanalDetalles)
      {
          var producto = await _repository.GetProductoAsync(item.ProductoId);
          if (producto == null)
              return BadRequest();
      
          var ubicacion = await _repository.GetUbicacionAsync(item.UbicacionId);
          if (ubicacion == null) 
              return BadRequest();
      
          var programacionSemanalDetalles = _repository.GetProgramacionSemanalDetalles(item.ProgramacionSemanalDetalleId)
          programacionSemanalDetalles.Producto = producto;             
          programacionSemanalDetalles.Ubicacion = ubicacion;
          programacionSemanalDetalles.Usuario = currentUser;
      
          // if some item properties changed
          //programacionSemanalDetalles.SomeProperty = item.SomeProperty;
      }
      

      【讨论】:

        猜你喜欢
        • 2021-04-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多