Entity Framework 4 - Где разместить логику «ApplyCurrentValues»? - PullRequest
6 голосов
/ 19 ноября 2010

Я использую " метод заглушки " для обновления моего 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. Я использую Dependency Injection (на основе интерфейса) практически для всего
  2. Я использую шаблон единиц работы, чтобы абстрагировать 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.

Есть мысли?

EDIT

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

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

Нужно ли мне "проверять, подключен ли объект, если нет, присоединить его?"

Могут ли помочь эксперты EF4?

EDIT

Nevermind - нашел решение, см. Ответ ниже.

1 Ответ

8 голосов
/ 19 ноября 2010

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

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

// _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 (что делал мой сервис)Я бы дал ему 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> не знал бы.

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

EDIT

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

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 ответу: Является ли яМожно ли проверить, подключен ли объект к контексту данных в Entity Framework?

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

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

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

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

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

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

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

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

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

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

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

...