Entity Framework 4 — где разместить логику ApplyCurrentValues?

Я использую заглушку "метод" для обновления моих POCO (используется в отсоединенном контексте, ASP.NET MVC).

Это код, который у меня сейчас есть в моем контроллере (который работает):

[HttpPost]
public ActionResult Edit(Review review)
{
   Review originalReview = _userContentService.FindById(review.PostId) as Review;
   var ctx = _unitOfWork as MySqlServerObjectContext;
   ctx.ApplyCurrentValues("MyEntities.Posts", review);
   _unitOfWork.Commit();
   // ..snip - MVC stuff..
}

Как видите, запах кода везде. :)

Несколько моментов:

  1. Я использую внедрение зависимостей (на основе интерфейса) практически для всего.
  2. Я использую шаблон Unit of Work для абстрагирования ObjectContext и обеспечения сохранения в нескольких репозиториях.
  3. В настоящее время мой интерфейс IUnitOfWork имеет только 1 метод: void Commit();
  4. Контроллер имеет IUserContentService и IUnitOfWork впрыск через DI
  5. IUserContentService вызывает Find в репозиториях, которые используют ObjectContext.

Вот две вещи, которые мне не нравятся в приведенном выше коде:

  1. Я не хочу преобразовывать IUnitOfWork как MySqlServerObjectContext.
  2. Я не хочу, чтобы Контролеру приходилось заботиться о ApplyCurrentValues

Я в основном хочу, чтобы мой код выглядел так:

[HttpPost]
public ActionResult Edit(Review review)
{
   _userContentService.Update(review);
   _unitOfWork.Commit();
   // ..snip - MVC stuff..
}

Любые идеи, как я могу это сделать? (или что-то подобное).

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

Но мне интересно, где лучше всего поставить ApplyCurrentValues? Кажется нецелесообразным размещать его в интерфейсе IUnitOfWork, так как это проблема постоянства (EF). По той же причине ему не место в Сервисе. Если я помещу его в свой класс MySqlServerObjectContext (имеет смысл), откуда я буду вызывать это, поскольку ничто не имеет прямого доступа к этому классу - он вводится через DI, когда что-то запрашивает IUnitOfWork.

Есть предположения?

ИЗМЕНИТЬ

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

Что имеет смысл, хотя я не уверен, как это решить?

Нужно ли мне «проверить, прикреплен ли объект уже, если нет, прикрепить его?»

Может ли кто-нибудь из экспертов по EF4 помочь?

ИЗМЕНИТЬ

Неважно - нашел решение, см. ответ ниже.


person RPM1984    schedule 19.11.2010    source источник


Ответы (1)


Разобрался - это было непросто, поэтому постараюсь объяснить, как смогу. (для тех, кому не все равно)

Соответствующий код контроллера:

// _userContentService is IUserContentService
_userContentService.Update(review);

Итак, мой контроллер вызывает метод с именем Update для IUserContentService, проходя через строго типизированный объект Review.

Соответствующий код службы пользовательского контента

public void Update(Post post)
{
   // _userContentRepository is IPostRepository
   _userContentRepository.UpdateModel(post);
}

Итак, моя служба вызывает метод с именем UpdateModel для IPostRepository, проходя через строго типизированный объект Review.

Теперь, вот сложная часть.

На самом деле у меня нет конкретных репозиториев. У меня есть общий репозиторий под названием GenericRepository<T> : IRepository<T>, который обрабатывает все различные репозитории.

Поэтому, когда что-то запрашивает IPostRepository (что делала моя служба), DI давал ему GenericRepository<Post>.

Но теперь я ставлю ему PostRepository:

public class PostRepository : GenericRepository<Post>, IPostRepository
{
   public void UpdateModel(Post post)
   {
      var originalPost = CurrentEntitySet.SingleOrDefault(p => p.PostId == post.PostId);
      Context.ApplyCurrentValues(GetEntityName<Post>(), post);
   }
}

А поскольку класс наследуется от GenericRepository, он наследует всю основную логику репозитория (найти, добавить и т. д.).

