【发布时间】:2017-12-08 22:41:30
【问题描述】:
我有一个带有时间戳(并发令牌)列的模型。我正在尝试编写一个集成测试,在其中检查它是否按预期工作但没有成功。我的测试如下所示
- 使用 HttpClient 调用从 Web api 获取应更新的实体。
- 直接向 Context 发出请求,获取相同的实体
- 从步骤 2 更改实体的属性。
- 保存在步骤 3 中更新的实体。
- 从步骤 1 更改实体的属性。
- 使用 HttpClient 向 Web Api 发送带有新实体的 put 请求。
- 在我的 Web API 中,我首先从数据库中获取实体,根据从客户端获取的实体设置属性和时间戳值。现在我在 api 控制器中的实体对象的时间戳值与数据库中的不同。现在我预计 savechanges 会失败,但事实并非如此。相反,它将实体保存到数据库并生成新的时间戳值。我检查了 Sql Server Profiler 以查看生成的查询,结果发现它仍然使用旧的时间戳值,而不是我分配给我的 api 控制器中的实体的那个。
这是什么原因?它与 Timestamp 是数据库生成的值是否使 EF 忽略从业务层对其所做的更改有什么关系?
完整的测试应用可以在这里找到:https://github.com/Abrissirba/EfTimestampBug
public class BaseModel
{
[Timestamp]
public byte[] Timestamp { get; set; }
}
public class Person : BaseModel
{
public int Id { get; set; }
public String Title { get; set; }
}
public class Context : DbContext
{
public Context()
{}
public Context(DbContextOptions options) : base(options)
{}
public DbSet<Person> Persons{ get; set; }
}
protected override void BuildModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "7.0.0-rc1-16348")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("EFTimestampBug.Models.Person", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Timestamp")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate();
b.Property<string>("Title");
b.HasKey("Id");
});
}
// PUT api/values/5
[HttpPut("{id}")]
public Person Put(int id, [FromBody]Person personDTO)
{
// 7
var person = db.Persons.SingleOrDefault(x => x.Id == id);
person.Title = personDTO.Title;
person.Timestamp = personDTO.Timestamp;
db.SaveChanges();
return person;
}
[Fact]
public async Task Fail_When_Timestamp_Differs()
{
using (var client = server.CreateClient().AcceptJson())
{
await client.PostAsJsonAsync(ApiEndpoint, Persons[0]);
// 1
var getResponse = await client.GetAsync(ApiEndpoint);
var fetched = await getResponse.Content.ReadAsJsonAsync<List<Person>>();
Assert.True(getResponse.IsSuccessStatusCode);
Assert.NotEmpty(fetched);
var person = fetched.First();
// 2
var fromDb = await db.Persons.SingleOrDefaultAsync(x => x.Id == person.Id);
// 3
fromDb.Title = "In between";
// 4
await db.SaveChangesAsync();
// 5
person.Title = "After - should fail";
// 6
var postResponse = await client.PutAsJsonAsync(ApiEndpoint + person.Id, person);
var created = await postResponse.Content.ReadAsJsonAsync<Person>();
Assert.False(postResponse.IsSuccessStatusCode);
}
}
// generated sql - @p1 has the original timestamp from the entity and not the assigned and therefore the save succeed which was not intended
exec sp_executesql N'SET NOCOUNT OFF;
UPDATE[Person] SET[Title] = @p2
OUTPUT INSERTED.[Timestamp]
WHERE [Id] = @p0 AND[Timestamp] = @p1;
',N'@p0 int,@p1 varbinary(8),@p2 nvarchar(4000)',@p0=21,@p1=0x00000000000007F4,@p2=N'After - should fail'
【问题讨论】:
-
请包含您的实体框架映射代码,它可能与此有关。您还可以发布从 Sql Profiler 生成和执行的查询吗?
标签: entity-framework entity-framework-core