Testing concurrency tokens with Microsoft.EntityFrameworkCore.InMemory

entity-framework-core

Question

As part of my EF 6.1 to EF Core 2.0 migration I added a simple test to check if the concurrency tokens do work the same way. I noted, however, that is dependent on the underlying database provider: it works for SqlServer, but it does not for MS InMemory database.

The entity class is pretty simple:

public class AcademicTermDate
{
    public int AcademicTermDateID { get; set; }

    public DateTime StartDate { get; set; } //but no end date, because it's derived in controcc and rederived here.

    public bool Deleted { get; set; }

    [Timestamp]
    public byte[] RowVersion { get; set; }
}

The code that creates it is also trivial:

        using (var context = _factory.CreateDbContext(null))
        {
            var term = new AcademicTermDate();
            term.StartDate = new DateTime(2001, month, 1);
            context.AcademicTermDate.Add(term);

            context.SaveChanges();
        }

What is interesting, if I use old plain Sql Server as per following code:

    public MyContext CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder<MyContext>();

        var connectionString = "server=.\\sql2012;Database=CA15;Trusted_Connection=True;";
        builder.UseSqlServer(connectionString);

        return new MyContext(builder.Options);
    }

it works as expected; on context.SaveChanges() I can see RowVersion to be populated.

If, however, I use the InMemory database provider, which seemed so tempting to be used for my tests, I can see a different behaviour: RowVersion remains populated with null value (i.e. not initialised at all).

For the latter, the factory is defined as:

    public MyContext CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder<MyContext>();

        builder.UseInMemoryDatabase(databaseName: "InMemory");

        return new MyContext(builder.Options);
    }

Am I missing any vital setting for InMemory db I should provide? The difference seem odd and, honestly, quite disturbing.

All code targets .NET Core 2.0:

<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
    <PackageReference Include="System.ComponentModel.Annotations" Version="4.4.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.0" />
</ItemGroup>

Any help greatly appreciated.

1
4
10/29/2017 6:30:33 AM

Popular Answer

The docs on testing with InMemory do a serious attempt at expectation management. For example:

(InMemory) is not designed to mimic a relational database.

Which, among other things, means

  • InMemory will allow you to save data that would violate referential integrity constraints in a relational database.

  • If you use DefaultValueSql(string) for a property in your model, this is a relational database API and will have no effect when running against InMemory.

No doubt, initializing and updating RowVersion column values can be added to this list.

Then they give the tip:

For many test purposes these difference will not matter. However, if you want to test against something that behaves more like a true relational database, then consider using SQLite in-memory mode.

For what it's worth, I agree with the first part which amounts to: test things where you know the differences don't matter. It may be convenient to use InMemory where you want to use the database layer merely as a quick supplier of mock data that you subsequently use in business logic unit tests.

But I wholeheartedly disagree with the second advice to use SQLite to test functions that depend more on correct data layer behavior. Well, go ahead if SQLite is the production database. Otherwise: always do integration tests against the same database brand as the production database. There are too many differences between database brands and query providers (the part that translates expressions into SQL) to make integration test reliable enough. And what if SQLite doesn't support LINQ constructs or statements or features that your own database brand/query provider does? Avoid them to please SQLite? I don't think so.

So my advice is to set up a Sql Server integration test database to test RowVersion-related code.

2
10/30/2017 1:43:21 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow