MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Calculer le nombre d'objets imbriqués avec C#

La requête pour compter les occurrences "uniques" dans un "EndpointId" de chacun des "Uid" dans "Tags" et le "Type" dans "Sensors" serait :

db.collection.aggregate([
  { "$unwind": "$Tags" },
  { "$unwind": "$Tags.Sensors" },
  { "$group": {
    "_id": {
      "EndpointId": "$EndpointId",
      "Uid": "$Tags.Uid",
      "Type": "$Tags.Sensors.Type"
    },
  }},
  { "$group": {
    "_id": {
      "EndpointId": "$_id.EndpointId",
      "Uid": "$_id.Uid",
    },
    "count": { "$sum": 1 }
  }},
  { "$group": {
    "_id": "$_id.EndpointId",
    "tagCount": { "$sum": 1 },
    "sensorCount": { "$sum": "$count" }
  }}
])

Ou pour C#

    var results = collection.AsQueryable()
      .SelectMany(p => p.Tags, (p, tag) => new
        {
          EndpointId = p.EndpointId,
          Uid = tag.Uid,
          Sensors = tag.Sensors
        }
      )
      .SelectMany(p => p.Sensors, (p, sensor) => new
        {
          EndpointId = p.EndpointId,
          Uid = p.Uid,
          Type = sensor.Type
        }
      )
      .GroupBy(p => new { EndpointId = p.EndpointId, Uid = p.Uid, Type = p.Type })
      .GroupBy(p => new { EndpointId = p.Key.EndpointId, Uid = p.Key.Uid },
        (k, s) => new { Key = k, count = s.Count() }
      )
      .GroupBy(p => p.Key.EndpointId,
        (k, s) => new
        {
          EndpointId = k,
          tagCount = s.Count(),
          sensorCount = s.Sum(x => x.count)
        }
      );

Qui sort :

{
  "EndpointId" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",
  "tagCount" : 4,
  "sensorCount" : 16
}

Bien qu'il s'agisse en fait de la manière "la plus efficace" de le faire, étant donné que les documents présentés ont des valeurs uniques pour "Uid" de toute façon serait de $reduce les montants dans les documents eux-mêmes :

db.collection.aggregate([
  { "$group": {
    "_id": "$EndpointId",
    "tags": {
      "$sum": {
        "$size": { "$setUnion": ["$Tags.Uid",[]] }
      }
    },
    "sensors": {
      "$sum": {
        "$sum": {
          "$map": {
            "input": { "$setUnion": ["$Tags.Uid",[]] },
            "as": "tag",
            "in": {
              "$size": {
                "$reduce": {
                  "input": {
                    "$filter": {
                      "input": {
                        "$map": {
                          "input": "$Tags",
                          "in": {
                            "Uid": "$$this.Uid",
                            "Type": "$$this.Sensors.Type"
                          }
                        }
                      },
                      "cond": { "$eq": [ "$$this.Uid", "$$tag" ] }
                    }
                  },
                  "initialValue": [],
                  "in": { "$setUnion": [ "$$value", "$$this.Type" ] }
                }
              }
            }
          }
        }
      }
    }
  }}
])

Cependant, l'instruction ne correspond pas vraiment bien à LINQ, vous devrez donc utiliser le BsonDocument interface pour construire le BSON pour l'instruction. Et bien sûr où le même "Uid" les valeurs "did" se produisent en fait dans plusieurs documents de la collection, puis le $unwind des instructions sont nécessaires pour les « regrouper » dans les documents à partir des entrées du tableau.

Original

Vous résolvez ce problème en obtenant le $size des tableaux. Pour le tableau externe, cela s'applique simplement au chemin du champ du tableau dans le document, et pour les éléments du tableau interne, vous devez traiter avec $map afin de traiter chaque "Tags" élément puis obtenir l'élément $size de "Sensors" et $sum le tableau résultant à réduire au nombre total.

Par document ce serait :

db.collection.aggregate([
  { "$project": {
    "tags": { "$size": "$Tags" },
    "sensors": {
      "$sum": {
        "$map": {
          "input": "$Tags",
           "in": { "$size": "$$this.Sensors" }
        }
      }
    }
  }}
])

Ce à quoi vous avez attribué des classes dans votre code C# ressemblerait à :

collection.AsQueryable()
  .Select(p => new
    {
      tags = p.Tags.Count(),
      sensors = p.Tags.Select(x => x.Sensors.Count()).Sum()
    }
  );

Où ceux-ci reviennent :

{ "tags" : 3, "sensors" : 13 }
{ "tags" : 2, "sensors" : 8 }

Où vous voulez $group les résultats, comme par exemple sur toute la collection, alors vous feriez :

db.collection.aggregate([
  /* The shell would use $match for "query" conditions */
  //{ "$match": { "EndpointId": "89799bcc-e86f-4c8a-b340-8b5ed53caf83" } },
  { "$group": {
    "_id": null,
    "tags": { "$sum": { "$size": "$Tags" } },
    "sensors": {
      "$sum": {
        "$sum": {
          "$map": {
            "input": "$Tags",
             "in": { "$size": "$$this.Sensors" }
          }
        }
      }
    }
  }}
])

Ce qui pour votre code C# comme avant serait :

collection.AsQueryable()
  .GroupBy(p => "", (k,s) => new
    {
      tags = s.Sum(p => p.Tags.Count()),
      sensors = s.Sum(p => p.Tags.Select(x => x.Sensors.Count()).Sum())
    }
  );

Où ceux-ci reviennent :

{ "tags" : 5, "sensors" : 21 }

Et pour "EndpointId , alors vous utilisez simplement ce champ comme clé de regroupement, plutôt que le null ou 0 tel qu'il est appliqué par le mappage du pilote C# :

collection.AsQueryable()
  /* Use the Where if you want a query to match only those documents */
  //.Where(p => p.EndpointId == "89799bcc-e86f-4c8a-b340-8b5ed53caf83")            
  .GroupBy(p => p.EndpointId, (k,s) => new
    {
      tags = s.Sum(p => p.Tags.Count()),
      sensors = s.Sum(p => p.Tags.Select(x => x.Sensors.Count()).Sum())
    }
  );

Qui est bien sûr la même somme des deux exemples de documents que vous nous avez donné :

{ "tags" : 5, "sensors" : 21 }

Ce sont donc des résultats très simples, avec une exécution simple du pipeline une fois que vous vous êtes habitué à la syntaxe.

Je suggère de vous familiariser avec les Opérateurs d'agrégation de la documentation de base, et bien sûr le "Aide-mémoire LINQ" d'expressions et leur mappage d'utilisation à partir du référentiel de code du pilote C#.

Voir également la référence LINQ dans la référence du pilote C # pour d'autres exemples de la façon dont cela correspond au cadre d'agrégation de MongoDB en général.