将项目推入MongoDb中的深层嵌套数组

时间:2019-05-31 16:59:42

标签: c# .net mongodb .net-core

是否可能(最好使用C#Builders)将新项目添加到深度嵌套的数组中,即数组中的一个数组中的一个数组。

我的数据模型看起来像:

public class Company 
{
    public string Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<Department> Departments { get; set; }
}

public class Department
{
    public string Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<Managers> Managers { get; set; }
}

public class Manager
{
    public string Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<Employee> Employees { get; set; }
}

public class Employee 
{
    public string Id { get; set; }
    public string Name { get; set; }
}

翻译为:

{
    "Id": 12345,
    "Name": "Company Ltd",
    "Departments": [
        {
            "Id": 1,
            "Name": "Development",
            "Managers" : [
                {
                    "Id" : 5555,
                    "Name" : "The Boss",
                    "Employees": [
                        {
                            "Id" : 123,
                            "Name" : "Developer 1"
                        },
                        {
                            "Id" : 124,
                            "Name" : "Developer 2"
                        }
                    ]
                }
            ]
        }
    ]
}

如果我想在特定经理下添加另一名员工,我该怎么做?

2 个答案:

答案 0 :(得分:1)

为了推送到嵌套数组,必须使用位置运算符$来指定匹配的外部数组元素以对其进行操作。例如:

db.collection.update(
    {"my_array._id": myTargetId},
    {$push: {"my_array.$.my_inner_array": myArrayElem}}
);

但是,这对于遍历嵌套数组会造成问题-也就是说,您只能 在单个数组上使用位置运算符,而不能在任何嵌套数组上使用。如MongoDB documentation中所述,这是一个定义明确的问题。

如果您绝对需要执行此类嵌套数组操作,则可以使用几个选项:

第一个也是首选的方法是更新文档结构,并避免将数组嵌套超过一个级别。这将完全避免该问题,但是将需要将任何现有数据迁移到新结构,并需要付出额外的努力来以您需要的即时获取方式构造数据。最终将需要使用数据的单独的客户端和服务器表示形式。

第二步是执行一系列不太可靠的步骤:
 1.检索原始文档。
 2.手动找到目标元素所在的每个数组的索引。
 3.尝试更新特定索引链,并尝试匹配该索引链。
 4.检查更新尝试的结果-如果失败,则可能是在计算索引时更改了文档。

例如,如果您想更新ID为5555的经理以拥有其他雇员,则在检索索引后将执行以下查询:

// Index chain found to be Departments.0 and Managers.0
db.collection.update(
    {
        "Id": 12345,
        "Departments.0.Managers.0.Id": 5555 // Specify index chain 0,0 and ensure that our target still has Id 5555.
    },
    { $push: {
        "Departments.0.Managers.0.Employees": myNewEmployee // Push to index chain 0,0
    }}

);

答案 1 :(得分:0)

对每个要使用的数组使用位置运算符。

在更新选项中使用数组过滤器来指定部门和经理的ID。数组过滤器中使用的字母应与更新定义中用作位置运算符的字母匹配。因此,“ d .Id”->“部门。$ [ d ]”

如果要匹配多个属性,可以在数组过滤器中使用字典。

    private IMongoCollection<Company> _collection;

    public async Task AddEmployee()
    {
        var filter = Builders<Company>.Filter.Where(d => d.Id == "companyId");
        var update = Builders<Company>.Update
            .Push("Departments.$[d].Managers.$[m].Employees", new Employee { Id = "employeeId", Name = "employeeName" });

        var updateOptions = new UpdateOptions
        {
            ArrayFilters = new List<ArrayFilterDefinition>
                {
                    new BsonDocumentArrayFilterDefinition<BsonDocument>(new BsonDocument("d.Id", "departmentId")),
                    new BsonDocumentArrayFilterDefinition<BsonDocument>(new BsonDocument("m.Id", "managerId")),
                }
        };

        await _collection.UpdateOneAsync(filter, update, updateOptions);
    }

这里的问题是,您需要在更新定义和过滤器中使用字符串,但不确定如何在没有字符串的情况下进行管理。

从阵列中删除员工是相似的,但是您必须为要删除的员工指定额外的过滤器。

    public async Task FireEmployee()
    {
        var filter = Builders<Company>.Filter.Where(d => d.Id == "companyId");
        var employeeFilter = Builders<Employee>.Filter.Where(e => e.Id == "employeeId");
        var update = Builders<Company>.Update
            .PullFilter("Departments.$[d].Managers.$[m].Employees", employeeFilter);

        var updateOptions = new UpdateOptions
        {
            ArrayFilters = new List<ArrayFilterDefinition>
                {
                    new BsonDocumentArrayFilterDefinition<BsonDocument>(new BsonDocument("d.Id", "departmentId")),
                    new BsonDocumentArrayFilterDefinition<BsonDocument>(new BsonDocument("m.Id", "managerId")),
                }
        };

        await _collection.UpdateOneAsync(filter, update, updateOptions);
    }