简体   繁体   中英

EF Core upsert child entities upon parent entity save

I have a DDD aggregate with User as root and Appointment as the child records. I want, when I save a User in my repository, the existing child appointments of the User are updated and the child appointment which do not exist in the database be inserted.

I have for each entity a domain class and a persistence class

I have read this post on the matter and I think I understand what the accepted answer explained, so I went with the following logic:

    public async Task Update(IApplicationUserWithAppointments domainUserEntity)
    {
        ApplicationUserEntity persistenceUserEntity = await FindEntityById(domainUserEntity.Id);

        IDictionary<Guid, AppointmentEntity> appointmentEntitiesById =
            persistenceUserEntity.Appointments
                .ToDictionary(appointmentEntity => appointmentEntity.Id, appointmentEntity => appointmentEntity);

        persistenceUserEntity.UserName = domainUserEntity.UserName;
        persistenceUserEntity.Password = domainUserEntity.Password;
        persistenceUserEntity.FirstName = domainUserEntity.FirstName;
        persistenceUserEntity.LastName = domainUserEntity.LastName;
        persistenceUserEntity.Role = domainUserEntity.Role;
        persistenceUserEntity.Validated = domainUserEntity.Validated;
        persistenceUserEntity.Appointments = domainUserEntity.Appointments
            .Select(appointment => BuildOrUpdateAppointmentEntity(appointmentEntitiesById, appointment))
            .ToList();

        this.context.Users.Update(persistenceUserEntity);
    }

    private static AppointmentEntity BuildOrUpdateAppointmentEntity(IDictionary<Guid, AppointmentEntity> appointmentEntitiesById,
        Appointment appointment)
    {
        if (!appointmentEntitiesById.ContainsKey(appointment.Id))
        {
            return new AppointmentEntity(appointment);
        }

        AppointmentEntity appointmentEntity = appointmentEntitiesById[appointment.Id];
        appointmentEntity.State = appointment.State.Name;
        appointmentEntity.DateTime = appointment.DateTime;
        return appointmentEntity;
    }

The logic is that I retrieve the user entity from the database with its appointments (to avoid detached entity error). Then, I map the appointment entity, updating those which exist and creating the new one.

This logic works well for the update of existing appointment records, but for the insertion of new appointments records, the following unit test fails:

    public async Task Update_ChildRecord_InsertChildRecordInDb()
    {
        // Given
        ApplicationUserEntity entity = await this.dbDataFactory.InsertValidatedLifeAssistant();
        var repository = new ApplicationUserRepository(this.context, factory);

        entity.Appointments.Add(new AppointmentEntity()
        {
            Id = Guid.NewGuid(),
            State = "Planned",
            DateTime = DateTime.Today.AddDays(3)
        });
        
        // When
        await repository.Update(entity.ToDomainEntity(new AppointmentStateFactory()));
        await repository.Save();

        // Then
        entity = await this.context
            .Users
            .Include(u => u.Appointments)
            .FirstAsync(item => item.Id == entity.Id);
        (await this.context.Appointments.CountAsync()).Should().Be(1);
    }

With the following error:

The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.

On the Save call of the update.

I don't understand why my logic is not working. Thank you in advance

After a lot of debugging, and thank to a github post I realised that my problem was that my child record had already an Id generated, and EF Core did not expected it to. I solved my problem with by using ValueGeneratedNever() in my onModelCreating model definition. Ex:

        modelBuilder.Entity<AppointmentEntity>().HasKey(appointment => appointment.Id);
        modelBuilder.Entity<AppointmentEntity>().Property(appointment => appointment.Id).ValueGeneratedNever();
        modelBuilder.Entity<AppointmentEntity>().Property(appointment => appointment.State);
        modelBuilder.Entity<AppointmentEntity>().Property(appointment => appointment.DateTime);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM