Find a string anywhere in the List c#

Asked

Viewed 2,096 times

1

Good Afternoon. I would like to know how to find a string anywhere in the list. Ex.: string "123". It can be in Pessoa.id, Pessoa.Nome, Endereco.id, Endereco.Rua or Endereco.CEP;

People’s List

List<Pessoa> Pessoas;

Classe Pessoa

using System;

public class Pessoa
{
    public int id { get; set; }
    public string Nome { get; set; }
    public Endereco Endereco { get; set; }
}

Address class

using System;

public class Endereco
{
    public int id { get; set; }
    public string Rua { get; set; }
    public string cep { get; set; }
}

Thank you all so much.

  • you want to filter, can use Where of Linq, alias where this information comes from is a ORM or manually loads?

  • comes from a function that returns a List<Person> which in turn pulls from a DB Xml...

  • then.. I don’t know how I would use the where for this... if it were for a specific attribute of one of the classes all right.. but I want for all attributes.. that it looks for in any one one.....

  • Luis can do it in a generic way, and I even created an answer with a solution that allows us to search all fields like this, including fields in properties that are also objects (like Endereco). Despite using Reflection and being able to have a negative effect on performance, depending on your data is more than enough. But I think what you’re trying to do would be best done at the database layer, using the full text search features...

2 answers

3


Following the another question, make a Where before the Select:

GridView.DataSource = Pessoas
   .Where(c => c.Id.ToString().Contains(str) ||
            c.Nome.Contains(str) ||
            c.Endereco.id.ToString().Contains(str) ||
            c.Endereco.Rua.Contains(str) ||
            c.Endereco.Cep.Contains(str))
   .Select(x => new 
   {
     x.id,
     x.Nome,
     Rua = x.Endereco.Rua,
     Cep = x.Endereco.Cep
   })
   .ToList();
  • I added an answer using generic LINQ. I still think it will be too slow for a large volume of data, but as we do not know the complete scenario that Luis intends to use can serve for him.

2

From the type of question it seems to me that, in fact, it is the implementation of Full Text Search, where several, possibly all properties of an object are considered during a comparison to find desired "records". This will always be best implemented in the data layer, directly by motor database. Virtually all, including Nosql repositories, implement some form of full text search.

However, it is possible to resolve this issue in a general way without having to manually compare property to property. This involves the use of Reflection and can have a negative impact on code performance as you increase the number of objects subject to the search.

First, we need to write a class with an extension method that allows us to "collect" the values of the public properties of an object automatically, such as a string. This routine needs to be smart enough to handle various types of data, such as collections, nested objects, null values, etc. The code below shows you how to do this:

public static class ReflectionSearchExtensions
{
    public static string CollectObjectPropertiesAsString(this object element) {
        var sb = new StringBuilder();
        if (element == null || element is ValueType || element is string) {
            sb.Append(GetValue(element));
        } else {
            IEnumerable enumerableElement = element as IEnumerable;
            if (enumerableElement != null) {
                foreach (object item in enumerableElement) {
                    sb.Append(CollectObjectPropertiesAsString(item));
                }
            } else {
                var members = element.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
                foreach (PropertyInfo p in members) {
                    if (p != null) {
                        Type t = p.PropertyType;
                        object value = p.GetValue(element, null);
                        if (t.IsValueType || t == typeof(string)) {
                            sb.Append(value);
                        } else {
                            if (value != null) {
                                sb.Append(CollectObjectPropertiesAsString(value));
                            }
                        }
                    }
                }
            }
        }
        return sb.ToString();
    }
    private static string GetValue(object o) {
        if (o == null) {
            return "";
        } else if (o is DateTime) {
            return ((DateTime)o).ToShortDateString();
        } else if (o is ValueType || o is string) {
            return o.ToString();
        } else if (o is IEnumerable) {
            return "...";
        }
        return "";
    }
}

Now we can carry out the search for all the public property of the object as follows:

// para encontrar as pessoas por todos os campos usa apenas uma linha de código
var encontrados = pessoas.Where(p => p.CollectObjectPropertiesAsString().Contains(str))

// para retornar sem hierarquia de objetos
var encontrados = pessoas
    .Where(p => p.CollectObjectPropertiesAsString().Contains(str))
    .Select(p => new {
        Id = p.id,
        Nome = p.Nome,
        Rua = p.Endereco != null ? p.Endereco.Rua : null,
        Cep = p.Endereco != null ? p.Endereco.cep : null,
    });

NOTE: Routine does not treat cyclic references of objects. However, it correctly treats collections of objects. For example, if the class Pessoa was declared with a collection of children the routine would also search in the content of the properties of each child (if any):

public class Pessoa {
    public int id { get; set; }
    public string Nome { get; set; }
    public Endereco Endereco { get; set; }
    public List<Pessoa> Filhos { get; set; }
}

However, as explained at the beginning of the answer, this routine is not fast, because it uses reflection (Reflection) to "collect" the values of public properties as a string. There is room for improvement, such as implementing a cache of properties by object type, avoiding the need to execute GetProperties every time. Another form of improvement, which would make this routine almost as fast as the "manual" solution shown below, would be to compile, dynamically, a method to concatenate all the public properties of the object. You could issue the code with good old emmit or use Expression Trees with Ambids. I honestly find too much work and would only follow this path if the option presented is too slow for the real scenarios and if there is not the possibility to implement Full Text Search directly in the data repository (which is the ideal solution).


Another simpler way to resolve this issue, but requiring manual code, is to override the Tostring() of all classes involved so that all "searchable" fields are included. Thus:

public class Pessoa
{
    public int id { get; set; }
    public string Nome { get; set; }
    public Endereco Endereco { get; set; }
    public override string ToString() {
        return string.Format("{0}: {1} - {2}", id, Nome, Endereco);
    }
}

public class Endereco
{
    public int id { get; set; }
    public string Rua { get; set; }
    public string cep { get; set; }
    public override string ToString() {
        return string.Format("{0} - {1} ({2})", Rua, cep, id);
    }
}

One advantage of this method is that it avoids problems with null fields, such as when the Endereco for null.

To find the desired people just do:

var encontrados = Pessoas.Where(p => p.ToString().Contains(str));

Or, following the idea of Virgilio Novic’s reply:

var encontrados = Pessoas
    .Where(p => p.ToString().Contains(str))
    .Select(p => new {
        Id = p.id,
        Nome = p.Nome,
        Rua = p.Endereco != null ? p.Endereco.Rua : null,
        Cep = p.Endereco != null ? p.Endereco.cep : null,
    });

Browser other questions tagged

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