Cinco años tarde a la fiesta.
Se menciona en los enlaces proporcionados de la respuesta aceptada, pero creo que merece una respuesta explícita en SO, generando dinámicamente la consulta en función de los parámetros proporcionados. P.ej:
Preparar
-- drop table Person
create table Person
(
PersonId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Person PRIMARY KEY,
FirstName NVARCHAR(64) NOT NULL,
LastName NVARCHAR(64) NOT NULL,
Title NVARCHAR(64) NULL
)
GO
INSERT INTO Person (FirstName, LastName, Title)
VALUES ('Dick', 'Ormsby', 'Mr'), ('Serena', 'Kroeger', 'Ms'),
('Marina', 'Losoya', 'Mrs'), ('Shakita', 'Grate', 'Ms'),
('Bethann', 'Zellner', 'Ms'), ('Dexter', 'Shaw', 'Mr'),
('Zona', 'Halligan', 'Ms'), ('Fiona', 'Cassity', 'Ms'),
('Sherron', 'Janowski', 'Ms'), ('Melinda', 'Cormier', 'Ms')
GO
Procedimiento
ALTER PROCEDURE spDoSearch
@FirstName varchar(64) = null,
@LastName varchar(64) = null,
@Title varchar(64) = null,
@TopCount INT = 100
AS
BEGIN
DECLARE @SQL NVARCHAR(4000) = '
SELECT TOP ' + CAST(@TopCount AS VARCHAR) + ' *
FROM Person
WHERE 1 = 1'
PRINT @SQL
IF (@FirstName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @FirstName'
IF (@LastName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @LastName'
IF (@Title IS NOT NULL) SET @SQL = @SQL + ' AND Title = @Title'
EXEC sp_executesql @SQL, N'@TopCount INT, @FirstName varchar(25), @LastName varchar(25), @Title varchar(64)',
@TopCount, @FirstName, @LastName, @Title
END
GO
Uso
exec spDoSearch @TopCount = 3
exec spDoSearch @FirstName = 'Dick'
Pros:
- fácil de escribir y entender
- flexibilidad: genere fácilmente la consulta para filtraciones más complicadas (por ejemplo, TOP dinámico)
Contras:
- posibles problemas de rendimiento en función de los parámetros, índices y volumen de datos proporcionados
No es una respuesta directa, pero está relacionada con el problema, también conocido como panorama general.
Por lo general, estos procedimientos almacenados de filtrado no flotan, sino que se llaman desde alguna capa de servicio. Esto deja la opción de alejar la lógica de negocios (filtrado) de SQL a la capa de servicio.
Un ejemplo es usar LINQ2SQL para generar la consulta basada en los filtros proporcionados:
public IList<SomeServiceModel> GetServiceModels(CustomFilter filters)
{
var query = DataAccess.SomeRepository.AllNoTracking;
// partial and insensitive search
if (!string.IsNullOrWhiteSpace(filters.SomeName))
query = query.Where(item => item.SomeName.IndexOf(filters.SomeName, StringComparison.OrdinalIgnoreCase) != -1);
// filter by multiple selection
if ((filters.CreatedByList?.Count ?? 0) > 0)
query = query.Where(item => filters.CreatedByList.Contains(item.CreatedById));
if (filters.EnabledOnly)
query = query.Where(item => item.IsEnabled);
var modelList = query.ToList();
var serviceModelList = MappingService.MapEx<SomeDataModel, SomeServiceModel>(modelList);
return serviceModelList;
}
Pros:
- consulta generada dinámicamente en función de los filtros proporcionados. No se necesitan husmear parámetros ni recompilar sugerencias
- algo más fácil de escribir para aquellos en el mundo OOP
- normalmente amigable con el rendimiento, ya que se emitirán consultas "simples" (aunque todavía se necesitan índices apropiados)
Contras:
- Se pueden alcanzar las limitaciones de LINQ2QL y forzar una rebaja a LINQ2Objects o volver a la solución SQL pura según el caso
- la escritura descuidada de LINQ podría generar consultas horribles (o muchas consultas, si se cargan las propiedades de navegación)