我使用两个表实现了内容版本控制。我知道这可能不是最佳实现,但这是我必须使用的。
一个是Contents,它有一个int Id和其他字段,如Title,HtmlContent等。
另一个是ContentVersions。它有一个Content Id的外键,还有一个表示内容修订版的Version字段,还有一个IsCurrent标志,表示哪个版本是当前版本(其中一个副本存在于Contents表中)。它还有其他字段,如Title,HtmlContent等。
当用户更新内容时,系统应该创建新的ContentVersion并更新相应的内容。我正在使用实体框架,看起来有点像这样:
// ASP.NET MVC Action
[HttpPost]
public JsonResult Save(ContentVersion contentVersion)
{
using (var scope = new TransactionScope())
{
try
{
var versions = contentVersionRepository.GetById(contentVersion.Id)
.OrderByDescending(v => v.Version)
.FirstOrDefault();
if (versions == null)
contentVersion.Version = 1;
else
contentVersion.Version = versions.Version + 1;
contentVersion.IsCurrent = false;
contentVersion.LastModified = DateTime.Now;
contentVersionRepository.Save(contentVersion);
contentVersionRepository.UpdateCurrent(contentVersion);
scope.Complete();
return Json(new { status = "success" }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(new { status = "fail" }, JsonRequestBehavior.AllowGet);
}
}
}
// EF repository
public class ContentVersionRepository {
private readonly MyEfContext context;
private readonly DbSet<ContentVersion> dbset;
public MyEfContext DataContext {
get { return context; }
}
public IList<ContentVersion> GetById(int id)
{
return context
.Database.SqlQuery<ContentVersion>(string.Format(@"select * From ContentVersions c Where c.Id = {0}", id))
.OrderBy(v=>v.Version)
.ToList();
}
public int Save(ContentVersion version)
{
Entities.Add(version);
return context.SaveChanges();
}
public int UpdateCurrent(ContentVersion version)
{
context.Database.ExecuteSqlCommand(string.Format("Execute SP_UPDATE_CURRENT {0},{1};", version.Id, version.Version));
return 0;
}
public DbSet Entities {
get { return dbset; }
}
public ContentVersionRepository(MyEfContext context) {
this.context = context;
dbset = context.ContentVersions;
}
}
}
SP_UPDATE_CURRENT sproc看起来像:
CREATE PROCEDURE [dbo].[SP_UPDATE_CURRENT](@Id INT, @Version INT)
AS
BEGIN
UPDATE ContentVersions SET IsCurrent = CASE WHEN [Version] = @Version THEN 1 ELSE 0 END WHERE Id = @Id;
UPDATE Contents SET
Title = cv.Title,
HtmlContent = cv.HtmlContent,
Summary = cv.Summary
FROM Contents c
INNER JOIN ContentVersions cv ON c.Id = cv.Id
WHERE cv.Id = @Id AND cv.IsCurrent = 1
END;
GO
但是,用户报告说,每隔一段时间,在保存新版本时,内容就会不同步。即:内容表未正确更新。新的ContentVersion记录存在,但其IsCurrent为false(0)。这并不总是偶尔发生。
我的想法是双击导致某种竞争条件,但是我放了一些JS以防止用户双击,我还添加了TransactionScope,以防万一。问题仍然存在。
我一遍又一遍地看着这段代码,我无法弄清楚我做错了什么。
答案 0 :(得分:1)
你没有重复的版权号码,对吗?在(contentid,version)上创建一个唯一索引以确保。
使事务可序列化,根据定义,它排除所有并发问题。
将SQL事件探查器与事务跟踪一起使用,以确保作为Save的一部分的所有数据库调用都发生在单个会话和单个事务中。有时候,电话会“滑出”,这很难从源代码中辨别出来。
顺便说一句,你的EF和原始SQL的混合有点奇怪且容易出错。如果EF维护的对象模型与数据库行为不同步,则变为未定义。