2008-09-09 14 views
11

Quiero preguntar cómo otros programadores están produciendo cadenas de SQL dinámico para la ejecución como el Texto de comando de un objeto SQLCommand.¿Existe un enfoque estándar para generar SQL dinámicamente?

Estoy produciendo consultas parametrizadas que contienen cláusulas "WHERE" generadas por el usuario y campos SELECT. Algunas veces las consultas son complejas y necesito mucho control sobre cómo se construyen las diferentes partes.

Actualmente, estoy usando muchos bucles y declaraciones de interruptor para producir los fragmentos de código SQL necesarios y para crear los parámetros de SQL necesarios. Este método es difícil de seguir y hace que el mantenimiento sea una tarea ardua.

¿Hay una manera más limpia y más estable de hacer esto?

¿Alguna sugerencia?

EDIT: para añadir detalles a mi post anterior:

1. No puedo realmente la plantilla de mi consulta debido a los requisitos. Simplemente cambia demasiado.

  1. Tengo que permitir funciones agregadas, como Count(). Esto tiene consecuencias para la cláusula Group By/Having. también causa sentencias SELECT anidadas. Esto a su vez afecta el nombre de columna utilizado por
  2. Algunos datos de contacto se almacenan en una columna XML. Los usuarios pueden consultar estos datos ASÍ COMO AS y las otras columnas relacionales juntas. Las consecuencias son que las columnas xml no pueden aparecer en cláusulas Group By [sintaxis sql].
  3. Estoy utilizando una técnica de paginación eficiente que utiliza Row_Number() Función de SQL. Las consecuencias son que tengo que usar una tabla Temp y luego obtener @@ rowcount, antes de seleccionar mi subconjunto, para evitar una segunda consulta.

voy a mostrar algo de código (el horror!) Para que ustedes tienen una idea de lo que estoy tratando.

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 



En este ejemplo. Estaría consultando datos XML. Para datos puramente relacionales, la consulta es mucho más simple. Cada una de las variables de sección son StringBuilders. Donde se construyen las cláusulas de este modo:

//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); 
+0

Puede que realmente no tenga una opción aquí, pero conserve en mis bases de datos como Oracle hará un análisis exhaustivo de estas sentencias SQL cada vez que las encuentre, lo que ralentizará el procesamiento. Peor aún, empujan los planes explicados en caché de la pila impactando a otros usuarios de la misma manera. – ScottCher

Respuesta

2

Tuve la necesidad de hacer esto en uno de mis proyectos recientes. Aquí es el esquema que estoy usando para generar el SQL:

  • Cada componente de la consulta está representada por un objeto (que en mi caso es una entidad de LINQ to SQL que se asigna a una tabla en la base de datos) Así que tengo las siguientes clases: Query, SelectColumn, Join, WhereCondition, Sort, GroupBy.Cada una de estas clases contiene todos los detalles relacionados con ese componente de la consulta.
  • Las últimas cinco clases están todas relacionadas con un objeto Query. Entonces, el objeto Query tiene colecciones de cada clase.
  • Cada clase tiene un método que puede generar el SQL para la parte de la consulta que representa. Por lo tanto la creación de la consulta general termina llamando Query.GenerateQuery() que a su vez enumera a través de todos los sub-colecciones y pide sus respectivos generateQuery() métodos

Todavía es un poco complicado, pero al final se saber dónde se origina la generación de SQL para cada parte individual de la consulta (y no creo que haya ninguna gran declaración de cambio). Y no te olvides de usar StringBuilder.

0

Por curiosidad, ¿ha considerado el uso de un ORM para la gestión de sus datos de acceso. Mucha de la funcionalidad que estás tratando de implementar ya podría estar allí. Puede ser algo para mirar porque es mejor no reinventar la rueda.

1

Puede probar el enfoque utilizado por las herramientas de generación de código como CodeSmith. Crea una plantilla SQL con marcadores de posición. En tiempo de ejecución, lea la plantilla en una cadena y sustituya los marcadores de posición con valores reales. Esto solo es útil si todo el código SQL sigue un patrón.

1

Gulzar y Ryan Lanciaux son buenos puntos al mencionar CodeSmith y ORM. Cualquiera de estos puede reducir o eliminar su carga actual cuando se trata de generar SQL dinámico. Su enfoque actual de usar SQL parametrizado es inteligente, simplemente porque protege bien contra los ataques de inyección SQL.

Sin un ejemplo de código real para comentar, es difícil proporcionar una alternativa informada a los bucles y las instrucciones de cambio que está utilizando actualmente. Pero como mencionas que estás configurando una propiedad CommandText, te recomendaría el uso de string.Format en tu implementación (si aún no la estás usando). Creo que puede hacer que su código sea más fácil de reestructurar y, por lo tanto, mejore la legibilidad y la comprensión.

1

por lo general es algo como esto:

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()); 

Estoy también acaba de empezar con ORM. Es posible que desee echar un vistazo a uno de esos. ActiveRecord/Hibernate son algunas buenas palabras clave para google.

0

ORM s ya han resuelto el problema de la generación de SQL dinámico (prefiero NHibernate/ActiveRecord). Al usar estas herramientas, puede crear una consulta con un número desconocido de condiciones al recorrer la entrada del usuario y generar una matriz de objetos de Expresión. A continuación, ejecute los métodos de consulta incorporados con ese conjunto de expresiones personalizadas.

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); 

Hay más opciones 'expresión': no ​​igualdad, mayor/menor que, nulo/nulo no, etc. El tipo 'condición' acabo de componer, es probable que pueda rellenar su entrada del usuario en una clase útil.

1

Si realmente necesita hacer esto desde el código, entonces un ORM es probablemente el camino a seguir para tratar de mantenerlo limpio.

Pero me gustaría ofrecer una alternativa que funcione bien y pueda evitar los problemas de rendimiento que acompañan a las consultas dinámicas, debido al cambio de SQL que requiere la creación de nuevos planes de consulta, con diferentes demandas en los índices.

crear un procedimiento almacenado que acepta todos los parámetros posibles, y luego usar algo como esto en la cláusula where:

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

a continuación, a partir del código, que es mucho más simple para establecer el valor del parámetro a DBNull.Value cuando no es aplicable, en lugar de cambiar la cadena SQL que generas.

Sus DBA estarán mucho más contentos con usted, porque tendrán un lugar donde ir para la optimización de consultas, el SQL será fácil de leer y no tendrán que buscar entre los rastreos de los perfiles para encontrar las diferentes consultas. siendo generado por tu código.

2

Creamos nuestro propio objeto FilterCriteria que es una especie de black-box constructor de consultas dinámicas. Tiene propiedades de colección para SelectClause, WhereClause, GroupByClause y OrderByClause. También contiene propiedades para CommandText, CommandType y MaximumRecords.

Luego pasamos nuestro objeto FilterCriteria a nuestra lógica de datos y lo ejecuta contra el servidor de la base de datos y pasa los valores de los parámetros a un procedimiento almacenado que ejecuta el código dinámico.

Funciona bien para nosotros ... y mantiene la generación de SQL muy bien contenida en un objeto.

Cuestiones relacionadas