EF 4.0 Метод расширения IsAttachedTo и ошибка Объект с таким же ключом уже существует - PullRequest
0 голосов
/ 11 июля 2011

Я получил ошибку

Объект с таким же ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с одинаковыми ключ.

После того, как я погуглил, я нашел IsAttachedTo метод расширения:

Можно ли проверить, подключен ли объект к контексту данных в Entity Framework?

вот мой код:

foreach (string s in types)
   {
    Subscription subscription = new Subscription { Id = Int32.Parse(s) };

    if (service.repository._context.IsAttachedTo(subscription))
        service.repository._context.Detach(subscription);

    service.repository._context.AttachTo("Subscriptions", subscription); //error here
    horse.Subscriptions.Add(subscription);
    }

Но когда подписка с тем же ключом появилась в цикле foreach, метод расширения IsAttachedTo, возвращающий каждый раз false, не обнаруживает, что такая сущность уже присоединена. И в результате я получаю ту же ошибку:

Объект с таким же ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с одинаковыми ключ.

Почему это может быть?

Что я могу сделать, чтобы это исправить?

Ответы [ 2 ]

3 голосов
/ 11 июля 2011

У меня есть небольшой обзор кода для вас, потому что ваш пример кода пугает меня.

Вы, наверное, много читали о модных моделях проектирования и многоуровневых архитектурах, и вы начали использовать их сами. К сожалению, вы пропустили главное. Что это за чертовщина?

service.repository._context.XXX

Почему вы беспокоитесь о каком-либо слое обслуживания или уровне хранилища, если они не инкапсулируют свою логику? Почему вы выставляете репозиторий на сервис? Никто не должен знать о внутренней реализации сервиса? Еще хуже, почему вы выставляете контекст в хранилище? Это испортило весь смысл хранилища!

Существует множество поддерживающих правил для написания высококачественного объектно-ориентированного кода. Одно из этих правил называется Закон Деметры . Вам не нужно следовать каждому правилу, вам также не нужно все время следовать правилам, но в случае многоуровневой архитектуры этот закон является обязательным.

Если у вас есть три уровня A -> B -> C, уровень A может вызывать методы на уровне B, но он не знает о C и не может достичь своих методов. Если это возможно, это не новый уровень, но это тот же уровень, что и B, и уровень A не должен вызывать его через B, он может вызывать его напрямую.

В вашем примере вы только что выставили D на A, потому что A - текущий слой, B - service, C - repository и D - context.

Еще один момент о вашем коде. Есть хорошо известные соглашения об именах. Эти соглашения касаются не того, что мне нравится, а вам это нравится, а того факта, что используемая вами среда строго следует этим соглашениям, поэтому использование другого соглашения для смешивания соглашения об именах с соглашением об именах платформы сделает ваш код грязным.

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


Теперь к вашей настоящей проблеме. Метод, на который вы ссылались из соответствующего вопроса, не будет работать в вашем случае. Я думаю, что это будет работать, только если вы загрузите подписку из базы данных. Причина в том, что указанный метод использует EntityKey (внутренне или напрямую) для получения объекта из контекста, но у вашего нового объекта еще нет ключа объекта. Я ожидаю, что вызов TryGetObjectStateEntry для вашей сущности всегда вернет Detached. Ключ сущности создается во время присоединения или вам нужно создать его вручную.

Если вы хотите какой-нибудь IsAttachedTo метод, попробуйте это:

public bool IsAttachedTo<T>(this ObjectContext context, T entity) where T : IEntity
{
    return context.GetObjectStateEntries(~EntityState.Detached)
                  .Where(e => !e.IsRelationship)
                  .Select(e => e.Entity)
                  .OfType<T>()
                  .Any(e => e.Id == entity.Id);
}

И убедитесь, что ваша сущность реализует вспомогательный интерфейс

public interface IEntity
{
    int Id { get; } 
} 

Но для того, чтобы отсоединить подключенную сущность, вам понадобится:

public T GetAttached<T>(this ObjectContext context, T entity) where T : IEntity
{
    return context.GetObjectStateEntries(~EntityState.Detached)
                  .Where(e => !e.IsRelationship)
                  .Select(e => e.Entity)
                  .OfType<T>()
                  .SingleOrDefault(e => e.Id == entity.Id);
}

Вам придется отсоединить экземпляр, возвращенный этим методом.

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

1 голос
/ 11 июля 2011

Вероятно, что IsAttachedTo сравнивается не по ключу (Id), а по идентификатору объекта.Поскольку вы создаете новый Subscription для каждого элемента в цикле, все объекты являются разными экземплярами.

Поскольку в вашей коллекции types у вас, похоже, есть объекты с одинаковым Id, но в конце концов вы хотите толькодобавив один объект на ключ в контекст, вы, возможно, можете упростить свою жизнь, отфильтровав дубликаты:

var distinctTypes = types.Distinct();
foreach (string s in distinctTypes)
{
    Subscription subscription = new Subscription { Id = Int32.Parse(s) };

    service.repository._context.AttachTo("Subscriptions", subscription);
    horse.Subscriptions.Add(subscription);
}

Таким образом, для каждого ключа должен быть только один объект, привязанный кконтекст.

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