Cinco anos atrasado para a festa.
É mencionado nos links fornecidos da resposta aceita, mas acho que merece uma resposta explícita no SO - construindo dinamicamente a consulta com base nos parâmetros fornecidos. Por exemplo:
Configuração
-- 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
Procedimento
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'
Prós:
- fácil de escrever e entender
- flexibilidade - gere facilmente a consulta para filtros mais complicados (por exemplo, TOP dinâmico)
Contras:
- possíveis problemas de desempenho, dependendo dos parâmetros, índices e volume de dados fornecidos
Resposta não direta, mas relacionada ao problema, também conhecido como quadro geral
Geralmente, esses procedimentos armazenados de filtragem não flutuam, mas estão sendo chamados de alguma camada de serviço. Isso deixa a opção de afastar a lógica de negócios (filtragem) do SQL para a camada de serviço.
Um exemplo é o uso do LINQ2SQL para gerar a consulta com base nos filtros fornecidos:
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;
}
Prós:
- consulta gerada dinamicamente com base nos filtros fornecidos. Nenhum parâmetro sniffing ou recompile necessárias dicas de
- um pouco mais fácil de escrever para aqueles no mundo OOP
- normalmente com desempenho otimizado, pois consultas "simples" serão emitidas (ainda são necessários índices apropriados)
Contras:
- As limitações do LINQ2QL podem ser alcançadas e forçar um downgrade para o LINQ2Objects ou retornar à solução SQL pura, dependendo do caso
- a escrita descuidada do LINQ pode gerar consultas terríveis (ou muitas consultas, se as propriedades de navegação forem carregadas)