Установить значение по умолчанию при сохранении изменений - PullRequest
1 голос
/ 04 апреля 2019

Все мои сущности расширяются BaseEntity, который имеет эти (соответствующие) свойства:

namespace Sppd.TeamTuner.Core.Domain.Entities
{
    public abstract class BaseEntity
    {
        /// <summary>
        ///     Unique identifier identifying a single instance of an entity.
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        ///     Specifies when the entity instance has been created.
        /// </summary>
        public DateTime CreatedOnUtc { get; set; }

        /// <summary>
        ///     Specifies by whom the entity instance has been created.
        /// </summary>
        public Guid CreatedById { get; set; }

        /// <summary>
        ///     Specifies when the entity instance has been last updated.
        /// </summary>
        public DateTime ModifiedOnUtc { get; set; }

        /// <summary>
        ///     Specifies by whom the entity instance has been last modified.
        /// </summary>
        public Guid ModifiedById { get; set; }

        protected BaseEntity()
        {
            Id = Guid.NewGuid();
        }
    }
}

Я хочу разрешить ef установить созданные / измененные свойства перед сохранением. Для этого я добавил следующее при настройке DbContext:

    private void ConfigureBaseEntity<TEntity>(EntityTypeBuilder<TEntity> builder)
        where TEntity : BaseEntity
    {
        // Constraints
        builder.Property(e => e.CreatedOnUtc)
               .HasDefaultValueSql(_databaseConfig.Value.SqlUtcDateGetter)
               .ValueGeneratedOnAdd();
        builder.Property(e => e.ModifiedOnUtc)
               .HasDefaultValueSql(_databaseConfig.Value.SqlUtcDateGetter)
               .ValueGeneratedOnAddOrUpdate()
               .IsConcurrencyToken();
        builder.Property(e => e.CreatedById)
               .HasValueGenerator<CurrentUserIdValueGenerator>()
               .ValueGeneratedOnAdd();
        builder.Property(e => e.ModifiedById)
               .HasValueGenerator<CurrentUserIdValueGenerator>()
               .ValueGeneratedOnAddOrUpdate();
    }

А это ValueGenerator:

internal class CurrentUserIdValueGenerator : ValueGenerator<Guid>
{
    public override bool GeneratesTemporaryValues => false;

    public override Guid Next(EntityEntry entry)
    {
        return GetCurrentUser(entry).Id;
    }

    private static ITeamTunerUser GetCurrentUser(EntityEntry entry)
    {
        var userProvider = entry.Context.GetService<ITeamTunerUserProvider>();
        if (userProvider.CurrentUser != null)
        {
            return userProvider.CurrentUser;
        }

        if (entry.Entity is ITeamTunerUser user)
        {
            // Special case for user creation: The user creates himself and thus doesn't exist yet. Use him as the current user.
            return user;
        }

        throw new BusinessException("CurrentUser not defined");
    }
}

Сохраняя изменения, вызывая SaveChanges() на DbContext, я получаю следующее исключение:

Microsoft.EntityFrameworkCore.DbUpdateException
  HResult=0x80131500
  Message=An error occurred while updating the entries. See the inner exception for details.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(DbContext _, ValueTuple`2 parameters)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IReadOnlyList`1 entries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList`1 entriesToSave)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Sppd.TeamTuner.Infrastructure.DataAccess.EF.TeamTunerContext.SaveChanges(Boolean acceptAllChangesOnSuccess) in E:\dev\Sppd.TeamTuner\Backend\Sppd.TeamTuner.DataAccess.EF\TeamTunerContext.cs:line 48

Inner Exception 1:
SqlException: Cannot insert the value NULL into column 'ModifiedById', table 'Sppd.TeamTuner-DEV.dbo.CardType'; column does not allow nulls. UPDATE fails.
The statement has been terminated.

При проверке содержимого ChangeTracker все сущности имеют набор ModifiedById: enter image description here Примечание: ToList() обязательны для заполнения, в противном случае они неправильно перечислили

Помимо того, что идентификаторы содержат ожидаемое мной значение, свойство ModifiedById не является Nullable и, следовательно, никогда не должно иметь значение null (оно может содержать default(Guid)).

Есть идеи, что происходит?

[Изменить] Код для добавления:

Сеялка:

internal class CardTypeDbSeeder : IDbSeeder
{
    private readonly IRepository<CardType> _cardTypeRepository;

    public CardTypeDbSeeder(IRepository<CardType> cardTypeRepository)
    {
        _cardTypeRepository = cardTypeRepository;
    }

    public int Priority => SeederConstants.Priority.BASE_DATA;

    public void Seed()
    {
        _cardTypeRepository.Add(new CardType
                                {
                                    Id = Guid.Parse(TestingConstants.CardType.ASSASSIN_ID),
                                    Name = "Assassin"
                                });
    }
        [...]
}

Repository:

namespace Sppd.TeamTuner.Infrastructure.DataAccess.EF.Repositories
{
    internal class Repository<TEntity> : IRepository<TEntity>
        where TEntity : BaseEntity
    {
        protected DbSet<TEntity> Set => Context.Set<TEntity>();

        protected TeamTunerContext Context { get; }

        protected virtual Func<IQueryable<TEntity>, IQueryable<TEntity>> Includes { get; } = null;

        public Repository(TeamTunerContext context)
        {
            Context = context;
        }

        public async Task<TEntity> GetAsync(Guid entityId)
        {
            TEntity entity;
            try
            {
                entity = await GetQueryWithIncludes().SingleAsync(e => e.Id == entityId);
            }
            catch (InvalidOperationException)
            {
                throw new EntityNotFoundException(typeof(TEntity), entityId.ToString());
            }

            return entity;
        }

        public async Task<IEnumerable<TEntity>> GetAllAsync()
        {
            return await GetQueryWithIncludes().ToListAsync();
        }

        public void Delete(Guid entityId)
        {
            var entityToDelete = GetAsync(entityId);
            entityToDelete.Wait();
            Set.Remove(entityToDelete.Result);
        }

        public void Add(TEntity entity)
        {
            Set.Add(entity);
        }

        public void Update(TEntity entity)
        {
            Set.Update(entity);
        }

        protected IQueryable<TEntity> GetQueryWithIncludes()
        {
            return Includes == null
                ? Set
                : Includes(Set);
        }
    }
}

Передать изменения:

            if (isNewDatabase)
            {
                s_logger.LogDebug($"New database created. Seed data for SeedMode={databaseConfig.SeedMode}");

                foreach (var seeder in scope.ServiceProvider.GetServices<IDbSeeder>().OrderBy(seeder => seeder.Priority))
                {
                    seeder.Seed();
                    s_logger.LogDebug($"Seeded {seeder.GetType().Name}");
                }

                // The changes are usually being saved by a unit of work. Here, while starting the application, we will do it on the context itself.
                context.SaveChanges();
            }

1 Ответ

0 голосов
/ 17 мая 2019

Как обсуждалось в комментариях и большом количестве просмотров в GitHub, оказалось, что для этого невозможно использовать генераторы значений.Я решил это, реализовав PrepareSaveChanges() в переопределении Datacontext.SaveChanges, которое вызывает следующий код:

    private void SetModifierMetadataProperties(EntityEntry<BaseEntity> entry, DateTime saveDate)
    {
        var entity = entry.Entity;
        var currentUserId = GetCurrentUser(entry).Id;

        if (entity.IsDeleted)
        {
            entity.DeletedById = currentUserId;
            entity.DeletedOnUtc = saveDate;
            return;
        }

        if (entry.State == EntityState.Added)
        {
            entity.CreatedById = currentUserId;
            entity.CreatedOnUtc = saveDate;
        }

        entity.ModifiedById = currentUserId;
        entity.ModifiedOnUtc = saveDate;
    }

Для полной реализации следуйте пути выполнения из переопределения SaveChangesAsync

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