Нарушение ограничения первичного ключа при добавлении новой строки в таблицу с EF - PullRequest
4 голосов
/ 25 января 2012

Мы получаем очень неустойчивое (как каждые несколько недель кто-то жалуется на это) нарушение ограничения первичного ключа в нашем веб-приложении.Я искал базу кода, и единственный код, который даже создает какие-либо строки в этой таблице, выглядит следующим образом:

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.

Ответы [ 2 ]

6 голосов
/ 25 января 2012

Поскольку вы используете Oracle, вы должны использовать значение последовательности оракула.Он не вернет дубликат!Вызов sequence.nextval вместо вашего max () + 1 решит это.

3 голосов
/ 25 января 2012

(просто публикация для будущих читателей)

Я считаю, что у меня есть достойный способ решить эту проблему. Я не уверен, что это абсолютно лучший способ, но я выйду на конечности и скажу, что это намного лучше, чем было раньше. Вот что я сделал.

Сначала я создал новую последовательность:

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 для каждой последовательности (сейчас у меня есть только одна, но у меня будут другие, когда я переношу устаревший код), а не строку, чтобы убедиться, что последовательность действительна и для некоторой помощи Intellisense .

Далее я заменил:

newDoc.DOCUMENTID = nextDocId;

С:

newDoc.DOCUMENTID = context.GetNextSequence(EntityUtil.Sequence.TPM_PROJECTVERSIONDOCS_PK_SEQ);

Пока, похоже, это работает довольно хорошо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...