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

Entity framework core 使用Microsoft.EntityFrameworkCore.InMemory测试并发令牌,entity-framework-core,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

作为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);
    }
public MyContext CreateDbContext(字符串[]args)
{
var builder=new DbContextOptionsBuilder();
var connectionString=“服务器=。\\sql2012;数据库=CA15;可信连接=True;”;
使用SQLServer(connectionString);
返回新的MyContext(builder.Options);
}
它按预期工作;在context.SaveChanges()上,我可以看到要填充的行版本

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

对于后者,工厂定义为:

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

        builder.UseInMemoryDatabase(databaseName: "InMemory");

        return new MyContext(builder.Options);
    }
public MyContext CreateDbContext(字符串[]args)
{
var builder=new DbContextOptionsBuilder();
builder.UseInMemoryDatabase(数据库名:“InMemory”);
返回新的MyContext(builder.Options);
}
我是否缺少应提供的InMemory db的任何重要设置?这种差异似乎很奇怪,老实说,相当令人不安

所有代码目标.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>

Exe
netcoreapp2.0
非常感谢您的帮助。

使用
InMemory进行测试时,请认真尝试期望管理。例如:

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

除其他外,这意味着

  • InMemory将允许您在关系数据库中保存违反引用完整性约束的数据

  • 如果对模型中的属性使用DefaultValueSql(字符串),则这是一个关系数据库API,在运行InMemory时不会产生任何影响

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

然后他们给出提示:

对于许多测试目的,这些差异并不重要。但是,如果您想测试一些更像真实关系数据库的东西,那么在内存模式中考虑使用SQLite。 无论如何,我同意第一部分的观点,即:在你知道差异无关紧要的地方进行测试。如果您只想将数据库层用作模拟数据的快速提供者,则可以方便地使用InMemory,然后在业务逻辑单元测试中使用模拟数据

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


因此,我的建议是设置一个Sql Server集成测试数据库来测试与行版本相关的代码。

内存数据库不会生成时间戳。然而,实体框架仍然验证时间戳是否与任何更新匹配

它们始终匹配,因为保持
null

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

entity.Timestamp=新字节[]{1};
更新(实体);

等待上下文。SaveAsync();// EF Core 5.0及更低版本不支持与内存中提供程序进行并发检查,但这是计划中的。看

提供EF核心内存中数据库提供程序的包装器。具体解决以下EF错误


InMemory:Improve in memory key generation

您是否尝试过使用fluent api设置并发令牌?是的,我尝试过:modelBuilder.Entity().Property(p=>p.RowVersion).valueGeneratedAndorUpdate().IsConcurrencyToken();好的,前面的代码可能不正确,但这段代码也不起作用:modelBuilder.Entity().Property(p=>p.RowVersion).valueGenerateAndorUpdate().IsRowVersion();让我恼火的是,原来的代码在Sql Server上运行得很好,但在InMemory db上却不行。所以我猜这个问题首先是关于数据库提供程序的。记住内存数据库和sql server实例上的数据库是不同的数据库,您检查过内存数据库中的行吗?回答得很好,谢谢。这可能是因为我的假设是错误的,因为我预期内存中的RDBMS会像其他任何RDBMS一样运行;看起来差异更为根本。缺少已实现/缺少的特性列表确实会有帮助,但我很不幸找到了。SQLite在这方面要好得多:。我可以理解你的观点,这可能被认为是一个集成测试;然而,从我的角度来看(我更倾向于db),时间戳没有得到正确维护是一个bug,而不是一个特性。我不认为EF团队同意你的观点。bug是指没有按预期运行的东西。我很确定他们不会声称在InMemory中支持RowVersions。(不要告诉任何人,但我看不到InMemory有多大的实际用途,因为无论我的DAL在哪里参与自动化测试,我都想100%确保它的行为与真实情况完全一致。我认为这是理所当然的