Pergunta

Estou trabalhando em um aplicativo que permite aos dentistas capturar informações sobre certas atividades clínicas. Embora o aplicativo não seja altamente personalizável (sem fluxos de trabalho ou formulários personalizados), ele oferece alguns recursos de personalização rudimentar; Os clientes podem optar por aumentar os campos de forma predefinidos com seus próprios personalizados. Existem cerca de meia dúzia de tipos de campo diferentes que os administradores podem criar (por exemplo, texto, data, numérico, suspensão, etc.). Estamos usando o valor da entidade-atributo (EAV) no lado da persistência para modelar essa funcionalidade.

Um dos outros recursos -chave do aplicativo é a capacidade de criar consultas personalizadas contra esses campos personalizados. Isso é realizado por meio de uma interface do usuário na qual qualquer número de regras (data <= (agora - 5 dias), texto como '444', suspenso == 'UTI') pode ser criado. Todas as regras são e são juntas para produzir uma consulta.

A implementação atual (que eu "herdei") não é orientada a objetos nem testável de unidade. Essencialmente, existe uma única classe "Deus" que compila todos os inúmeros tipos de regra diretamente em uma declaração dinâmica complexa de SQL (ou seja, junções internas, juntas externas e subseletas). Essa abordagem é problemática por vários motivos:

  • Testes de unidade Regras individuais isoladamente é quase impossível
  • Esse último ponto também significa adicionar tipos de regras adicionais no futuro definitivamente violarão o princípio fechado aberto.
  • As preocupações de lógica e persistência dos negócios estão sendo co-montadas.
  • Testes de unidade de execução lenta, já que é necessário um banco de dados real (o sqllite não pode analisar T-SQL e zombando de um analisador seria UHH ... Hard)

Estou tentando criar um design de substituição flexível, sustentável e testável, mantendo o desempenho da consulta bastante ágil. Esse último ponto é essencial, pois imagino que uma implementação baseada em OOAD mova pelo menos algumas das lógicas de filtragem de dados do servidor de banco de dados para o servidor de aplicativos (.NET).

Estou considerando uma combinação dos padrões de comando e cadeia de responsabilidade:

A classe de consulta contém uma coleção de classes de regras abstratas (daterule, textrule, etc). e mantém uma referência a uma classe de conjunto de dados que contém um conjunto de dados não filtrados. O conjunto de dados é modelado de maneira agnóstica de persistência (ou seja, sem referências ou ganchos nos tipos de banco de dados)

A regra possui um único método filtro () que recebe um conjunto de dados, filtra -o adequadamente e o retorna ao chamador. A classe de consulta do que simplesmente itera sobre cada regra, permitindo que cada regra filtre o conjunto de dados como considera o ajuste. A execução pararia assim que todas as regras forem executadas ou assim que o conjunto de dados for filtrado até nada.

A única coisa que me preocupa sobre essa abordagem são as implicações de desempenho de analisar um conjunto de dados potencialmente grande não filtrado no .NET. Certamente existem algumas abordagens testadas e verdadeiras para resolver esse tipo de problema que oferece um bom equilíbrio entre manutenção e desempenho?

Uma nota final: a gerência não permitirá o uso do Nibernate. O LINQ para SQL pode ser possível, mas não tenho certeza de quão aplicável seria a tecnologia à tarefa em questão.

Muito obrigado e estou ansioso pelo feedback de todos!

Atualização: ainda procurando uma solução nisso.

Foi útil?

Solução

Eu acho que o LINQ para SQL seria uma solução ideal acoplada, talvez, com o LINQ dinâmico das amostras VS2008. Usando o LINQ, particularmente com métodos de extensão no IEnumerable/iQueryable, você pode criar suas consultas usando sua lógica padrão e personalizada, dependendo das entradas que você obtém. Eu uso essa técnica fortemente para implementar filtros em muitas das minhas ações de MVC com grande efeito. Como ele realmente constrói uma árvore de expressão então a usa para gerar o SQL no ponto em que a consulta precisa ser materializada, acho que seria ideal para o seu cenário, pois a maior parte do levantamento pesado ainda é feito pelo servidor SQL. Nos casos em que o LINQ prova gerar consultas não ideais, você sempre pode usar funções com valor de tabela ou procedimentos armazenados adicionados ao seu contexto de dados LINQ como métodos para aproveitar as consultas otimizadas.

Atualizada: Você também pode tentar usar PredicateBuilder de C# 3.0 em poucas palavras.

Exemplo: Encontre todos os livros em que o título contém um conjunto de termos de pesquisa e o editor é O'Reilly.

 var predicate = PredicateBuilder.True<Book>();
 predicate = predicate.And( b => b.Publisher == "O'Reilly" );
 var titlePredicate = PredicateBuilder.False<Book>();
 foreach (var term in searchTerms)
 {
     titlePredicate = titlePredicate.Or( b => b.Title.Contains( term ) );
 }
 predicate = predicate.And( titlePredicate );

 var books = dc.Book.Where( predicate );

Outras dicas

A maneira como eu vi isso é criando objetos que modelam cada uma das condições das quais você deseja que o usuário crie a consulta e construa uma árvore de objetos usando -os.

A partir da árvore dos objetos, você poderá criar recursivamente uma declaração SQL que satisfaz a consulta.

Os objetos básicos que você precisarão serão e / / ou objetos, bem como objetos para modelar a comparação, como iguais, menos etc. Você provavelmente desejará usar uma interface para esses objetos para facilitar a encadeamento de diferentes maneiras.

Um exemplo trivial:

public interface IQueryItem
{
    public String GenerateSQL();
}


public class AndQueryItem : IQueryItem
{
    private IQueryItem _FirstItem;
    private IQueryItem _SecondItem;

    // Properties and the like

    public String GenerateSQL()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(_FirstItem.GenerateSQL());
        builder.Append(" AND ");
        builder.Append(_SecondItem.GenerateSQL());

        return builder.ToString();
    }
}

Implementá -lo dessa maneira deve permitir que você teste as regras com bastante facilidade.

No lado negativo, essa solução ainda deixa o banco de dados para fazer muito do trabalho, o que parece que você realmente não quer fazer.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top