Элемент с таким же ключом уже добавлен - Просто в режиме деблокирования

Эксперты,

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

Кто-нибудь знает больше о таком странном поведении? Как я могу это исправить? Я не могу развернуть отладочную версию для своего клиента :(

В этот момент выбрасывается исключение:

try
{
  var blub = context.ExecuteStoreQuery<int>(QueryString);
}
catch (Exception ex)
{
  // ...
}    

Трассировки стека:

System.ArgumentException: An item with the same key has already been added.
   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boo
lean add)
   at System.Data.Metadata.Edm.ObjectItemAttributeAssemblyLoader.LoadRelationshi
pTypes()
   at System.Data.Metadata.Edm.ObjectItemAttributeAssemblyLoader.LoadTypesFromAs
sembly()
   at System.Data.Metadata.Edm.ObjectItemAssemblyLoader.Load()
   at System.Data.Metadata.Edm.ObjectItemAttributeAssemblyLoader.Load()
   at System.Data.Metadata.Edm.AssemblyCache.LoadAssembly(Assembly assembly, Boo
lean loadReferencedAssemblies, ObjectItemLoadingSessionData loadingData)
   at System.Data.Metadata.Edm.AssemblyCache.LoadAssembly(Assembly assembly, Boo
lean loadReferencedAssemblies, ObjectItemLoadingSessionData loadingData)
   at System.Data.Metadata.Edm.AssemblyCache.LoadAssembly(Assembly assembly, Boo
lean loadReferencedAssemblies, ObjectItemLoadingSessionData loadingData)
   at System.Data.Metadata.Edm.AssemblyCache.LoadAssembly(Assembly assembly, Boo
lean loadReferencedAssemblies, ObjectItemLoadingSessionData loadingData)
   at System.Data.Metadata.Edm.AssemblyCache.LoadAssembly(Assembly assembly, Boo
lean loadReferencedAssemblies, KnownAssembliesSet knownAssemblies, EdmItemCollec
tion edmItemCollection, Action`1 logLoadMessage, Object& loaderCookie, Dictionar
y`2& typesInLoading, List`1& errors)
   at System.Data.Metadata.Edm.ObjectItemCollection.LoadAssemblyFromCache(Object
ItemCollection objectItemCollection, Assembly assembly, Boolean loadReferencedAs
semblies, EdmItemCollection edmItemCollection, Action`1 logLoadMessage)
   at System.Data.Metadata.Edm.MetadataWorkspace.ImplicitLoadAssemblyForType(Typ
e type, Assembly callingAssembly)
   at System.Data.Objects.ObjectContext.ExecuteStoreQueryInternal[TElement](Stri
ng commandText, String entitySetName, MergeOption mergeOption, Object[] paramete
rs)

