使用 EF 将新行添加到表中时违反主键约束

问题描述 投票:0回答:3

我们的网络应用程序中经常出现间歇性的主键约束违规(就像每隔几周就会有人抱怨这一点)。我搜索了代码库,甚至在此表中创建任何行的唯一代码如下:

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 查询序列。

c# .net sql entity-framework entity-framework-4
3个回答
6
投票

由于您使用的是 Oracle,因此您应该使用 Oracle 序列值。它不会返回重复项!调用sequence.nextval而不是max()+1将解决它。


4
投票

(只是为未来的读者发布)

我相信我有一个很好的方法来解决这个问题。我不确定这是否是绝对最好的方法,但我会大胆地说它比以前的方法要好得多。这就是我所做的。

首先,我创建了一个新序列:

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);

到目前为止,这似乎效果很好。


0
投票

将带有 Oracle 序列的

.ValueGeneratedOnAdd().UseHiLo
添加到方法
OnModelCreating
。具有 Oracle 序列的 EF 核心将负责唯一性:

speechToTextEvent.Property(e => e.Id)
.ValueGeneratedOnAdd()    
.UseHiLo("SPEECH_TO_TEXT_EVENTS_SEQUENCE")
.HasColumnName("ID")
.IsRequired();
© www.soinside.com 2019 - 2024. All rights reserved.