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:
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:
Finally, the operators are filtered according to the field type: