Map Enum with search conditions

Asked

Viewed 544 times

4

I have a class that makes 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 for a consultation within a LINQ.

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

But how could I do that? or would it be better to use just one Switch case with each operation?

OBS:That question is part of the Gypsy’s answer

2 answers

3


This question continues of this question here, that the answer has gotten a little dense even. The idea is now to better sift through the filtering technique I explained earlier.

In the previous answer, I set up an example in Windows Forms with a screen containing three fields: ownership of the entity to be filtered, operator and condition.

Tela do Windows Forms

At the click of checkbox Filter, would activate a function also called Filtrar:

private void checkBoxFiltrar_CheckedChanged(object sender, EventArgs e)
{
    Filtrar(checkBoxFiltrar.Checked);
}

The function is below:

protected void Filtrar(bool checkFiltrar)
{
    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 in our code with dynamic LINQ. Before, as we only have columns string, put an extra 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 using Extensions, as the 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";
        }
    }
}

Use:

    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 filter now by "contains" or "equal" from the screen:

Filtrado por "contém"

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

For that, 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 will need to tie an event to the search field. When it changes, the conditions list should 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 has been done, I can comment on the fulfilment of the 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 method Filtrar? Stay 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 put an extra column on the screen, "Number of Users", which is integer:

Operador "Diferente"

Operador "Maior"

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

Operadores Filtrados 1

Operadores Filtrados 2

  • Show you answered the question there on Switch Case , which was my main question, because I thought I should somehow map the numbering instead of using Switch Case

  • Sorry for the size, but without the rest of the answer you would have several strange mistakes (because I had them here), so I worked out something a little bigger.

  • Cigado, could only detail what this method TAttribute GetAttribute<TAttribute>(this Enum enumValue) do? because be very doubtful on this part, I did not understand why use. Thank you very much for the help so far

  • It is used in this passage: .Select(c => new&#xA; {&#xA; Valor = c.ToString(),&#xA; Texto = c.GetAttribute<DisplayAttribute>().Name&#xA; }). Just to simplify and not stand a gigantic statement when defining Texto.

1

A shorter answer. Assuming you have a method that takes 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;
}

QED

  • +1, but isn’t it better to do generic? This case only meets whole.

  • It is not possible to do completely generic as operators >, <, >= and <= cannot be implemented for the interface (i.e.: cannot be exchanged int for IComparable or similar in my example). But this code is already an example applicable to most . NET structures.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.