AutoMapper 9 устанавливает новые объекты EF Core 3.1 как измененные, а не добавленные - PullRequest
0 голосов
/ 30 марта 2020

TLDR: добавление нового неотслеживаемого типа сущности во вложенную коллекцию существующего DTO => существующий DTO сопоставляется с существующим отслеживаемым типом сущности => EF Core tracker теперь видит состояние новой сущности как измененное, а не ошибка Added =>, там обновлять нечего.

Я столкнулся с проблемами с AutoMapper и Entity Framework Core после того, как мы обновили эти пакеты:

  1. . NET Core 3.1
  2. AutoMapper 9.0.0
  3. EF Core 3.1.3

Существует простой сценарий:

У нас есть DTO D, представляющий сущность E. Оба DTO и entity имеют коллекцию дочерних объектов типа R, которые также являются EF-сущностями (не самый лучший дизайн, но держите его так, пожалуйста).

  1. Я создаю новый DTO D (с пустым коллекция), сопоставьте его с сущностью E и сохраните в базе данных. ОК
  2. Я загружаю E из базы данных, сопоставляю его с DTO D (E теперь отслеживается в EF Core). OK
  3. Я создаю новый объект R и добавляю его в коллекцию DTO D. OK
  4. Я сопоставляю DTO D с существующим объектом E, который теперь отслеживается ядром EF
  5. Теперь в EF-трекере есть элемент для объекта R, который говорит, что R изменено , но определенно должно быть добавлено .

Существует минимальный рабочий пример такого поведения:

using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using System;
using System.Collections.Generic;
using System.Linq;

namespace AutomapperEFTest
{
    class TestContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string databaseName = "TestDB";
            optionsBuilder
                .UseInMemoryDatabase(databaseName: databaseName)
                .ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning));
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<TestEntity>(e =>
            {
                e.ToTable("TestEntity");
                e.HasKey(t => t.Id);
                e.Property(t => t.SomeProperty);
                e.HasMany(t => t.RefValues)
                .WithOne()
                .HasForeignKey(r => r.EntityId);
            });
            modelBuilder.Entity<RefValue>(e =>
            {
                e.ToTable("RefValue");
                e.HasKey(r => r.Id);
                e.Property(r => r.EntityId)
                .HasColumnName("EntityId");
                e.Property(r => r.Value)
                .HasColumnName("Value");
            });
        }

        public DbSet<TestEntity> Entities => Set<TestEntity>();
        public DbSet<RefValue> RefValues => Set<RefValue>();
    }

    class TestEntity
    {
        public Guid Id { get; set; }
        public string SomeProperty { get; set; }
        public ICollection<RefValue> RefValues { get; set; }
    }

    class TestDto
    {
        public TestDto()
        {
            RefValues = new List<RefValue>();
        }
        public Guid Id { get; set; }
        public string SomeProperty { get; set; }
        public List<RefValue> RefValues { get; set; }
    }

    class RefValue
    {
        public Guid Id { get; set; }
        public Guid EntityId { get; set; }
        public string Value { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var config = new MapperConfiguration(cfg => {
                cfg.CreateMap<TestDto, TestEntity>()
                .ForMember(d => d.Id, o => o.MapFrom(s => s.Id))
                .ForMember(d => d.SomeProperty, o => o.MapFrom(s => s.SomeProperty))
                .ForMember(d => d.RefValues, o => o.MapFrom(s => s.RefValues))
                .ReverseMap();
            });

            using (var context = new TestContext())
            {
                var mapper = new Mapper(config);

                #region Works
                /*
                 * Adding both parent and child in one step works.
                var newDto = new TestDto() { Id = Guid.Parse("4C1596D7-C080-4E56-B2F6-1BAEBDC1CF7D"), SomeProperty = "Whatever" };                
                var newRef = new RefValue() { Id = Guid.Parse("526DC1A6-130B-41DC-9F74-32F9C807D7F5"), EntityId = Guid.Parse("4C1596D7-C080-4E56-B2F6-1BAEBDC1CF7D"), Value = "New Ref Value" };
                newDto.RefValues.Add(newRef);
                var entityToSave = mapper.Map<TestEntity>(newDto);
                context.Add(entityToSave);
                context.SaveChanges();
                */
                #endregion

                #region Data preparation
                var newDto = new TestDto(){Id = Guid.Parse("4C1596D7-C080-4E56-B2F6-1BAEBDC1CF7D"),SomeProperty = "Whatever"};
                var entity = mapper.Map<TestEntity>(newDto);
                context.Entities.Add(entity);
                context.SaveChanges();
                #endregion

                //Simple scenario - add a new item to a collection of an existing item
                var loadedEntity = context.Entities.FirstOrDefault();
                var loadedDto = mapper.Map<TestDto>(loadedEntity);
                var newRef = new RefValue(){Id = Guid.Parse("526DC1A6-130B-41DC-9F74-32F9C807D7F5"),EntityId = Guid.Parse("4C1596D7-C080-4E56-B2F6-1BAEBDC1CF7D"),Value = "New Ref Value"};
                loadedDto.RefValues.Add(newRef);
                //ensure it will be mapped to existing tracked item
                var entityToSave = mapper.Map(loadedDto, loadedEntity, typeof(TestDto), typeof(TestEntity));
                //Now the inner state of the newRef in the tracker is Modified but definitely should be Added
                //check it out: context.ChangeTracker.Entries<RefValue>().ToArray();

                //throws exception
                context.SaveChanges();                
            }
        }
    }
}

Как должен быть настроен AutoMapper для предотвращения этого поведения? У нас это работало в предыдущих версиях - NET Core 2.1, EF Core 1.x, AutoMapper 8.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...