Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RowVersion property incorrectly fails required properties check when using In-Memory provider for unit testing #27027

Closed
to-ros opened this issue Dec 16, 2021 · 2 comments

Comments

@to-ros
Copy link

to-ros commented Dec 16, 2021

File a bug

Issue #10613 introduced null-checking on required properties when using the In-Memory provider.
After updating however all my unit tests with record creation are now failing because I haven't set the RowVersion (ConcurrencyToken) property when creating an entity in my service.
As far as I can see this is incorrect behaviour as the ConcurrencyToken value is/should always (be) generated by the database engine.

Could properties that are defined as IsConcurrencyToken and ValueGeneratedOnAddOrUpdate either be ignored by the check or have a value generated for them so the check doesn't fail?

Is there a way to hook into the In-Memory provider to handle the generation of the ConcurrencyToken property values?

Include your code

internal sealed class RouteService
{
        private readonly AppDbContext_dbContext;

        public RouteService(AppDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<int> CreateRouteAsync(string routeName, CancellationToken token)
        {
            var route = new Route
            {
                RouteName = routeName,
            };

            _dbContext.Routes.Add(route);
            await _dbContext.SaveChangesAsync(token).ConfigureAwait(false);
            
            return route.RouteId;
        }

}

[TestClass]
public sealed class RouteServiceTests
{
        private DbContextOptions<AppDbContext> _options;
        private AppDbContext_dbContext;
        private RouteService _service;

        [TestInitialize]
        public void TestInitialize()
        {
            _options = new DbContextOptionsBuilder<AppDbContext>()
                .UseInMemoryDatabase(databaseName: "RouteServiceTests")
                .Options;

            _dbContext = Substitute.ForPartsOf<AppDbContext>(_options);
            
            var routes = new List<Route> {
                new Route { RouteId = 1, RouteName = "Route 1", RowVersion = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, }, },
                new Route { RouteId = 2, RouteName = "Route 2", RowVersion = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, }, },
                new Route { RouteId = 3, RouteName = "Route 3", RowVersion = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, }, },
                new Route { RouteId = 4, RouteName = "Route 4", RowVersion = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, }, },
            };
            _dbContext.Routes.AddRange(routes);
            _dbContext.SaveChanges();

            _service = new RouteService(_dbContext);
        }

        [TestMethod]
        public async Task CreateRouteAsync_Pass_Test()
        {
            // Act.
            var result = await _service.CreateRouteAsync("Route 5", CancellationToken.None).ConfigureAwait(false);

            // Assert.
            Assert.AreEqual(5, result);
        }

}

public class Route
{
        public int RouteId { get; set; }
        public string RouteName { get; set; }
        public byte[] RowVersion { get; set; }
}

internal class RouteMapper : IEntityTypeConfiguration<Route>
{
        public void Configure(EntityTypeBuilder<Route> builder)
        {
            builder.ToTable("Routes");
            builder.HasKey(x => x.RouteId);

            builder.Property(x => x.RouteId).HasColumnName("RouteId").IsRequired(true).UseIdentityColumn();
            builder.Property(x => x.RouteName).HasColumnName("RouteName").IsRequired(true).HasMaxLength(50).IsUnicode(true);
            builder.Property(x => x.RowVersion).HasColumnName("RowVersion").IsRequired(true).IsRowVersion();
        }
}

public class AppDbContext : DbContext
{
        public AppDbContext (DbContextOptions<AppDbContext > options)
            : base(options)
        {
        }

        public DbSet<Route> Routes { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            
            modelBuilder.ApplyConfiguration(new RouteMapper());
        }
 }

Include stack traces

Message: 
    Test method RouteServiceTests.CreateRouteAsync_Pass_Test threw exception: 
    Microsoft.EntityFrameworkCore.DbUpdateException: Required properties '{'RowVersion'}' are missing for the instance of entity type 'Route'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the entity key value.

  Stack Trace: 
    InMemoryTable`1.ThrowNullabilityErrorException(IUpdateEntry entry, IList`1 nullabilityErrors)
    InMemoryTable`1.Create(IUpdateEntry entry)
    InMemoryStore.ExecuteTransaction(IList`1 entries, IDiagnosticsLogger`1 updateLogger)
    InMemoryDatabase.SaveChangesAsync(IList`1 entries, CancellationToken cancellationToken)
    StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
    StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
    DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
    DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
    RouteService.CreateRouteAsync(String routeName, CancellationToken token) line 137
    RouteServiceTests.CreateRouteAsync_Pass_Test() line 343
    ThreadOperations.ExecuteWithAbortSafety(Action action)

Include provider and version information

EF Core version: 6.0.0
Database provider: Microsoft.EntityFrameworkCore.InMemory
Target framework: .NET 6.0
Operating system: Windows 10
IDE: Visual Studio 2020 17.0.1

@ajcvickers
Copy link
Member

Duplicate of #26649. See also #26686, and consider voting for #10625.

@to-ros
Copy link
Author

to-ros commented Dec 19, 2021

Sorry I had searched for duplicates but hadn't come across those.

@to-ros to-ros closed this as completed Dec 19, 2021
@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants