By the question type it seems to me that it really is the implementation of Full Text Search , where several, possibly all properties of an object are considered during a comparison to find "records" desired. This will always be best implemented in the data layer, directly by the database engine . Virtually everyone, including NoSQL repositories, implements some form of full text search .
However, it is possible to resolve this question in a generic way, without having to manually compare property to the property. This involves using Reflection and can have a negative impact on code performance as you increase the amount of objects subject to the search.
First, we need to write a class with an extension method that allows us to "collect" the public property values of an object automatically, such as string. This routine needs to be smart enough to handle various types of data, such as collections, nested objects, null values, and so on. The code below shows 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 search for all public properties 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: The routine does not handle cyclic object references. However it correctly treats collections of objects. For example, if the Pessoa
class was declared with a collection of children the routine would also look at the contents 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 perform "collection" of public property values as a string. There is room for improvement, such as implementing a cache of properties by object type, avoiding the need to run GetProperties
every time. Another form of improvement, which would make this routine almost as fast as the "manual" solution shown below, would dynamically compile a method for concatenating all public properties of the object. You could issue the code with good old emmit
or use Expression Trees with lambdas. I honestly think that I work too hard and would only follow this path if the option presented is too slow for the real scenarios and if there is no possibility to implement Full Text Search directly in the data repository (which is the ideal solution ).
Another simpler way to solve this, but requiring manual code is to overwrite the ToString () of all classes involved so that all "searchable" fields are included. This way:
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 Endereco
is null
.
To find the people you want, you just need to do:
var encontrados = Pessoas.Where(p => p.ToString().Contains(str));
Or, following the idea of Virgilio Novic's answer:
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,
});