保存更改时设置默认值

时间:2019-04-04 14:17:39

标签: c# entity-framework-core ef-fluent-api .net-core-2.2

我所有的实体都扩展了res.send(),它具有以下(相关的)属性:

BaseEntity

我想让ef在保存之前设置创建/修改的属性。为此,我在配置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(); } } } 时添加了以下内容:

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. 的内容时,所有实体都设置了ChangeTrackerenter image description here 旁注:ModifiedById是必需的,否则不能正确枚举

除了ID包含我期望的值外,ToList()属性不可为空,因此永远不应为null(它可能包含ModifiedById)。

知道发生了什么吗?

[编辑]要添加的代码:

种子:

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"
                                });
    }
        [...]
}

提交更改:

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);
        }
    }
}

1 个答案:

答案 0 :(得分:0)

正如评论中讨论的和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的执行路径