Разработка системы запросов, удобной для OO и модульного тестирования

StackOverflow https://stackoverflow.com/questions/1430634

Вопрос

Я работаю над приложением, которое позволяет стоматологам собирать информацию об определенных клинических мероприятиях.Хотя приложение не обладает высокой степенью настройки (нет пользовательских рабочих процессов или форм), оно предлагает некоторые элементарные возможности настройки;клиенты могут дополнить предопределенные поля формы своими собственными пользовательскими.Существует около полудюжины различных типов полей, которые могут создавать администраторы (т.е.Текст, Дата, Числовое значение, выпадающий список и т.д.).Мы используем Entity-Attribute-Value (EAV) на стороне сохранения для моделирования этой функциональности.

Одной из других ключевых особенностей приложения является возможность создавать пользовательские запросы к этим пользовательским полям.Это достигается с помощью пользовательского интерфейса, в котором любое количество правил (дата <= (Сейчас - 5 дней), текст типа '444', выпадающий == 'Отделение интенсивной терапии') может быть создан.Все правила объединяются для создания запроса.

Текущая реализация (которую я "унаследовал") не является ни объектно-ориентированной, ни поддающейся модульному тестированию.По сути, существует единый класс "God", который компилирует все бесчисленные типы правил непосредственно в сложный динамический оператор SQL (т.е.внутренние соединения, внешние соединения и подвыборки).Такой подход вызывает проблемы по нескольким причинам:

  • Модульное тестирование отдельных правил изолированно практически невозможно
  • Этот последний пункт также означает добавление дополнительных типов правил в будущее, безусловно, будет нарушать принцип открытого закрытия.
  • Проблемы бизнес-логики и настойчивости переплетаются.
  • Медленно выполняемые модульные тесты, поскольку требуется реальная база данных (SQLLite не может анализировать T-SQL, и подделать синтаксический анализатор было бы ухх ... сложно)

Я пытаюсь придумать альтернативный дизайн, который был бы гибким, ремонтопригодным и тестируемым, сохраняя при этом довольно быструю производительность запросов.Этот последний момент является ключевым, поскольку я полагаю, что реализация на основе OOAD переместит по крайней мере часть логики фильтрации данных с сервера базы данных на сервер приложений (.NET).

Я рассматриваю комбинацию моделей командования и цепочки ответственности:

Класс Query содержит коллекцию абстрактных классов Правил (DateRule, TextRule и т.д.).и содержит ссылку на класс DataSet, который содержит нефильтрованный набор данных.Набор данных моделируется независимо от постоянства (т. е. нет ссылок или привязок к типам баз данных).

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

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

Одно заключительное замечание:руководство не разрешает использовать NHibernate.Linq to SQL может быть возможен, но я не уверен, насколько применима эта технология к поставленной задаче.

Большое спасибо, и я с нетерпением жду отзывов каждого!

Обновить:Все еще ищу решение по этому поводу.

Это было полезно?

Решение

Я думаю, что LINQ to SQL был бы идеальным решением в сочетании, возможно, с динамическим LINQ из образцов VS2008.Используя LINQ, особенно с методами расширения в IEnumerable / IQueryable, вы можете создавать свои запросы, используя свою стандартную и пользовательскую логику, в зависимости от получаемых входных данных.Я активно использую эту технику для реализации фильтров во многих моих действиях MVC с большим эффектом.Поскольку он фактически создает дерево выражений, а затем использует его для генерации SQL в точке, где запрос должен быть материализован, я думаю, что это было бы идеально для вашего сценария, поскольку большая часть тяжелой работы по-прежнему выполняется SQL server.В случаях, когда LINQ генерирует неоптимальные запросы, вы всегда можете использовать табличные функции или хранимые процедуры, добавленные в ваш контекст данных LINQ, в качестве методов, позволяющих воспользоваться преимуществами оптимизированных запросов.

Обновленный:Вы также можете попробовать использовать Предикатный конструктор из C # 3.0 в двух словах.

Пример:найдите все Книги, в названии которых содержится одно из условий поиска, а издателем является О'Рейли.

 var predicate = PredicateBuilder.True<Book>();
 predicate = predicate.And( b => b.Publisher == "O'Reilly" );
 var titlePredicate = PredicateBuilder.False<Book>();
 foreach (var term in searchTerms)
 {
     titlePredicate = titlePredicate.Or( b => b.Title.Contains( term ) );
 }
 predicate = predicate.And( titlePredicate );

 var books = dc.Book.Where( predicate );

Другие советы

Я видел, как это делается, создавая объекты, которые моделируют каждое из условий, из которых вы хотите, чтобы пользователь строил свой запрос, и создавая дерево объектов, используя их.

Из дерева объектов вы должны быть в состоянии рекурсивно создать инструкцию SQL, которая удовлетворяет запросу.

Основными из них, которые вам понадобятся, будут объекты AND и OR, а также объекты для сравнения моделей, такие как EQUALS, LESSTHAN и т.д.Вероятно, вы захотите использовать интерфейс для этих объектов, чтобы упростить объединение их вместе различными способами.

Тривиальный пример:

public interface IQueryItem
{
    public String GenerateSQL();
}


public class AndQueryItem : IQueryItem
{
    private IQueryItem _FirstItem;
    private IQueryItem _SecondItem;

    // Properties and the like

    public String GenerateSQL()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(_FirstItem.GenerateSQL());
        builder.Append(" AND ");
        builder.Append(_SecondItem.GenerateSQL());

        return builder.ToString();
    }
}

Реализация этого таким образом должна позволить вам довольно легко выполнять модульное тестирование правил.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top