2021-01-31

Why is EF Core trying to insert nulls when using custom ValueGenerator?

I have a custom ValueGenerator as follows:

public class UtcDateTimeGenerator : ValueGenerator<DateTime>
{
    public override DateTime Next(EntityEntry entry)
    {
        // This method never seems to be called
        return DateTime.UtcNow;
    }

    protected override object NextValue(EntityEntry entry)
    {
        // This one is called.
        return DateTime.UtcNow;
    }

    public override bool GeneratesTemporaryValues => false;
}

On my entity's IEntityTypeConfiguration I have this:

builder.Property(x => x.Created)
    .ValueGeneratedOnAdd()
    .HasValueGenerator<UtcDateTimeGenerator>()
    .IsRequired();

I have a generic repository:

public class Repository<TEntity> : IRepository<TEntity>
    where TEntity : class
{
    private readonly EmpiresDbContext _dbContext;
    private readonly DbSet<TEntity> _table;
    
    public Repository(EmpiresDbContext dbContext)
    {
        _dbContext = dbContext;
        _table = _dbContext.Set<TEntity>();
    }


    public async Task<IQueryable<TEntity>> GetQueryAsync()
    {
        return _table;
    }

    public async Task<TEntity> GetByIdAsync(object id)
    {
        return await _table.FindAsync(id).ConfigureAwait(false);
    }

    public void Insert(TEntity obj)
    {
        _table.Add(obj);
    }

    public void Update(TEntity obj)
    {
        _table.Attach(obj);
        _dbContext.Entry(obj).State = EntityState.Modified;
    }

    public async Task DeleteAsync(object id)
    {
        var existing = await _table.FindAsync(id).ConfigureAwait(false);
        _table.Remove(existing);
    }

    public async Task SaveAsync()
    {
        await _dbContext.SaveChangesAsync().ConfigureAwait(false);
    }

    public IDatabaseTransaction StartTransaction()
    {
        return new DatabaseTransaction(_dbContext);
    }
}

When I call Insert() I have used the debugger to verify that the call to _table.Add(obj); does in fact call the protected override object NextValue(EntityEntry entry) method on the UtcDateTimeGenerator, and after the call to _table.Add(obj); I can inspect the obj and see that the Created property has a value.

However, when public async Task SaveAsync() calls await _dbContext.SaveChangesAsync().ConfigureAwait(false); I get an exception

SqlException: Cannot insert the value NULL into column 'Created'

Insert() and SaveAsync() are called from within a service using the following code:

var player = new Player(externalId, displayName, email); // The entity we're creating
await using (var transaction = _repository.StartTransaction())
{
    try
    {
        _repository.Insert(player);

        // TODO Add any other new player setup here

        await _repository.SaveAsync().ConfigureAwait(false);
        await transaction.CommitAsync().ConfigureAwait(false);
    }
    catch
    {
        await transaction.RollbackAsync().ConfigureAwait(false);
        throw;
    }
}

DatabaseTransaction is as follows:

public sealed class DatabaseTransaction : IDatabaseTransaction
{
    private readonly IDbContextTransaction _transaction;

    public DatabaseTransaction(EmpiresDbContext dbContext)
    {
        _transaction = dbContext.Database.BeginTransaction();
    }
    
    public void Commit()
    {
        _transaction.Commit();
    }

    public void Rollback()
    {
        _transaction.Rollback();
    }

    public async Task CommitAsync()
    {
        await _transaction.CommitAsync();
    }

    public async Task RollbackAsync()
    {
        await _transaction.RollbackAsync();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    private bool _disposed = false;

    private void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            _transaction?.Dispose();
        }

        _disposed = true;
    }

    public ValueTask DisposeAsync()
    {
        try
        {
            Dispose();
            return default;
        }
        catch (Exception exception)
        {
            return new ValueTask(Task.FromException(exception));
        }
    }
}


from Recent Questions - Stack Overflow https://ift.tt/2Yx1XIM
https://ift.tt/eA8V8J

No comments:

Post a Comment