Map Enum with search conditions

4

I have a class that does the search conditions as follows:

public enum EnumCondicao
{
    [Display(Name = "Igual")]
    Igual,
    [Display(Name = "Diferente")]
    Diferente,
    [Display(Name = "Maior")]
    Maior,
    [Display(Name = "Menor")]
    Menor,
    [Display(Name = "Maior ou Igual")]
    MaiorOuIgual,
    [Display(Name = "Menor ou Igual")]
    MenorOuIgual
}

I would like to map the attributes of a enum to a query within a condition in LINQ .

Since each variable will have its actual value as a condition in LINQ ( Igual = "Equals", Maior = ">", MaiorOuIgual = ">=" ). So using WHERE with Entity would like to put the true value of the variable in the search.

But how could you do that? or would it be better to only use a Switch case with each operation?

NOTE: This question is part of Cigano's answer

    
asked by anonymous 03.05.2017 / 03:11

2 answers

3
  

This question is continued of this question here , that the answer got a little dense indeed. The idea is now to better narrow down the filtering technique I explained earlier.

In the previous response, I mounted an example in Windows Forms with a screen containing three fields: property of the entity to be filtered, operator and condition.

OnthecheckboxclickFilter,wewouldactivateafunctionalsocalledFiltrar:

privatevoidcheckBoxFiltrar_CheckedChanged(objectsender,EventArgse){Filtrar(checkBoxFiltrar.Checked);}

Thefunctionisbelow:

protectedvoidFiltrar(boolcheckFiltrar){if(checkFiltrar)clienteBindingSource.DataSource=context.Clientes.Where(comboBoxCampoPesquisa.SelectedValue.ToString()+".Contains(@0)", textBoxValor.Text)
            .ToList();
    else
        clienteBindingSource.DataSource = context.Clientes.Local.ToBindingList();

    dataGridView.Refresh();
}

Now we must include the logic to insert the correct operator into our code with dynamic LINQ. Before, since we only have columns string , I put one more operator, called "Contains":

namespace TesteWindowsForms.Models.Enums
{
    public enum Condicao
    {
        [Display(Name = "Contém")]
        Contem,
        [Display(Name = "Igual")]
        Igual,
        [Display(Name = "Diferente")]
        Diferente,
        [Display(Name = "Maior")]
        Maior,
        [Display(Name = "Menor")]
        Menor,
        [Display(Name = "Maior ou Igual")]
        MaiorOuIgual,
        [Display(Name = "Menor ou Igual")]
        MenorOuIgual
    }
}

The most interesting way I found to implement an operator resolution by Enum is by using Extensions , as below:

public static class EnumExtensions
{
    public static TAttribute GetAttribute<TAttribute>(this Enum enumValue)
            where TAttribute : Attribute
    {
        return enumValue.GetType()
                        .GetMember(enumValue.ToString())
                        .First()
                        .GetCustomAttribute<TAttribute>();
    }

    public static String CondicaoParaLinq(this Condicao condicao)
    {
        switch (condicao)
        {
            case Condicao.Contem:
                return ".Contains(@0)";
            case Condicao.Diferente:
                return " != @0";
            case Condicao.Maior:
                return " > @0";
            case Condicao.MaiorOuIgual:
                return " >= @0";
            case Condicao.Menor:
                return " < @0";
            case Condicao.MenorOuIgual:
                return " <= @0";
            case Condicao.Igual:
            default:
                return " == @0";
        }
    }
}

Usage:

    protected void Filtrar(bool checkFiltrar)
    {
        if (checkFiltrar)
            clienteBindingSource.DataSource = context.Clientes
                .Where(comboBoxCampoPesquisa.SelectedValue.ToString() + 
                       ((Condicao)Enum.Parse(typeof(Condicao), comboBoxCondicao.SelectedValue.ToString())).CondicaoParaLinq(), 
                       textBoxValor.Text)
                .ToList();
        else
            clienteBindingSource.DataSource = context.Clientes.Local.ToBindingList();

        dataGridView1.Refresh();
    }

That is, I can now filter by "contains" or "equal" from the screen:

Filteringby"contains"

But it does not make much sense to use larger, smaller or smaller or equal, for example, in string fields, right? It means that every time your search field changes, you will need to restrict the operators.

For this, I created this Helper :

public static class CondicoesHelper
{
    private static Dictionary<Type, IEnumerable<Condicao>> condicoesPorTipo = new Dictionary<Type, IEnumerable<Condicao>> {
        { typeof(String), new List<Condicao> { Condicao.Igual, Condicao.Diferente, Condicao.Contem } },
        { typeof(int), new List<Condicao> { Condicao.Igual, Condicao.Diferente, Condicao.Maior, Condicao.MaiorOuIgual, Condicao.Menor, Condicao.MenorOuIgual } },
        { typeof(long), new List<Condicao> { Condicao.Igual, Condicao.Diferente, Condicao.Maior, Condicao.MaiorOuIgual, Condicao.Menor, Condicao.MenorOuIgual } },
        { typeof(Guid), new List<Condicao> { Condicao.Igual, Condicao.Diferente } }
    };

