mongodb - 重命名位于数组中的复杂类型的字段

标签 mongodb mongodb-.net-driver

我正在对生产数据库进行重构,需要进行一些重命名。 mongodb的版本是1.8.0。我使用 C# 驱动程序来重构数据库。当我尝试重命名位于数组中的复杂类型的字段时遇到问题。

例如我有这样的文件:

FoobarCollection:

{
  Field1: "",
  Field2: [
    { NestedField1: "", NestedField2: "" },
    { NestedField1: "", NestedField2: "" },
    ... 
  ]
}

例如,我需要将 NestedField2 重命名为 NestedField3。 MongoDB 文档说:

$rename

Version 1.7.2+ only.

{ $rename : { old_field_name : new_field_name } } Renames the field with name 'old_field_name' to 'new_field_name'. Does not expand arrays to find a match for 'old_field_name'.

据我所知,简单地使用 Update.Rename() 不会给出结果,因为正如文档所说“重命名 - 不会扩展数组以找到旧字段名称的匹配项”

我应该编写什么 C# 代码来将 NestedField2 重命名为 NestedField3

最佳答案

我已经实现了特殊类型来重命名 MongoDB 中的任意字段。这是它:

using System.Linq;
using MongoDB.Bson;
using MongoDB.Driver;

namespace DatabaseManagementTools
{
    public class MongoDbRefactorer
    {
        protected MongoDatabase MongoDatabase { get; set; }

        public MongoDbRefactorer(MongoDatabase mongoDatabase)
        {
            MongoDatabase = mongoDatabase;
        }

        /// <summary>
        /// Renames field
        /// </summary>
        /// <param name="collectionName"></param>
        /// <param name="oldFieldNamePath">Supports nested types, even in array. Separate nest level with '$': "FooField1$FooFieldNested$FooFieldNestedNested"</param>
        /// <param name="newFieldName">Specify only field name without path to it: "NewFieldName", but not "FooField1$NewFieldName"</param>
        public void RenameField(string collectionName, string oldFieldNamePath, string newFieldName)
        {
            MongoCollection<BsonDocument> mongoCollection = MongoDatabase.GetCollection(collectionName);
            MongoCursor<BsonDocument> collectionCursor = mongoCollection.FindAll();

            PathSegments pathSegments = new PathSegments(oldFieldNamePath);

            // Rename field in each document of collection
            foreach (BsonDocument document in collectionCursor)
            {
                int currentSegmentIndex = 0;
                RenameField(document, pathSegments, currentSegmentIndex, newFieldName);

                // Now document is modified in memory - replace old document with new in mongo:
                mongoCollection.Save(document);
            }
        }

        private void RenameField(BsonValue bsonValue, PathSegments pathSegments, int currentSegmentIndex, string newFieldName)
        {
            string currentSegmentName = pathSegments[currentSegmentIndex];

            if (bsonValue.IsBsonArray)
            {
                var array = bsonValue.AsBsonArray;
                foreach (var arrayElement in array)
                {
                    RenameField(arrayElement.AsBsonDocument, pathSegments, currentSegmentIndex, newFieldName);
                }
                return;
            }

            bool isLastNameSegment = pathSegments.Count() == currentSegmentIndex + 1;
            if (isLastNameSegment)
            {
                RenameDirect(bsonValue, currentSegmentName, newFieldName);
                return;
            }

            var innerDocument = bsonValue.AsBsonDocument[currentSegmentName];
            RenameField(innerDocument, pathSegments, currentSegmentIndex + 1, newFieldName);
        }

        private void RenameDirect(BsonValue document, string from, string to)
        {
            BsonElement bsonValue;
            bool elementFound = document.AsBsonDocument.TryGetElement(from, out bsonValue);
            if (elementFound)
            {
                document.AsBsonDocument.Add(to, bsonValue.Value);
                document.AsBsonDocument.Remove(from);
            }
            else
            {
                // todo: log missing elements
            }
        }
    }
}

和帮助类型来保留路径段:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace DatabaseManagementTools
{
    public class PathSegments : IEnumerable<string>
    {
        private List<string> Segments { get; set; }

        /// <summary>
        /// Split segment levels with '$'. For example: "School$CustomCodes"
        /// </summary>
        /// <param name="pathToParse"></param>
        public PathSegments(string pathToParse)
        {
            Segments = ParseSegments(pathToParse);
        }

        private static List<string> ParseSegments(string oldFieldNamePath)
        {
            string[] pathSegments = oldFieldNamePath.Trim(new []{'$', ' '})
                .Split(new [] {'$'}, StringSplitOptions.RemoveEmptyEntries);

            return pathSegments.ToList();
        }

        public IEnumerator<string> GetEnumerator()
        {
            return Segments.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public string this[int index]
        {
            get { return Segments[index]; }
        }
    }
}

为了分隔嵌套级别,我使用“$”符号——mongo 中唯一禁止用于集合名称的符号。 用法可以是这样的:

MongoDbRefactorer mongoDbRefactorer = new MongoDbRefactorer(Mongo.Database);
mongoDbRefactorer.RenameField("schools", "FoobarTypesCustom$FoobarDefaultName", "FoobarName");

此代码将在集合 schools FoobarTypesCustom 属性中找到。它可以是复杂类型,所以数组。然后将找到所有 FoobarDefaultName 属性(如果 FoobarTypesCustom 是数组,那么它将遍历它)并将其重命名为 FoobarName。嵌套级别和嵌套数组的数量无关紧要。

关于mongodb - 重命名位于数组中的复杂类型的字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7109654/

相关文章:

json - Mongoose查询结果是只读的吗?

javascript - MongoDB 数组与 AngularJS

linux - 执行db.shutdownServer();后如何再次运行mongodb在管理数据库中

MongoDB Primary 无法恢复

c# - 使用 .net core 和 mongodb 了解性能

MongoDB 3.2 - 管理员用户无权执行命令

python - 无法使用 pymongo 删除记录

mongodb - 如何同时插入记录和数组元素?

mongodb - C# - MongoDB 如何按元素值从多个嵌套数组中删除项目?

mongodb - C# MongoDB 驱动程序 : How to insert a new subdocument into an existing document