Сначала я попытался поместить этот код UpdateModel в сам класс GenericRepository (и тогда мне не понадобился бы этот конкретный репозиторий), но проблема заключается в логике извлечение существующего объекта основано на определенном ключе объекта, о котором GenericRepository<T> не знает.

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

ИЗМЕНИТЬ

Этот «метод заглушки» также работает:

public void UpdateModel(Post post)
{
   var stub = new Review {PostId = post.PostId};
   CurrentEntitySet.Attach(stub);
   Context.ApplyCurrentValues(GetEntityName<Post>(), post);
}

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

ИЗМЕНИТЬ 2 (ПОСЛЕДНИЙ РАЗ)

Хорошо, у вас есть «метод заглушки», работающий с абстрактными классами, так что теперь проблема параллелизма решена.

Я добавил параметр универсального типа в свой метод UpdateModel и специальный параметр ограничение new().

Реализация:

public void UpdateModel<T>(T post) where T : Post, new()
{
   var stub = new T { PostId = post.PostId };
   CurrentEntitySet.Attach(stub);
   Context.ApplyCurrentValues(GetEntityName<Post>, post);
}

Интерфейс:

void UpdateModel<T>(T post) where T : Post, new();

Это избавляет меня от необходимости определять тип T вручную, предотвращает проблемы параллелизма, а также предотвращает дополнительное обращение к БД.

Довольно заводной.

ИЗМЕНИТЬ 3 (я думал, что последний раз был последним)

Приведенный выше «метод заглушки» работает, но если я извлеку объект заранее, он выдаст исключение, указывающее, что сущность с этим ключом уже существует в OSM.

Кто-нибудь может подсказать, как с этим справиться?

РЕДАКТИРОВАТЬ 4 (Хорошо, вот оно!)

Я нашел решение благодаря этому ответу SO: to-a-data-context-in-ent/1716109#1716109">Можно ли проверить, присоединен ли объект к контексту данных в Entity Framework?

Я пытался «проверить, прикреплен ли объект», используя следующий код:

ObjectStateEntry entry;
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry);

Но он всегда возвращал null, даже когда я исследовал OSM, я мог видеть там свою сущность с тем же ключом.

Но этот код работает:

CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), entity), out entry)

Может быть, из-за того, что я использую Pure POCO, у OSM возникли проблемы с определением ключа объекта, кто знает.

Да, и еще одну вещь, которую я добавил — чтобы мне не нужно было добавлять отдельный репозиторий для каждой сущности, я создал атрибут с именем «[EntityKey]» (атрибут общедоступного свойства).

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

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

Да, он использует отражение, но это умное отражение (на основе атрибутов), и я уже использую отражение для плюрализации имен наборов сущностей из T.

В любом случае, проблема решена - теперь все работает нормально!

person RPM1984    schedule 19.11.2010
comment
Выглядит интересно, хорошо, что вы опубликовали такое подробное описание своего решения. Однако есть одна проблема: Attach не генерирует исключение, если Post с тем же PostId уже прикреплен? Например. если вы выполняете GetAll() для того же контекста в какой-то момент ранее, и все сообщения уже прикреплены? - person Yakimych; 19.11.2010
comment
Хм, это хороший момент - я проверю это, сделав именно это (когда я вернусь в офис в понедельник). Если это так, мне, возможно, придется проверить, уже ли прикреплен объект, хотя я не совсем уверен, как это сделать. (особенно с тех пор, как я использую чистые pocos) - person RPM1984; 20.11.2010
comment
@Yakimych - блин, твое право. Если я извлеку этот объект заранее, он выдаст исключение. Любые идеи? Есть ли способ проверить, прикреплена ли уже сущность к графу? Возможно, мне придется задать еще один вопрос. - person RPM1984; 22.11.2010
comment
Мы используем CurrentContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry);, как и в другом вопросе SO, который вы нашли. Вроде работает нормально, но мы используем POCO с прокси. Хороший пост, в любом случае. Голосуйте за! - person Yakimych; 22.11.2010
comment
Да, у меня это не сработало - мы используем чистые POCO. Я мог видеть там сущность с тем же ключом сущности, но он не извлекал ее. Все равно теперь все в порядке. Теперь я продолжаю, пока не столкнусь со следующей проблемой. :) - person RPM1984; 22.11.2010