Разработка системы запросов OO и модульных тестов - PullRequest
2 голосов
/ 16 сентября 2009

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

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

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

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

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

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

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

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

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

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

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

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

Ответы [ 2 ]

1 голос
/ 16 сентября 2009

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

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

Пример: найти все книги, где в названии содержится один из набора поисковых терминов, а издатель - O'Reilly.

 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 );
0 голосов
/ 16 сентября 2009

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

Из дерева объектов вы сможете рекурсивно создать 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();
    }
}

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

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

...