person Machine    schedule 18.04.2012    source источник
comment
Какую версию и вариант (POCO, Code First и т. д.) EF вы используете?   -  person Pavel Gatilov    schedule 18.04.2012
comment
В трассировке стека должно быть больше информации. Вы, наверное, что-нибудь вырезали?   -  person Pavel Gatilov    schedule 18.04.2012
comment
только что создал модель из существующей базы данных mssql. нет поко.   -  person Machine    schedule 18.04.2012
comment
как выглядит код и окружение? когда вы выполняете версию dbug в идентичной среде (количество пользователей и т. д.)? это веб или рабочий стол?   -  person Rune FS    schedule 18.04.2012
comment
я удалил последнюю часть (вызов моей первой функции сущности), потому что это относится к компании и не должно здесь читаться;)   -  person Machine    schedule 18.04.2012
comment
та же среда в отладке и выпуске. это консольное приложение   -  person Machine    schedule 18.04.2012
comment
если вы не хотите публиковать код, связанный с вашей компанией, вы должны скомпилировать минимальный образец и опубликовать его здесь, никто не поймет, в чем проблема, просто из этой трассировки стека   -  person Ventsyslav Raikov    schedule 18.04.2012
comment
Зачем? это просто функция, в которой вызывается var context = new SecretEntityModelClass();. это все. это конец трассировки стека.   -  person Machine    schedule 18.04.2012
comment
ошибся, я должен исправить себя. в этот момент генерируется исключение: var blub = context.ExecuteStoreQuery‹int›(QueryString);   -  person Machine    schedule 18.04.2012
comment
Убедитесь, что вы получаете доступ к ObjectContext из одного потока (это не потокобезопасно). Многопоточный доступ к объектам EF может вызвать такие ошибки в сборках выпуска из-за некоторых различий в поведении компилятора и JIT.   -  person Pavel Gatilov    schedule 18.04.2012
comment
Вы пишете о «самом первом использовании функции Entity Framework во время выполнения». Вы имеете в виду, что после одного исключения приложение продолжает работать нормально?   -  person Pavel Gatilov    schedule 18.04.2012
comment
нет вылетает. я узнал, что ExecuteStoreQuery вызывает проблему. после удаления приложение работает нормально О_о   -  person Machine    schedule 19.04.2012
comment
Похоже на ошибку. Возможно, вам следует опубликовать репродукцию на сайте подключения. Это все еще может быть как-то связано с edmx и сопоставлениями, поскольку ExecuteStoreQuery использует сопоставления для материализации результатов запроса.   -  person Pavel Gatilov    schedule 19.04.2012
comment
но там должно быть отображено только целое число.. ничего особенного. сценарий просто выполняет SELECT @isUpdated. isUpdated содержит 1 или 0. не могу себе представить, что это может вызвать исключения при отображении.. странная вещь.. не могу отладить, потому что эта ошибка просто возникает в режиме выпуска :(   -  person Machine    schedule 19.04.2012
comment
Ну, я понимаю, что вы сомневаетесь, но исходный код предполагает, что EF пытается загрузить метаданные сопоставления каждый раз, когда вы используете метод, независимо от параметра типа. Я предполагаю, что они просто не рассматривали введение оптимизаций для таких редких случаев, которые стоило бы делать. Однако эту ошибку по-прежнему нельзя рассматривать как ожидаемое поведение.   -  person Pavel Gatilov    schedule 19.04.2012


Ответы (1)


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

Если ошибка возникает только один раз и после этого приложение работает нормально, у вас должны быть проблемы с синхронизацией. Хотя это довольно странно, так как загрузка метаданных вроде как синхронизирована. Но в любом случае объекты EF не являются потокобезопасными. Не используйте их в сценариях с несколькими потоками.

Если сказанное выше не помогло, то, возможно, результаты моего исследования вам помогут. Вот отрывок из исходного кода EF, относящийся к ошибке:

//---------------------------------------------------------------------- 
// <copyright file="ObjectItemAttributeAssemblyLoader.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 

using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Objects.DataClasses;
using System.Diagnostics; 
using System.Reflection;

namespace System.Data.Metadata.Edm 
{
  /// <summary> 
  /// Class for representing a collection of items for the object layer.
  /// Most of the implemetation for actual maintainance of the collection is
  /// done by ItemCollection
  /// </summary> 
  internal sealed class ObjectItemAttributeAssemblyLoader :
      ObjectItemAssemblyLoader
  { 
    // ...

    /// <summary>
    /// This method loads all the relationship type that this entity takes part in
    /// </summary> 
    /// <param name="entityType"></param>
    /// <param name="context"></param> 
    private void LoadRelationshipTypes() 
    {
      foreach (EdmRelationshipAttribute roleAttribute in
               SourceAssembly.GetCustomAttributes(typeof(EdmRelationshipAttribute), false /*inherit*/)) 
      {
        // Check if there is an entry already with this name
        if (TryFindNullParametersInRelationshipAttribute(roleAttribute))
        { 
          // don't give more errors for these same bad parameters
          continue; 
        } 

        bool errorEncountered = false; 

        // return error if the role names are the same
        if (roleAttribute.Role1Name == roleAttribute.Role2Name)
        { 
          SessionData.EdmItemErrors.Add(new EdmItemError(
              System.Data.Entity.Strings.SameRoleNameOnRelationshipAttribute(roleAttribute.RelationshipName, roleAttribute.Role2Name),
                       null)); 
          errorEncountered = true; 
        }


        if (!errorEncountered)
        {
          AssociationType associationType = new AssociationType(
            roleAttribute.RelationshipName, roleAttribute.RelationshipNamespaceName,
            roleAttribute.IsForeignKey, DataSpace.OSpace); 
          SessionData.TypesInLoading.Add(associationType.FullName, associationType);
          TrackClosure(roleAttribute.Role1Type); 
          TrackClosure(roleAttribute.Role2Type); 

          // prevent lifting of loop vars 
          string r1Name = roleAttribute.Role1Name;
          Type r1Type = roleAttribute.Role1Type;
          RelationshipMultiplicity r1Multiplicity = roleAttribute.Role1Multiplicity;
          AddTypeResolver(() => 
            ResolveAssociationEnd(associationType, r1Name, r1Type, r1Multiplicity));

          // prevent lifting of loop vars 
          string r2Name = roleAttribute.Role2Name;
          Type r2Type = roleAttribute.Role2Type; 
          RelationshipMultiplicity r2Multiplicity = roleAttribute.Role2Multiplicity;
          AddTypeResolver(() =>
            ResolveAssociationEnd(associationType, r2Name, r2Type, r2Multiplicity));

          // get assembly entry and add association type to the list of types in the assembly
          Debug.Assert(!CacheEntry.ContainsType(associationType.FullName), "Relationship type must not be present in the list of types"); 
          CacheEntry.TypesInAssembly.Add(associationType); 
        }
      } 
    }

    // ...
  }
}

В методе используется только словарь SessionData.TypesInLoading. Ключ представляет собой комбинацию RelationshipName и RelationshipNamespaceName. Самое смешное, что в комментарии сказано, что они проверяют наличие дубликатов ключей, но метод TryFindNullParametersInRelationshipAttribute проверяет только то, что свойства атрибута не являются nulls. Я полагаю, это ошибка.

Но более важно то, что описанная ошибка должна была возникнуть из-за того, что где-то в ваших сборках у вас есть более одного EdmRelationshipAttribute с одинаковыми комбинациями свойств RelationshipName и RelationshipNamespaceName. Могут быть разные причины:

  1. Неверный файл EDMX.
  2. Неправильно скопированные сборки,
  3. Конфигурация выпуска, выводящая результаты сборки в ту же папку, что и конфигурация отладки,
  4. Ошибки при ручном создании сборок сопоставления EF,
  5. и т. д.

К сожалению, уловить имя разорванных отношений будет очень сложно. Вероятно, вам придется установить точку останова на System.Data.Metadata.Edm.ObjectItemAttributeAssemblyLoader.LoadRelationshipTypes() и проследить ее при дизассемблировании. Я бы сказал, что это почти невозможно, потому что метод Dictionary.Insert будет вызываться много раз.

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

person Pavel Gatilov    schedule 18.04.2012
comment
спасибо за ваш ответ. Вчера вечером я узнал, что ExecuteStoreQuery вызывает исключение. не знаю почему. так что с edmx или сборками вообще нет проблем.. :/ - person Machine; 19.04.2012