我们的网络应用程序中出现了一个非常间歇性的问题(就像每隔几周有人抱怨这一点)主键约束违规。我搜索了代码库,甚至在这个表中创建任何行的代码如下:
decimal nextDocId = (from d in context.TPM_PROJECTVERSIONDOCS
orderby d.DOCUMENTID descending
select d.DOCUMENTID).Max() + 1;
foreach (TPM_PROJECTVERSIONDOCS doc in documents)
{
TPM_PROJECTVERSIONDOCS newDoc = new TPM_PROJECTVERSIONDOCS();
newDoc.DOCUMENTID = nextDocId;
newDoc.DOCBLOB = doc.DOCBLOB;
newDoc.DOCUMENTNAME = doc.DOCUMENTNAME;
newDoc.FILECONTENTTYPE = doc.FILECONTENTTYPE;
version.TPM_PROJECTVERSIONDOCS.Add(newDoc);
nextDocId++;
}
我们得到的错误是:
ORA-00001: unique constraint (TPMDBO.TPM_PROJECTVERSIONDOCS_PK) violated
这意味着DOCUMENTID
已在使用中。关于是什么原因,我有一些理论。首先,如果多个人同时添加文档,介于设置nextDocId
的时间和上下文保存的时间之间,可能已将新文档添加到数据库中。但是,这个时间只有几毫秒,所以我认为这不太可能,因为我们网站的流量很小。
我的第二个理论可能是EF做了某种缓存,nextDocId
正在返回一个不再有效的缓存值。
由于此错误只是经常发生,当然只在我们的生产服务器上发生,因此我没有好的方法来调试它或重新编写。
我的问题:最有可能的原因是什么,是否有更好的方法来重写此代码以防止主键违规?我很乐意只为主键使用自动递增字段,但遗憾的是Oracle不支持它们。切换到UUID也是一种解决方案,但会导致很多数据库更改。谢谢!
更新
这是TPM_PROJECTVERSIONDOCS
实体:
<EntityType Name="TPM_PROJECTVERSIONDOCS">
<Key>
<PropertyRef Name="DOCUMENTID" />
</Key>
<Property Name="DOCUMENTID" Type="decimal" Nullable="false" />
<Property Name="PROJECTID" Type="decimal" Nullable="false" />
<Property Name="VERSIONID" Type="decimal" Nullable="false" />
<Property Name="DOCUMENTNAME" Type="VARCHAR2" Nullable="false" MaxLength="500" />
<Property Name="DOCBLOB" Type="BLOB" Nullable="false" />
<Property Name="FILECONTENTTYPE" Type="VARCHAR2" Nullable="false" MaxLength="80" />
</EntityType>
我不知道如何使DOCUMENTID
默认为序列,或使用EF查询序列。
答案 0 :(得分:6)
由于您使用的是Oracle,因此您应该使用oracle序列值。它不会返回重复!调用sequence.nextval而不是max()+ 1将解决它。
答案 1 :(得分:3)
(仅限未来读者发布)
我相信我有一个很好的方法来解决这个问题。我不确定这是绝对最好的方式,但我会坚持下去并说它比以前更好。这就是我所做的。
首先,我创建了一个新序列:
CREATE SEQUENCE TPM_PROJECTVERSIONDOCS_PK_SEQ
START WITH 1
INCREMENT BY 1
NOCACHE
NOCYCLE;
当我将其移植到生产中时,我会在当前MAX
而不是1之后开始;但是我的测试数据库在此表中没有行。
接下来,我在TPMEntities
(我的实体上下文)上创建了一个扩展方法:
public static class EntityUtil
{
public enum Sequence
{
TPM_PROJECTVERSIONDOCS_PK_SEQ
};
public static decimal GetNextSequence(this TPMEntities context, Sequence sequence)
{
string sql = String.Format("select {0}.nextval from dual", sequence.ToString());
var testId = context.ExecuteStoreQuery<decimal>(sql);
return testId.First();
}
}
我决定为每个序列使用enum
(我现在只有一个,但是我会将其他代码移植到遗留代码上),而不是字符串以确保序列有效,对于某些序列智能感知帮助。
接下来,我换了:
newDoc.DOCUMENTID = nextDocId;
使用:
newDoc.DOCUMENTID = context.GetNextSequence(EntityUtil.Sequence.TPM_PROJECTVERSIONDOCS_PK_SEQ);
到目前为止,这似乎运作良好。