SqlQueryBuilder
SqlQueryBuilder<T> es el punto de entrada principal para construir consultas SELECT completas. Es una clase sellada genérica — T es el tipo de entidad cuyas propiedades están disponibles como columnas tipadas.
var result = new SqlQueryBuilder<Order>(new SqlServerDialect())
.From("Orders", schema: "dbo")
.Select(x => x.Id, x => x.Total)
.Where(x => x.Total > 100)
.OrderBy(x => x.CreatedAt, ascending: false)
.Page(1, 20)
.Build();
SELECT
Select — múltiples columnas
Firma
SqlQueryBuilder<T> Select(params Expression<Func<T, object>>[] columns)
Descripción
Agrega una o más columnas tipadas a la lista SELECT. Si nunca se llama, la consulta utiliza SELECT * por defecto.
Ejemplo
builder.Select(x => x.Id, x => x.Name);
// → SELECT [Id], [Name] FROM ...
Select — columna única con alias
Firma
SqlQueryBuilder<T> Select(Expression<Func<T, object>> column, string? alias)
Descripción Agrega una columna tipada con alias opcional.
Ejemplo
builder.Select(x => x.Name, "UserName");
// → SELECT [Name] AS [UserName] FROM ...
SelectAll
Firma
SqlQueryBuilder<T> SelectAll()
Descripción
Restablece la lista de columnas a SELECT *, limpiando cualquier columna previamente agregada.
SelectRaw
Firma
SqlQueryBuilder<T> SelectRaw(string rawSql)
Descripción Agrega un fragmento SQL literal a la lista SELECT. Útil para expresiones que no se pueden representar mediante columnas tipadas.
Ejemplo
builder.SelectRaw("GETDATE() AS [Now]");
// → SELECT GETDATE() AS [Now] FROM ...
SelectRawIf
Firma
SqlQueryBuilder<T> SelectRawIf(bool condition, string rawSql)
Descripción
Agrega una expresión SELECT literal solo cuando condition es true.
SelectIf
Firma
SqlQueryBuilder<T> SelectIf(bool condition, Expression<Func<T, object>> column, string? alias = null)
Descripción
Agrega una columna tipada solo cuando condition es true.
SelectCount — estrella
Firma
SqlQueryBuilder<T> SelectCount(string? alias = null)
Descripción
Agrega COUNT(*) a la lista SELECT.
Ejemplo
builder.SelectCount("total");
// → SELECT COUNT(*) AS [total] FROM ...
SelectCount — columna
Firma
SqlQueryBuilder<T> SelectCount(Expression<Func<T, object>> column, string? alias = null)
Descripción
Agrega COUNT([col]) a la lista SELECT.
SelectCountDistinct
Firma
SqlQueryBuilder<T> SelectCountDistinct(Expression<Func<T, object>> column, string? alias = null)
Descripción
Agrega COUNT(DISTINCT [col]) a la lista SELECT.
SelectSum
Firma
SqlQueryBuilder<T> SelectSum(Expression<Func<T, object>> column, string? alias = null)
Descripción
Agrega SUM([col]) a la lista SELECT.
SelectAvg
Firma
SqlQueryBuilder<T> SelectAvg(Expression<Func<T, object>> column, string? alias = null)
Descripción
Agrega AVG([col]) a la lista SELECT.
SelectMin
Firma
SqlQueryBuilder<T> SelectMin(Expression<Func<T, object>> column, string? alias = null)
Descripción
Agrega MIN([col]) a la lista SELECT.
SelectMax
Firma
SqlQueryBuilder<T> SelectMax(Expression<Func<T, object>> column, string? alias = null)
Descripción
Agrega MAX([col]) a la lista SELECT.
SelectCase
Firma
SqlQueryBuilder<T> SelectCase(CaseWhenBuilder caseWhen)
Descripción
Agrega una expresión CASE WHEN ... THEN ... ELSE ... END a la lista SELECT, construida mediante CaseWhenBuilder.
Ejemplo
var caseExpr = new CaseWhenBuilder()
.When("[Status] = 1", "Activo")
.When("[Status] = 2", "Inactivo")
.Else("Desconocido")
.As("EstadoLabel");
builder.SelectCase(caseExpr);
// → SELECT CASE WHEN [Status] = 1 THEN 'Activo' WHEN [Status] = 2 THEN 'Inactivo' ELSE 'Desconocido' END AS [EstadoLabel]
SelectCoalesce
Firma
SqlQueryBuilder<T> SelectCoalesce(
Expression<Func<T, object>> column, string fallbackSql, string alias)
Descripción
Agrega COALESCE([col], fallbackSql) AS [alias] a la lista SELECT.
Ejemplo
builder.SelectCoalesce(x => x.Department, "'Sin Dept'", "Departamento");
// → SELECT COALESCE([Department], 'Sin Dept') AS [Departamento] FROM ...
SelectCast
Firma
SqlQueryBuilder<T> SelectCast(
Expression<Func<T, object>> column, string typeName, string alias)
Descripción
Agrega CAST([col] AS typeName) AS [alias] a la lista SELECT.
Ejemplo
builder.SelectCast(x => x.Age, "FLOAT", "EdadDecimal");
// → SELECT CAST([Age] AS FLOAT) AS [EdadDecimal] FROM ...
SelectConcat
Firma
SqlQueryBuilder<T> SelectConcat(string alias, params Expression<Func<T, object>>[] columns)
Descripción Agrega una expresión de concatenación de cadenas adaptada al dialecto. El operador varía por dialecto:
- SQL Server:
[col1] + [col2] - PostgreSQL / SQLite:
"col1" || "col2" - MySQL:
CONCAT(`col1`, `col2`)
Ejemplo
builder.SelectConcat("NombreCompleto", x => x.FirstName, x => x.LastName);
// SQL Server → SELECT [FirstName] + [LastName] AS [NombreCompleto]
// PostgreSQL → SELECT "FirstName" || "LastName" AS "NombreCompleto"
Funciones de Ventana
SelectRowNumber
Firma
SqlQueryBuilder<T> SelectRowNumber(
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>> orderBy,
bool ascending = true,
string? alias = "RowNum")
// Sobrecarga multi-partición
SqlQueryBuilder<T> SelectRowNumber(
IReadOnlyList<Expression<Func<T, object>>> partitionBy,
Expression<Func<T, object>> orderBy,
bool ascending = true,
string? alias = "RowNum")
Descripción
Agrega ROW_NUMBER() OVER (PARTITION BY [col] ORDER BY [col] ASC|DESC) AS [alias]. Pasa null como partitionBy para omitir la cláusula PARTITION BY.
Ejemplo
builder.SelectRowNumber(x => x.Department, x => x.Salary, ascending: false, alias: "RangoSalario");
// → SELECT ROW_NUMBER() OVER (PARTITION BY [Department] ORDER BY [Salary] DESC) AS [RangoSalario]
SelectRank
Firma
SqlQueryBuilder<T> SelectRank(
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>> orderBy,
bool ascending = true,
string? alias = "Rank")
Descripción
Agrega RANK() OVER (...). Mismo comportamiento de saltos que el RANK estándar de SQL.
SelectDenseRank
Firma
SqlQueryBuilder<T> SelectDenseRank(
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>> orderBy,
bool ascending = true,
string? alias = "DenseRank")
Descripción
Agrega DENSE_RANK() OVER (...). Sin saltos en los valores de ranking.
SelectLag
Firma
SqlQueryBuilder<T> SelectLag(
Expression<Func<T, object>> column,
int offset,
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>> orderBy,
bool ascending = true,
string? alias = null)
Descripción
Agrega LAG([col], offset) OVER ([PARTITION BY ...] ORDER BY ...). Recupera el valor de una fila anterior dentro de la partición.
Ejemplo
builder.SelectLag(x => x.Price, 1, null, x => x.CreatedAt, alias: "PrecioAnterior");
// → SELECT LAG([Price], 1) OVER (ORDER BY [CreatedAt] ASC) AS [PrecioAnterior]
SelectLead
Firma
SqlQueryBuilder<T> SelectLead(
Expression<Func<T, object>> column,
int offset,
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>> orderBy,
bool ascending = true,
string? alias = null)
Descripción
Agrega LEAD([col], offset) OVER (...). Recupera el valor de una fila siguiente dentro de la partición.
SelectFirstValue
Firma
SqlQueryBuilder<T> SelectFirstValue(
Expression<Func<T, object>> column,
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>> orderBy,
bool ascending = true,
string? alias = null)
Descripción
Agrega FIRST_VALUE([col]) OVER (...). Recupera el primer valor en el marco de ventana ordenado.
SelectLastValue
Firma
SqlQueryBuilder<T> SelectLastValue(
Expression<Func<T, object>> column,
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>> orderBy,
bool ascending = true,
string? alias = null)
Descripción
Agrega LAST_VALUE([col]) OVER (...). Recupera el último valor en el marco de ventana ordenado.
SelectSumOver
Firma
SqlQueryBuilder<T> SelectSumOver(
Expression<Func<T, object>> column,
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>>? orderBy,
bool ascending = true,
string? alias = null)
Descripción
Agrega SUM([col]) OVER ([PARTITION BY ...] [ORDER BY ...]). Útil para totales acumulados. El alias por defecto es Running{NombreColumna}.
Ejemplo
builder.SelectSumOver(x => x.Amount, x => x.CustomerId, x => x.CreatedAt, alias: "TotalAcumulado");
// → SELECT SUM([Amount]) OVER (PARTITION BY [CustomerId] ORDER BY [CreatedAt] ASC) AS [TotalAcumulado]
SelectAvgOver
Firma
SqlQueryBuilder<T> SelectAvgOver(
Expression<Func<T, object>> column,
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>>? orderBy,
bool ascending = true,
string? alias = null)
Descripción
Agrega AVG([col]) OVER (...).
SelectCountOver
Firma
SqlQueryBuilder<T> SelectCountOver(
Expression<Func<T, object>> column,
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>>? orderBy,
bool ascending = true,
string? alias = null)
Descripción
Agrega COUNT([col]) OVER (...).
SelectMinOver
Firma
SqlQueryBuilder<T> SelectMinOver(
Expression<Func<T, object>> column,
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>>? orderBy,
bool ascending = true,
string? alias = null)
Descripción
Agrega MIN([col]) OVER (...).
SelectMaxOver
Firma
SqlQueryBuilder<T> SelectMaxOver(
Expression<Func<T, object>> column,
Expression<Func<T, object>>? partitionBy,
Expression<Func<T, object>>? orderBy,
bool ascending = true,
string? alias = null)
Descripción
Agrega MAX([col]) OVER (...).
SelectWindowRaw
Firma
SqlQueryBuilder<T> SelectWindowRaw(string windowExpression, string alias)
Descripción Agrega una expresión de función de ventana literal al SELECT. Útil cuando las sobrecargas tipadas no cubren tu caso.
Ejemplo
builder.SelectWindowRaw("NTILE(4) OVER (ORDER BY [Score] DESC)", "Cuartil");
// → SELECT NTILE(4) OVER (ORDER BY [Score] DESC) AS [Cuartil]
FROM
From — nombre de tabla
Firma
SqlQueryBuilder<T> From(string tableName, string? schema = null)
Descripción
Establece la tabla FROM. Si no se llama, toma typeof(T).Name como valor por defecto. El esquema es opcional.
Ejemplo
builder.From("Users", schema: "dbo");
// SQL Server → FROM [dbo].[Users]
// PostgreSQL → FROM "dbo"."Users"
From — subconsulta
Firma
SqlQueryBuilder<T> From(SqlQueryResult subquery, string alias)
Descripción
Usa un SqlQueryResult precompilado como fuente FROM: FROM (subquery) alias.
Ejemplo
var inner = new SqlQueryBuilder<User>(dialect)
.From("Users")
.Where(x => x.IsActive)
.Build();
new SqlQueryBuilder<User>(dialect)
.From(inner, "activos")
.Select(x => x.Name)
.Build();
// → SELECT "Name" FROM (SELECT * FROM "Users" WHERE "IsActive" = true) activos
JOINs
Todos los métodos de join aceptan un nombre de tabla (entre comillas según el dialecto), un alias opcional y una condición ON como string literal.
InnerJoin
Firma
SqlQueryBuilder<T> InnerJoin(string table, string? alias, string on)
Ejemplo
builder.From("Users").InnerJoin("Orders", "o", "[Users].[Id] = [o].[UserId]");
// → FROM [Users] INNER JOIN [Orders] o ON [Users].[Id] = [o].[UserId]
LeftJoin
Firma
SqlQueryBuilder<T> LeftJoin(string table, string? alias, string on)
Descripción
Agrega una cláusula LEFT JOIN.
RightJoin
Firma
SqlQueryBuilder<T> RightJoin(string table, string? alias, string on)
Descripción
Agrega una cláusula RIGHT JOIN.
FullOuterJoin
Firma
SqlQueryBuilder<T> FullOuterJoin(string table, string? alias, string on)
Descripción
Agrega una cláusula FULL OUTER JOIN.
CrossJoin
Firma
SqlQueryBuilder<T> CrossJoin(string table, string? alias = null)
Descripción
Agrega una cláusula CROSS JOIN. No requiere condición ON.
InnerJoinSubquery
Firma
SqlQueryBuilder<T> InnerJoinSubquery(SqlQueryResult subquery, string alias, string on)
Descripción
Agrega un INNER JOIN contra una tabla derivada (subconsulta). Los parámetros de la subconsulta se renombran automáticamente para evitar colisiones.
Ejemplo
var sub = new SqlQueryBuilder<Order>(dialect)
.From("Orders")
.Select(x => x.UserId)
.SelectSum(x => x.Total, "TotalGastado")
.GroupBy(x => x.UserId)
.Build();
builder.From("Users").InnerJoinSubquery(sub, "totales", "[Users].[Id] = [totales].[UserId]");
// → FROM [Users] INNER JOIN (SELECT [UserId], SUM([Total]) AS [TotalGastado] FROM [Orders] GROUP BY [UserId]) [totales] ON [Users].[Id] = [totales].[UserId]
LeftJoinSubquery
Firma
SqlQueryBuilder<T> LeftJoinSubquery(SqlQueryResult subquery, string alias, string on)
Descripción
Agrega un LEFT JOIN contra una tabla derivada (subconsulta).
RightJoinSubquery
Firma
SqlQueryBuilder<T> RightJoinSubquery(SqlQueryResult subquery, string alias, string on)
Descripción
Agrega un RIGHT JOIN contra una tabla derivada (subconsulta).
FullOuterJoinSubquery
Firma
SqlQueryBuilder<T> FullOuterJoinSubquery(SqlQueryResult subquery, string alias, string on)
Descripción
Agrega un FULL OUTER JOIN contra una tabla derivada (subconsulta).
WHERE
Múltiples llamadas WHERE se combinan con AND. La sobrecarga de ValiFlow<T> / Expression y la de SqlWhereBuilder usan prefijos de nombre de parámetro diferentes (p vs pw) y pueden coexistir sin conflictos.
Where — ValiFlow
Firma
SqlQueryBuilder<T> Where(ValiFlow<T> filter)
Descripción
Establece la cláusula WHERE a partir de un filtro ValiFlow<T>. Los parámetros usan el prefijo p.
Ejemplo
var filter = new ValiFlow<User>().EqualTo(x => x.IsActive, true).GreaterThan(x => x.Age, 18);
builder.From("Users").Where(filter);
// → WHERE [IsActive] = @p0 AND [Age] > @p1
Where — Expression
Firma
SqlQueryBuilder<T> Where(Expression<Func<T, bool>> predicate)
Descripción
Establece la cláusula WHERE a partir de una expresión lambda. Los parámetros usan el prefijo p.
Nota: Llamar tanto a
Where(ValiFlow<T>)como aWhere(Expression<...>)no está soportado — la segunda llamada sobreescribe la primera. Usa solo un predicado tipado; combínalo conWhere(SqlWhereBuilder)para condiciones compuestas.
Where — SqlWhereBuilder (instancia)
Firma
SqlQueryBuilder<T> Where(SqlWhereBuilder<T> whereBuilder)
Descripción
Establece la cláusula WHERE a partir de un SqlWhereBuilder<T> preconfigurado. Los parámetros usan el prefijo pw. Puede combinarse con la sobrecarga de predicado tipado.
Where — SqlWhereBuilder (inline)
Firma
SqlQueryBuilder<T> Where(Action<SqlWhereBuilder<T>> configure)
Descripción
Configura la cláusula WHERE de forma inline usando una acción SqlWhereBuilder<T>.
Ejemplo
builder.Where(w => w.EqualTo(x => x.IsActive, true).GreaterThan(x => x.Age, 18));
// → WHERE [IsActive] = @pw0 AND [Age] > @pw1
WhereRaw
Firma
SqlQueryBuilder<T> WhereRaw(string rawSql)
Descripción Agrega un fragmento SQL literal a la cláusula WHERE (combinado con AND). Los parámetros en el fragmento son responsabilidad del caller.
Ejemplo
builder.WhereRaw("[CreatedAt] > DATEADD(day, -30, GETDATE())");
// → WHERE [CreatedAt] > DATEADD(day, -30, GETDATE())
WhereExists
Firma
SqlQueryBuilder<T> WhereExists(SqlQueryResult subquery)
Descripción
Agrega EXISTS (subquery) a la cláusula WHERE. Los parámetros de la subconsulta se fusionan y renombran automáticamente.
Ejemplo
var sub = new SqlQueryBuilder<Order>(dialect)
.From("Orders")
.WhereRaw("[Orders].[UserId] = [Users].[Id]")
.Build();
builder.From("Users").WhereExists(sub);
// → WHERE EXISTS (SELECT * FROM [Orders] WHERE [Orders].[UserId] = [Users].[Id])
WhereNotExists
Firma
SqlQueryBuilder<T> WhereNotExists(SqlQueryResult subquery)
Descripción
Agrega NOT EXISTS (subquery) a la cláusula WHERE.
WhereInSubquery
Firma
SqlQueryBuilder<T> WhereInSubquery<TValue>(Expression<Func<T, TValue>> column, SqlQueryResult subquery)
Descripción
Agrega [col] IN (subquery) a la cláusula WHERE. Los parámetros de la subconsulta se renombran automáticamente.
Ejemplo
var sub = new SqlQueryBuilder<Order>(dialect)
.From("Orders")
.Select(x => x.UserId)
.Build();
builder.From("Users").WhereInSubquery(x => x.Id, sub);
// → WHERE [Id] IN (SELECT [UserId] FROM [Orders])
WhereNotInSubquery
Firma
SqlQueryBuilder<T> WhereNotInSubquery<TValue>(Expression<Func<T, TValue>> column, SqlQueryResult subquery)
Descripción
Agrega [col] NOT IN (subquery) a la cláusula WHERE.
WhereIf — sobrecarga builder
Firma
SqlQueryBuilder<T> WhereIf(bool condition, Action<SqlWhereBuilder<T>> configure)
Descripción
Aplica una cláusula WHERE con SqlWhereBuilder solo cuando condition es true.
WhereIf — sobrecarga expression
Firma
SqlQueryBuilder<T> WhereIf(bool condition, Expression<Func<T, bool>> predicate)
Descripción
Aplica un predicado WHERE lambda solo cuando condition es true.
GROUP BY / HAVING
GroupBy
Firma
SqlQueryBuilder<T> GroupBy(params Expression<Func<T, object>>[] columns)
Descripción Agrega columnas GROUP BY.
Ejemplo
builder.GroupBy(x => x.Department, x => x.Status);
// → GROUP BY [Department], [Status]
GroupByRaw
Firma
SqlQueryBuilder<T> GroupByRaw(string rawSql)
Descripción Agrega un fragmento SQL literal a la cláusula GROUP BY. Útil para consultas multi-tabla donde se necesitan nombres de columna calificados con la tabla.
Ejemplo
builder.GroupByRaw("[Products].[Id], [Products].[Name]");
GroupByIf
Firma
SqlQueryBuilder<T> GroupByIf(bool condition, Expression<Func<T, object>> column)
Descripción
Agrega una columna GROUP BY solo cuando condition es true.
Having — string literal
Firma
SqlQueryBuilder<T> Having(string rawHavingSql)
Descripción
Establece una cláusula HAVING literal. Se usa después de GroupBy.
Ejemplo
builder.GroupBy(x => x.Department).Having("COUNT(*) > 5");
// → GROUP BY [Department] HAVING COUNT(*) > 5
Having — SqlHavingBuilder (instancia)
Firma
SqlQueryBuilder<T> Having(SqlHavingBuilder<T> havingBuilder)
Descripción
Establece la cláusula HAVING a partir de un SqlHavingBuilder<T>. Puede combinarse con la sobrecarga de string literal — ambas se unen con AND.
Having — SqlHavingBuilder (inline)
Firma
SqlQueryBuilder<T> Having(Action<SqlHavingBuilder<T>> configure)
Descripción Configura la cláusula HAVING de forma inline.
Ejemplo
builder.Having(h => h.CountGreaterThan(5).SumGreaterThan(x => x.Amount, 1000m));
HavingIf — sobrecarga builder
Firma
SqlQueryBuilder<T> HavingIf(bool condition, Action<SqlHavingBuilder<T>> configure)
Descripción
Aplica una cláusula HAVING fluida solo cuando condition es true.
HavingIf — sobrecarga string literal
Firma
SqlQueryBuilder<T> HavingIf(bool condition, string rawHavingSql)
Descripción
Aplica una cláusula HAVING literal solo cuando condition es true.
ORDER BY
OrderBy
Firma
SqlQueryBuilder<T> OrderBy(Expression<Func<T, object>> column, bool ascending = true)
Descripción Agrega una columna ORDER BY principal.
Ejemplo
builder.OrderBy(x => x.CreatedAt, ascending: false);
// → ORDER BY [CreatedAt] DESC
OrderBy — con NullsOrder
Firma
SqlQueryBuilder<T> OrderBy(Expression<Func<T, object>> column, bool ascending, NullsOrder nulls)
Descripción
Agrega ORDER BY con NULLS FIRST / NULLS LAST opcional. El parámetro nulls se ignora silenciosamente en dialectos que no lo soportan (SQL Server, MySQL).
Ejemplo
builder.OrderBy(x => x.DeletedAt, ascending: true, NullsOrder.Last);
// PostgreSQL → ORDER BY "DeletedAt" ASC NULLS LAST
// SQL Server → ORDER BY [DeletedAt] ASC (cláusula nulls omitida)
ThenBy
Firma
SqlQueryBuilder<T> ThenBy(Expression<Func<T, object>> column, bool ascending = true)
Descripción
Agrega una columna ORDER BY secundaria. Funcionalmente equivalente a una segunda llamada a OrderBy.
OrderByIf
Firma
SqlQueryBuilder<T> OrderByIf(bool condition, Expression<Func<T, object>> column, bool ascending = true)
Descripción
Aplica ORDER BY solo cuando condition es true.
OrderByRaw
Firma
SqlQueryBuilder<T> OrderByRaw(string rawSql)
Descripción Agrega una expresión ORDER BY literal textualmente. Útil para expresiones de ordenamiento multi-tabla o calculadas.
Ejemplo
builder.OrderByRaw("[Products].[Price] DESC, [Name] ASC");
Paginación
Take
Firma
SqlQueryBuilder<T> Take(int count)
Descripción
Limita el número de filas devueltas. Se mapea a TOP N (SQL Server sin OFFSET) o LIMIT N.
Ejemplo
builder.From("Users").Take(10);
// SQL Server → SELECT TOP 10 * FROM [Users]
// PostgreSQL → SELECT * FROM "Users" LIMIT 10
Skip
Firma
SqlQueryBuilder<T> Skip(int count)
Descripción Omite el número de filas especificado. Se mapea a OFFSET en todos los dialectos.
Ejemplo
builder.From("Users").OrderBy(x => x.Id).Skip(20).Take(10);
// SQL Server → SELECT * FROM [Users] ORDER BY [Id] ASC OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
// PostgreSQL → SELECT * FROM "Users" ORDER BY "Id" ASC LIMIT 10 OFFSET 20
Nota: SQL Server requiere una cláusula ORDER BY cuando se usa OFFSET.
Page
Firma
SqlQueryBuilder<T> Page(int pageNumber, int pageSize)
Descripción
Establece paginación basada en número de página (base 1). Page(2, 20) equivale a Skip(20).Take(20).
Ejemplo
builder.From("Users").OrderBy(x => x.Id).Page(3, 25);
// SQL Server → OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY
DISTINCT / WithHint
Distinct
Firma
SqlQueryBuilder<T> Distinct()
Descripción
Agrega DISTINCT a la cláusula SELECT.
Ejemplo
builder.From("Orders").Select(x => x.CustomerId).Distinct();
// → SELECT DISTINCT [CustomerId] FROM [Orders]
WithHint
Firma
SqlQueryBuilder<T> WithHint(string hint)
Descripción
Agrega un table hint después del FROM. Principalmente usado con SQL Server para hints como NOLOCK.
Ejemplo
builder.From("Users").WithHint("NOLOCK");
// SQL Server → FROM [Users] WITH (NOLOCK)
Nota: Este método genera el hint para todos los dialectos. Úsalo únicamente con SQL Server — otros dialectos incluirán un fragmento
WITH (...)inválido.
Operaciones de Conjunto
Union
Firma
SqlQueryBuilder<T> Union(SqlQueryResult other)
Descripción
Agrega un UNION (eliminando duplicados) con una consulta precompilada.
Ejemplo
builder.From("ActiveUsers").Union(
new SqlQueryBuilder<User>(dialect).From("InactiveUsers").Build());
// → SELECT * FROM [ActiveUsers] UNION SELECT * FROM [InactiveUsers]
UnionAll
Firma
SqlQueryBuilder<T> UnionAll(SqlQueryResult other)
Descripción
Agrega un UNION ALL (incluyendo duplicados) con una consulta precompilada.
Except
Firma
SqlQueryBuilder<T> Except(SqlQueryResult other)
Descripción
Agrega EXCEPT — retorna filas de la consulta principal que no están en la otra consulta.
Intersect
Firma
SqlQueryBuilder<T> Intersect(SqlQueryResult other)
Descripción
Agrega INTERSECT — retorna solo las filas que aparecen en ambas consultas.
CTEs
WithCte — SqlQueryResult
Firma
SqlQueryBuilder<T> WithCte(string name, SqlQueryResult cteQuery)
Descripción
Antepone una Expresión de Tabla Común: WITH nombre AS (cteQuery). Múltiples llamadas agregan múltiples CTEs.
Ejemplo
var activosCte = new SqlQueryBuilder<User>(dialect)
.From("Users")
.Where(w => w.EqualTo(x => x.IsActive, true))
.Build();
new SqlQueryBuilder<User>(dialect)
.WithCte("UsuariosActivos", activosCte)
.From("UsuariosActivos")
.Build();
// → WITH "UsuariosActivos" AS (SELECT * FROM "Users" WHERE ...) SELECT * FROM "UsuariosActivos"
WithCte — inline
Firma
SqlQueryBuilder<T> WithCte(string name, Action<SqlQueryBuilder<T>> configure)
Descripción
Antepone un CTE definido de forma inline. Crea un SqlQueryBuilder<T> anidado internamente.
WithRecursive
Firma
SqlQueryBuilder<T> WithRecursive(string name, SqlQueryResult anchor, SqlQueryResult recursive)
Descripción
Agrega un CTE recursivo. Genera: WITH [RECURSIVE] nombre AS (anchor UNION ALL recursive). La palabra clave RECURSIVE se agrega automáticamente para dialectos que la requieren (PostgreSQL, SQLite, MySQL).
Ejemplo
var anchor = new SqlQueryBuilder<Category>(dialect).From("Categories").Where(x => x.ParentId == null).Build();
var rec = new SqlQueryBuilder<Category>(dialect).From("Categories").WhereRaw("[Categories].[ParentId] = [arbol].[Id]").Build();
builder.WithRecursive("arbol", anchor, rec).From("arbol").Build();
// PostgreSQL → WITH RECURSIVE "arbol" AS (... UNION ALL ...) SELECT * FROM "arbol"
// SQL Server → WITH [arbol] AS (... UNION ALL ...) SELECT * FROM [arbol]
Bloqueo de Filas
ForUpdate
Firma
SqlQueryBuilder<T> ForUpdate()
Descripción
Agrega FOR UPDATE al final de la consulta para bloqueo exclusivo de filas. No hace nada en dialectos que no lo soportan (SQL Server — usa WithHint("UPDLOCK"); SQLite — ignorado silenciosamente).
ForShare
Firma
SqlQueryBuilder<T> ForShare()
Descripción
Agrega FOR SHARE al final de la consulta para bloqueo compartido de filas. No hace nada en dialectos que no lo soportan.
Tag / Build / ToPreviewSql
Tag — simple
Firma
SqlQueryBuilder<T> Tag(string description)
Descripción
Etiqueta la consulta. Cuando se llama Build(), la descripción se antepone como un comentario SQL (-- descripción). Útil para rastreo en logs.
Ejemplo
builder.From("Users").Tag("Obtener usuarios activos").Build();
// SQL: -- Obtener usuarios activos
// SELECT * FROM [Users]
Tag — con logger
Firma
SqlQueryBuilder<T> Tag(string description, Action<string>? logger)
Descripción
Igual que el anterior, pero también invoca logger con "[SQL] {descripción}" cuando se llama Build(). Si logger es null, el tag solo se incrusta como comentario SQL.
Ejemplo
builder.Tag("Obtener usuarios activos", msg => Console.WriteLine(msg));
// Salida en consola: [SQL] Obtener usuarios activos
ToPreviewSql
Firma
string ToPreviewSql()
Descripción Devuelve una vista previa del SQL en medio de la cadena fluida sin finalizar el constructor. Útil en la ventana de observación del depurador. Devuelve un mensaje alternativo si el estado del constructor está incompleto.
Build
Firma
SqlQueryResult Build()
Descripción
Ensambla y devuelve el SqlQueryResult final. Esta llamada es terminal — el constructor puede reutilizarse pero el resultado es inmutable.
Ejemplos avanzados
1) JOIN + WHERE + ORDER
var q = new SqlQueryBuilder<Order>()
.SelectAll()
.From("Orders o")
.Join("Users u", "o.UserId = u.Id")
.Where(o => o.Status == "Open")
.OrderByDesc(o => o.CreatedAt)
.Take(100);
2) GROUP BY + HAVING
var q = new SqlQueryBuilder<Order>()
.SelectCount()
.SelectRaw("o.CustomerId")
.From("Orders o")
.GroupByRaw("o.CustomerId")
.HavingRaw("COUNT(*) > 5");
3) CTE + Window
var q = new SqlQueryBuilder<Order>()
.WithCte("recent", "SELECT * FROM Orders WHERE CreatedAt > @p0")
.SelectRaw("Id, ROW_NUMBER() OVER (ORDER BY CreatedAt DESC) AS rn")
.From("recent");