使用Microsoft.EntityFrameworkCore.InMemory测试并发令牌

时间:2017-10-29 06:30:34

标签: entity-framework-core

作为我的EF 6.1到EF Core 2.0迁移的一部分,我添加了一个简单的测试来检查并发令牌是否以相同的方式工作。但是,我注意到,它依赖于底层数据库提供程序:它适用于SqlServer,但它不适用于MS InMemory数据库。

实体类非常简单:

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

创建它的代码也很简单:

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

            context.SaveChanges();
        }

有趣的是,如果我按照以下代码使用旧的普通Sql Server:

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

它按预期工作;在context.SaveChanges()上我可以看到RowVersion被填充。

但是,如果我使用InMemory数据库提供程序,这似乎很容易用于我的测试,我可以看到不同的行为:RowVersion仍然填充空值(即根本没有初始化)。

对于后者,工厂定义为:

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

        builder.UseInMemoryDatabase(databaseName: "InMemory");

        return new MyContext(builder.Options);
    }

我是否错过了我应该提供的InMemory数据库的重要设置?差别似乎很奇怪,老实说,非常令人不安。

所有代码都以.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>

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:2)

使用InMemory进行测试的docs会对期望管理进行认真尝试。例如:

  

(InMemory)不是为了模仿关系数据库而设计的。

除其他外,其中包括

  •   

    InMemory将允许您保存违反关系数据库中的参照完整性约束的数据。

  •   

    如果对模型中的属性使用DefaultValueSql(string),则这是一个关系数据库API,在针对InMemory运行时无效。

毫无疑问,初始化和更新RowVersion列值可以添加到此列表中。

然后他们给出了提示:

  

对于许多测试目的,这些差异无关紧要。但是,如果您想测试的行为更像真正的关系数据库,那么请考虑使用SQLite内存模式。

对于它的价值,我同意第一部分相当于:测试你知道差异无关紧要的事情。使用InMemory可能会很方便,您只想将数据库层用作随后在业务逻辑单元测试中使用的模拟数据的快速供应商。

但我完全不同意第二条使用SQLite来测试更多依赖于正确数据层行为的函数的建议。好吧,如果SQLite是生产数据库,请继续。否则:始终针对与生产数据库相同的数据库品牌进行集成测试。数据库品牌和查询提供程序(将表达式转换为SQL)之间存在太多差异,使集成测试足够可靠。如果SQLite不支持您自己的数据库品牌/查询提供程序所做的LINQ构造或语句或功能,该怎么办?避免他们取悦SQLite?我不这么认为。

所以我的建议是设置一个Sql Server集成测试数据库来测试与RowVersion相关的代码。

答案 1 :(得分:0)

在内存中,DB不生成时间戳。但是,实体框架仍会验证时间戳是否与任何更新匹配!

因为逗留null,他们一直都匹配。

因此,您仍然可以通过设置更新的行版本来测试是否抛出DbUpdateConcurrencyException

entity.Timestamp = new byte[] {1};
repository.Update(entity);
await context.SaveAsync(); // <-- this will throw a DbUpdateConcurrencyException

答案 2 :(得分:0)

EF Core 5.0 及更低版本不支持与内存中提供程序的并发检查,但已在计划中。见https://github.com/dotnet/efcore/issues/10625

EfCore.InMemoryHelpers 提供 EF Core In-Memory Database Provider 的包装器。专门解决以下 EF 错误。

InMemory:改进内存中的密钥生成