    public static IEnumerable<dynamic> FiltrarCondicoesPorTipoDeCampo(Type tipoDoCampo)
    {
        return condicoesPorTipo[tipoDoCampo]
               .Select(c => new
               {
                   Valor = c.ToString(),
                   Texto = c.GetAttribute<DisplayAttribute>().Name
               })
               .AsEnumerable();
    }

    public static IEnumerable<dynamic> TodasAsCondicoes()
    {
        return Enum.GetValues(typeof(Condicao))
            .Cast<Condicao>()
            .Select(c => new
            {
                Valor = c.ToString(),
                Texto = c.GetAttribute<DisplayAttribute>().Name
            })
            .AsEnumerable();
    }
}

Now I'll need to tie an event to the search field. When it changes, the list of conditions must be updated. That is:

    private void comboBoxCampoPesquisa_SelectedValueChanged(object sender, EventArgs e)
    {
        IEnumerable<dynamic> condicoes;
        if (comboBoxCampoPesquisa.SelectedValue != null)
        {
            condicoes = CondicoesHelper.FiltrarCondicoesPorTipoDeCampo(typeof(Cliente).GetProperty(comboBoxCampoPesquisa.SelectedValue.ToString()).PropertyType);
        } else
        {
            condicoes = CondicoesHelper.TodasAsCondicoes();
        }

        comboBoxCondicao.ValueMember = "Valor";
        comboBoxCondicao.DisplayMember = "Texto";
        comboBoxCondicao.DataSource = condicoes.ToList();
    }

Once this is done, I can comment on the conditions in OnLoad :

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        context.Clientes.Load();

        clienteBindingSource.DataSource =
            context.Clientes.Local.ToBindingList();

        var camposPesquisa =
            typeof(Cliente).GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
                                          BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
                .Select(p => new
                {
                    Valor = p.Name,
                    Texto = p.GetCustomAttribute<DisplayNameAttribute>().DisplayName
                }).ToList();

        comboBoxCampoPesquisa.ValueMember = "Valor";
        comboBoxCampoPesquisa.DisplayMember = "Texto";
        comboBoxCampoPesquisa.DataSource = camposPesquisa;

        //var condicoes = Enum.GetValues(typeof(Condicao))
        //    .Cast<Condicao>()
        //    .Select(c => new
        //    {
        //        Valor = c.ToString(),
        //        Texto = c.GetAttribute<DisplayAttribute>().Name
        //    })
        //    .ToList();

        //comboBoxCondicao.ValueMember = "Valor";
        //comboBoxCondicao.DisplayMember = "Texto";
        //comboBoxCondicao.DataSource = condicoes;

        viewModel = new FiltrosPesquisaViewModel
        {

        };
    }

And the Filtrar method? It looks like this:

    protected void Filtrar(bool checkFiltrar)
    {
        if (checkFiltrar)
            clienteBindingSource.DataSource = context.Clientes
                .Where(comboBoxCampoPesquisa.SelectedValue.ToString() +
                       ((Condicao)Enum.Parse(typeof(Condicao), comboBoxCondicao.SelectedValue.ToString())).CondicaoParaLinq(),
                       Convert.ChangeType(textBoxValor.Text, typeof(Cliente).GetProperty(comboBoxCampoPesquisa.SelectedValue.ToString()).PropertyType))
                .ToList();
        else
            clienteBindingSource.DataSource = context.Clientes.Local.ToBindingList();

        dataGridView1.Refresh();
    }

To test, I placed an extra column on the screen, "Number of Users", which is whole:

Operator"Different"

Operator"Larger"

Finally, the operators are filtered according to the field type:

    
03.05.2017 / 06:52
1

A shorter response. Assuming you have a method that receives a list, a number to compare, and an instance of your enumeration:

public List<int> Metodo(List<int> input, int comparado, EnumCondicao condicao)
{
    List<int> resultado = input.Where(i =>
        (condicao == EnumCondicao.Igual && i == comparado)
        || (condicao == EnumCondicao.Diferente && i != comparado)
        || (condicao == EnumCondicao.Maior && i > comparado)
        || (condicao == EnumCondicao.Menor && i < comparado)
        || (condicao == EnumCondicao.MaiorOuIgual && i >= comparado)
        || (condicao == EnumCondicao.MenorOuIgual && i <= comparado)
    ).ToList();

    return resultado;
}

CQD

    
03.05.2017 / 13:37