Tengo clase estructura algo como esto

List<MainCat> AllCat;

public class MainCat
{
    public string id { get; set; }
    public string name { get; set; }
    public List<subcat> subcat { get; set; }
}

public class subcat
{
    public string id { get; set; }
    public string name { get; set; }
    public List<subsubcat> subsubcat { get; set; }
}

public class subsubcat
{
    public string id { get; set; }
    public string name { get; set; }
}

Quiero obtener nombre por id , por ejemplo, sé que la identificación es 69

Quiero obtener una salida como esta

MainCat.name> subcat.name> subsubcat.name (si se encuentra 69 en subsubcat)

MainCat.name> subcat.name (si se encuentra 69 en subcat)

MainCat.name (si se encuentra 69 en MainCat)

-1
arthur 21 nov. 2017 a las 10:32

3 respuestas

La mejor respuesta

Si he entendido bien su requisito, este es un caso en el que la sintaxis de la consulta puede hacer maravillas:

IEnumerable<string> MyFunc(IEnumerable<MainCat> mainCategories, string idToMatch)
{
  return (from main in mainCategories
          where main.id == idToMatch
          select main.name)
    .Concat(from main in mainCategories
            from sub in main.subcat
            where sub.id == idToMatch
            select string.Format("{0} > {1}", main.name, sub.name))
    .Concat(from main in mainCategories
            from sub in main.subcat
            from subsub in sub.subsubcat
            where subsub.id == idToMatch
            select string.Format("{0} > {1} > {2}", main.name, sub.name, subsub.name));
}

Si solo te interesa el primer partido, puedes llamarlo como

string resultName = MyFunc(AllCat, "69").FirstOrDefault();

Debido a que la consulta usa ejecución diferida, esto evitará llamar a las consultas más complejas si se encuentra una coincidencia en la categoría principal.

También es posible usar la función SelectMany con la sintaxis de llamada de función, sin embargo, es mucho más difícil de seguir, p. lo siguiente es cómo reescribí el contenido de la segunda llamada .Concat(...) para ilustrar:

mainCategories.SelectMany(main => main.subcat, (main, sub) => new { Main = main, Sub = sub })
              .SelectMany(pair => pair.Sub.subsubcat, (pair, subsub) => new { Main = pair.Main, Sub = pair.Sub, SubSub = subsub})
              .Where(triplet => triplet.SubSub.id == idToMatch)
              .Select(triplet => string.Format("{0} > {1} > {2}", triplet.Main, triplet.Sub, triplet.SubSub));

Según tengo entendido, la sintaxis de la consulta se compila en algo muy similar a esto detrás de escena.


Otra posibilidad sería agregar una interfaz a las 3 clases (o unificarlas en una sola clase o derivar de una clase base común dependiendo del caso de uso real).

Esto permite una implementación recursiva que puede buscar a profundidad arbitraria (a continuación hay 2 implementaciones diferentes basadas en Linq dependiendo de si tiene preferencia por una u otra sintaxis):

public interface ITreeCat
{
  string id { get; }
  string name { get; }
  IEnumerable<ITreeCat> subcat { get; }
}

// add explicit interface implemetantion to existing 3 classes
// e.g.
// IEnumerable<ITreeCat> ITreeCat.subcat { get { return subsubcat; } }
// IEnumerable<ITreeCat> ITreeCat.subcat { get { return Enumerable.Empty<ITreeCat>(); } }

IEnumerable<string> MyFunc(IEnumerable<ITreeCat> categories, string idToMatch, string prefix = "")
{
  return (from cat in categories
          where cat.id == idToMatch
          select prefix + cat.name)
    .Concat(from cat in categories
            from recursiveResult in MyFunc(cat.subcat, idToMatch, prefix + cat.name + " > ")
            select recursiveResult);
}

IEnumerable<string> MyFunc2(IEnumerable<ITreeCat> categories, string idToMatch, string prefix = "")
{
  return categories.Where(cat => cat.id == idToMatch)
                   .Select(cat => prefix + cat.name)
                   .Concat(categories.SelectMany(cat => MyFunc2(cat.subcat, idToMatch, prefix + cat.name + " > ")));
}

Esto tiene la ventaja de que continúa funcionando si luego agrega un subsubcatcat, etc.

Todos los ejemplos de código anteriores utilizan una búsqueda de amplitud y enumeran repetidamente las categorías "principales" cada vez que van un nivel más profundo.

En algunas aplicaciones, una búsqueda profunda puede ser una mejor opción, ya que cada lista solo se enumera una vez, en cuyo caso es mucho más fácil usar foreach en lugar de Linq. Una vez más, una versión recursiva es más concisa que 3 bucles anidados con diferentes clases:

IEnumerable<string> MyFuncDepthFirst(IEnumerable<ITreeCat> categories, string idToMatch)
{
  foreach(var cat in categories)
  {
    if (cat.id == idToMatch)
      yield return cat.name;
    foreach (var subResult in MyFuncDepthFirst(cat.subcat, idToMatch))
      yield return string.Format("{0} > {1}", cat.name, subResult);
  }
}

Esto todavía supone que pueden ocurrir múltiples coincidencias. Si estamos justo después de la primera coincidencia, entonces no hay necesidad de usar un bloque iterador, y la función anterior se puede modificar para devolver una cadena simple:

string FirstMatchingIdDepthFirst(IEnumerable<ITreeCat> categories, string idToMatch)
{
  foreach(var cat in categories)
  {
    if (cat.id == idToMatch)
      return cat.name;
    string subResult = FirstMatchingIdDepthFirst(cat.subcat, idToMatch);
    if(subResult != null)
      return string.Format("{0} > {1}", cat.name, subResult);
  }
  return null;
}
1
Steve 21 nov. 2017 a las 10:56

Puedes optar por un método como el siguiente

public static Type Find(string id, MainCat m)
{
    if (m.id.Equals(id))
    {
        return m.GetType();
    }
    if (m.subcat.Any(a => a.id.Equals(id)))
    {
        return typeof(subcat);
    }
    if (m.subcat.Any(a => a.subsubcat.Any(b => b.id.Equals(id))))
    {
        return typeof(subsubcat);
    }
    return null;
}

Y realiza la búsqueda. Encuentre la esencia, https://gist.github.com/IshamMohamed/33d75064789d77d884017b8c

De esta manera, puede aumentar el número de listas internas (por ejemplo: subsubsubcat)

0
Isham Mohamed 21 nov. 2017 a las 09:34
var list = this.AllCat.Where(t=>t.subcat.Any(s=> subsubcat.contains(s));
0
Sajeetharan 21 nov. 2017 a las 07:35