Существует ли стандартный подход для динамического создания sql? - PullRequest
12 голосов
/ 09 сентября 2008

Я хочу спросить, как другие программисты создают строки динамического SQL для выполнения в качестве CommandText объекта SQLCommand.

Я создаю параметризованные запросы, содержащие сгенерированные пользователем предложения WHERE и поля SELECT. Иногда запросы являются сложными, и мне нужно много контролировать, как строятся различные части.

В настоящее время я использую множество циклов и операторов switch для создания необходимых фрагментов кода SQL и создания необходимых объектов параметров SQL. Этот метод сложен в использовании и делает техническое обслуживание настоящим делом.

Есть ли более чистый и стабильный способ сделать это?

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

EDIT: Чтобы добавить детали к моему предыдущему сообщению:

  1. Я не могу шаблонизировать свой запрос из-за требований. Это просто слишком сильно меняется.
  2. Я должен разрешить агрегатные функции, такие как Count (). Это имеет последствия для условия Group By / Have. Это также вызывает вложенные операторы SELECT. Это, в свою очередь, влияет на имя столбца, используемое
  3. Некоторые контактные данные хранятся в столбце XML. Пользователи могут запрашивать эти данные AS WELL AS и другие реляционные столбцы вместе. Это приводит к тому, что столбцы xml не могут появляться в предложениях Group By [синтаксис sql].
  4. Я использую эффективную технику подкачки, которая использует функцию SQL Row_Number (). В результате я должен использовать временную таблицу и затем получить @@ rowcount, прежде чем выбрать мое подмножество, чтобы избежать второго запроса.

Я покажу некоторый код (ужас!), Чтобы вы, ребята, имели представление о том, с чем я имею дело.

sqlCmd.CommandText = "DECLARE @t Table(ContactId int, ROWRANK int" + declare
      + ")INSERT INTO @t(ContactId, ROWRANK" + insertFields + ")"//Insert as few cols a possible
      + "Select ContactID, ROW_NUMBER() OVER (ORDER BY " + sortExpression + " "
      + sortDirection + ") as ROWRANK" // generates a rowrank for each row
      + outerFields
      + " FROM ( SELECT c.id AS ContactID"
      + coreFields
      + from         // sometimes different tables are required 
      + where + ") T " // user input goes here.
      + groupBy+ " "
      + havingClause //can be empty
      + ";"
      + "select @@rowcount as rCount;" // return 2 recordsets, avoids second query
      + " SELECT " + fields + ",field1,field2" // join onto the other cols n the table
      +" FROM @t t INNER JOIN contacts c on t.ContactID = c.id" 
      +" WHERE ROWRANK BETWEEN " + ((pageIndex * pageSize) + 1) + " AND " 
      + ( (pageIndex + 1) * pageSize); // here I select the pages I want

В этом примере я запрашиваю данные XML. Для чисто реляционных данных запрос гораздо проще. Каждая из переменных раздела является StringBuilders. Где пункты построены так:

// Add Parameter to SQL Command
AddParamToSQLCmd(sqlCmd, "@p" + z.ToString(), SqlDbType.VarChar, 50, ParameterDirection.Input, qc.FieldValue);
// Create SQL code Fragment
where.AppendFormat(" {0} {1} {2} @p{3}", qc.BooleanOperator, qc.FieldName, qc.ComparisonOperator, z);

Ответы [ 8 ]

2 голосов
/ 09 сентября 2008

Мы создали наш собственный объект FilterCriteria, который является своего рода black-box динамическим построителем запросов. Он имеет свойства коллекции для SelectClause, WhereClause, GroupByClause и OrderByClause. Он также содержит свойства для CommandText, CommandType и MaximumRecords.

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

Хорошо работает для нас ... и прекрасно поддерживает генерацию SQL в объекте.

2 голосов
/ 09 сентября 2008

Мне нужно было сделать это в одном из моих недавних проектов. Вот схема, которую я использую для генерации SQL:

  • Каждый компонент запроса представлен объектом (который в моем случае является сущностью Linq-to-Sql, которая отображается в таблицу в БД). Итак, у меня есть следующие классы: Query, SelectColumn, Join, WhereCondition, Sort, GroupBy. Каждый из этих классов содержит все детали, относящиеся к этому компоненту запроса.
  • Последние пять классов связаны с объектом Query. Таким образом, сам объект Query имеет коллекции каждого класса.
  • У каждого класса есть метод, который может генерировать SQL для части запроса, которую он представляет. Таким образом, создание общего запроса в итоге вызывает Query.GenerateQuery (), который, в свою очередь, перечисляет все вложенные коллекции и вызывает их соответствующие методы GenerateQuery ()

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

1 голос
/ 09 сентября 2008

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

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

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

where...
and (@MyParam5 is null or @MyParam5 = Col5)

тогда из кода гораздо проще установить значение параметра в DBNull.Value, когда оно неприменимо, чем изменять генерируемую строку SQL.

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

1 голос
/ 09 сентября 2008

Обычно это что-то вроде этого:

string query= "SELECT {0} FROM .... WHERE {1}"
StringBuilder selectclause = new StringBuilder();
StringBuilder wherecaluse = new StringBuilder();

// .... the logic here will vary greatly depending on what your system looks like

MySqlcommand.CommandText = String.Format(query, selectclause.ToString(), whereclause.ToString());

Я также только начинаю с ORM. Возможно, вы захотите взглянуть на один из них. ActiveRecord / Hibernate - это несколько хороших ключевых слов для Google.

1 голос
/ 09 сентября 2008

Гульзар и Райан Лансьо делают хорошие замечания, упоминая CodeSmith и ORM. Любой из них может уменьшить или устранить ваше текущее бремя, когда дело доходит до генерации динамического SQL. Ваш текущий подход к использованию параметризованного SQL мудр, просто потому что он хорошо защищает от атак SQL-инъекций.

Без фактического примера кода для комментирования трудно предоставить информированную альтернативу циклам и операторам switch, которые вы используете в данный момент. Но поскольку вы упоминаете, что устанавливаете свойство CommandText, я бы порекомендовал использовать string.Format в вашей реализации (если вы его еще не используете). Я думаю, что это может упростить реструктуризацию вашего кода и, следовательно, улучшить удобочитаемость и понимание.

1 голос
/ 09 сентября 2008

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

0 голосов
/ 09 сентября 2008

ORM s уже решили проблему генерации динамического SQL (я предпочитаю NHibernate / ActiveRecord ). Используя эти инструменты, вы можете создать запрос с неизвестным числом условий, выполняя циклический переход между пользовательским вводом и создавая массив объектов Expression. Затем выполните встроенные методы запроса с этим набором пользовательских выражений.

List<Expression> expressions = new List<Expression>(userConditions.Count);
foreach(Condition c in userConditions)
{
    expressions.Add(Expression.Eq(c.Field, c.Value));
}
SomeTable[] records = SomeTable.Find(expressions);

Существует больше опций 'Выражение': неравенство, больше / меньше чем, нуль / не ноль и т. Д. Тип «Условие», который я только что составил, вы, вероятно, можете поместить свой пользовательский ввод в полезный класс.

0 голосов
/ 09 сентября 2008

Из любопытства вы рассматривали возможность использования ORM для управления доступом к данным. Многие функции, которые вы пытаетесь реализовать, уже могут быть там. Это может быть что-то, на что можно смотреть, потому что лучше не изобретать велосипед.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...