Asp.net mvc Web API的并发性检查未检查
使用EntityFrameworkV6,我正在构建一个原型来演示Web Api和桌面应用程序中的并发检查 实体:Asp.net mvc Web API的并发性检查未检查,asp.net-mvc,entity-framework,knockout.js,concurrency,Asp.net Mvc,Entity Framework,Knockout.js,Concurrency,使用EntityFrameworkV6,我正在构建一个原型来演示Web Api和桌面应用程序中的并发检查 实体: public static class IRowVersionExtensions { public static string RowVersionAsString(this IRowVersion ivr) { return Convert.ToBase64String(ivr.RowVersion); } public stat
public static class IRowVersionExtensions
{
public static string RowVersionAsString(this IRowVersion ivr)
{
return Convert.ToBase64String(ivr.RowVersion);
}
public static void SetRowVersion(this IRowVersion ivr, string rowVersion)
{
ivr.RowVersion = Convert.FromBase64String(rowVersion);
}
}
public interface IRowVersion
{
byte[] RowVersion { get; set; }
}
public class Department : IRowVersion
{
[Key]
public int Id { get; set; }
[Required, MaxLength(255)]
public string Name { get; set; }
public string Description { get; set; }
[Timestamp]
[ConcurrencyCheck]
public byte[] RowVersion { get; set; }
}
DbContext:
public class CompDbContext : DbContextEx
{
public CompDbContext()
: base("Company")
{
this.Configuration.LazyLoadingEnabled = false;
}
public DbSet<Department> Departments { get; set; }
}
Api调用:
DepartmentVM.prototype.onSave = function (entity) {
var method = entity.id() ? 'PATCH' : 'PUT';
$.ajax({
url: '/api/departments',
method: method,
data: ko.toJSON(entity),
contentType: 'application/json',
dataType: 'JSON'
})
.done(function (data) {
alert('Saved');
entity.rowVersion(data.rowVersion);
entity.id(data.id);
})
.error(function (data) {
alert('Unable to save changes to department.');
});
};
当我在控制器操作中断线时:
if (Convert.ToBase64String(changed.RowVersion) != Convert.ToBase64String(original.RowVersion))
在第一次保存时,changed.RowVersion==original.RowVersion(完美)并保存(如预期的那样)。在第二页的保存中,更改的.RowVersion!=original.RowVersion(完美版),但它仍然保存,没有异常(不符合预期)
有人能帮我理解为什么这在桌面应用程序中运行良好,但在Web API中不起作用吗?它不起作用,因为EF使用
RowVersion
的“原始”值来执行并发检查。在您的示例中,原始值(就DbContext
而言)是来自数据库的值,因为它是使用.Find()
从数据库加载的
例如,更改实体的RowVersion为1,数据库中当前的RowVersion为2
// changed's RowVersion is 1
var original = dbContext.Departments.Find(changed.Id);
// original's RowVersion is 2
if (original == null)
this.NotFound();
if (Convert.ToBase64String(changed.RowVersion) != Convert.ToBase64String(original.RowVersion))
Console.WriteLine("Should error."); // 2 != 1, so prints this line
original.RowVersion = changed.RowVersion;
// original's "current" RowVersion is now 1
// ... but its "original" RowVersion is still 2!
original.Name = changed.Name;
original.Description = changed.Description;
dbContext.SaveChanges();
// UPDATE DEPT SET ... WHERE Id = ... AND RowVersion = 2
// (works, therefore no concurrency exception)
要使其工作,您只需将传入实体添加到上下文中
[HttpPatch, Route("")]
public Department UpdateDepartment(Department changed)
{
dbContext.Entry(changed).State = EntityState.Modified;
dbContext.SaveChanges();
// you'll get an exception if RowVersion has changed
return changed;
}
如果您只想更改名称和描述,您可以有选择地将这些属性标记为已更改,而其余属性则不会更新
[HttpPatch, Route("")]
public Department UpdateDepartment(Department changed)
{
dbContext.Entry(changed).State = EntityState.Unchanged;
dbContext.Entry(changed).Property(d => d.Name).IsModified = true;
dbContext.Entry(changed).Property(d => d.Description).IsModified = true;
dbContext.SaveChanges();
// you'll get an exception if RowVersion has changed
return changed;
}
控制台应用程序工作的原因有点幸运。有一种比赛条件,如果在
t1
中的Find()
在t2
中的SaveChanges()
之后执行Find()
(或者反之亦然),你也会遇到同样的情况。非常感谢你,我的队友和我都快被这件事砸到脑袋了!没有必要在问题的标题中添加[SOLVED]
。您在下面的答案上打上的绿色复选标记向系统(以及所有其他感兴趣的用户)表明问题已“解决”。啊,没有意识到-谢谢。
[HttpPatch, Route("")]
public Department UpdateDepartment(Department changed)
{
dbContext.Entry(changed).State = EntityState.Unchanged;
dbContext.Entry(changed).Property(d => d.Name).IsModified = true;
dbContext.Entry(changed).Property(d => d.Description).IsModified = true;
dbContext.SaveChanges();
// you'll get an exception if RowVersion has changed
return changed;
}