Тестирование Entity Framework с помощью IRepository - проблема с отложенной загрузкой - PullRequest
3 голосов
/ 17 сентября 2010

Я выполняю рефакторинг проекта MVC, чтобы сделать его тестируемым.В настоящее время Контроллер использует объекты контекста Entity Framework напрямую для запроса необходимых данных.Я начал абстрагировать это, и это просто не работает.В конце концов у меня есть IService и абстракция IRepository, но для описания проблемы давайте просто посмотрим на IRepository.Многие советуют интерфейс с функциями, которые возвращают некоторые из них: IQueriable <...>, IEnumerable <...>, IList <...>, SomeEntityObject, SomeDTO .Затем, когда кто-то хочет протестировать уровень обслуживания, он может реализовать интерфейс с классом, который не отправляет их в базу данных, чтобы вернуть их.

Проблема: Использование linq для сущностей У меня есть lazy (deferred)загрузка в моем наборе инструментов.Это на самом деле очень полезно, потому что мои функции действия контроллера знают, какие данные им нужны для представления, и я не запрашивал больше, чем требовалось.Однако linq ни к чему не имеет ленивой загрузки .Поэтому, когда мои функции IRepository возвращают любую из вышеупомянутых вещей, я теряю ленивую загрузку.Я расширил свой интерфейс такими функциями, как «GetAnything» и «GetAnythingDeep» , но этого недостаточно: он должен быть гораздо более детализированным.В результате получается около 5-6 функций для одного и того же типа объекта, в зависимости от свойств, которые я хочу получить в результате.Возможно, это может быть общая функция с некоторым параметром «include properties», но мне это тоже не нравится.

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

Кстати, я думал о том, чтобы изменить источник данных, лежащий в основе модели сущностей, на xml или какой-либо объектный источник данных, и поэтому я мог сохранить связь с сущностями.Я обнаружил, что он не поддерживается "из коробки" ... что также печально: это означает, что структура сущностей означает источник базы данных - не очень полезная абстракция.

Конкретный пример:

Объекты сущностей: Статья, язык, личность.Отношения: Статья может иметь 1-N языков и одного человека (издатель).

Объект ViewModel: ArticleDeepViewModel: Содержит все свойства статьи, включая языки и имя человека (для просмотрастатья, поэтому не нужны другие свойства человека).

Действие контроллера, которое будет возвращать это представление, должно получить данные откуда-то.

Код до изменений:

    using (var context = new Entities.Articles())
        {
            var article = (from a in context.Articles.Include("Languages")
                       where a.ID == ID
                       select new ViewArticleViewModel()
                       {
                       ID = a.ID,
                       Headline = a.Headline,
                       Summary = a.Summary,
                       Body = a.Body,
                       CreatedBy = a.CreatedByEntity.Name,
                       CreatedDate = a.CreatedDate,
                       Languages = (from l in context.Languages select new ViewLanguagesViewModel() { ID = l.ID, Name = l.Name, Selected = a.Languages.Contains(l) })}).Single();
        this.ViewData.Model = article;
    }
    return View();

Код после изменений может выглядеть примерно так:

var article = ArticleService.GetArticleDeep(ID);
var viewModel = /* mapping */
this.ViewData.Model = viewModel;
return View();

Проблема заключается в том, что GetArticleDeep должен возвращать объект Article с включенными языками и всем объектом Person (он не должен знать, что модели представления требуется толькоИмя человека).Также у меня есть 3 разных модели для статьи.Например, если кто-то хочет просмотреть список статей , то нет необходимости получать языки, текст и некоторые другие свойства , однако может быть полезно получить Имя издателя (которое находится вглубоко).Перед «тестируемым» кодом действия контроллера могли бы просто содержать запрос linq to entity и получать любые необходимые данные, используя ленивую загрузку, функцию Включить , используя подзапросы, ссылаясь на внешние свойства (Publisher.Name) ... Итакнет ненужного запроса к базе данных и ненужных данных, передаваемых из базы данных.

Что должен обеспечить интерфейс IService или IRepository для получения 3-4 различных уровней объектов Article или иногда спискаэти объекты?

1 Ответ

0 голосов
/ 17 сентября 2010

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

http://blogs.msdn.com/b/alexj/archive/2009/07/25/tip-28-how-to-implement-include-strategies.aspx

Он, в основном, дает вам способ создать строго типизированную стратегию включения, такую ​​как:

var strategy = new IncludeStrategy<Article>();
strategy.Include(a => a.Author);

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

Вот пример метода репозитория, использующего приведенную выше стратегию включения:

public IQueryable<Article> Find(Expression<Func<Article, bool>> criteria, IncludeStrategy<Article> includes)
{
    var query = includes.ApplyTo(context.Articles).Where(criteria);
    return query;
}
...