【问题标题】:Is there a way to create or update a MongoDB index?有没有办法创建或更新 MongoDB 索引?
【发布时间】:2014-07-20 12:55:21
【问题描述】:

根据createIndexes command上的文档:

如果您使用一组选项创建索引,然后发出具有相同索引字段但选项​​不同的 createIndexes,MongoDB 将不会更改选项也不会重建索引。

解决方案是删除索引并从头开始创建它,但这很昂贵。

有没有办法在没有索引的情况下创建索引,如果有具有相同选项的索引则不执行任何操作,如果选项发生更改则替换索引?


这个问题最初是由Phil Barresihere提出的,但后来被删除了。

【问题讨论】:

    标签: c# .net mongodb indexing mongodb-.net-driver


    【解决方案1】:

    查看driver,我实现了一个CreateOrUpdateIndex 扩展方法,该方法比较原始索引文档,如果索引选项已更改,则替换索引(只要索引名称保持不变):

    public static WriteConcernResult CreateOrUpdateIndex(
        this MongoCollection mongoCollection,
        IMongoIndexKeys keys,
        IMongoIndexOptions options = null)
    {
        if (mongoCollection.IndexExists(keys))
        {
            var indexDocument = mongoCollection.GenerateIndexDocument(keys, options);
            if (!mongoCollection.GetIndexes().RawDocuments.Any(indexDocument.Equals))
            {
                mongoCollection.DropIndex(keys);
            }
        }
    
        return mongoCollection.CreateIndex(keys, options);
    }
    

    生成原始索引文档:

    public static BsonDocument GenerateIndexDocument(this MongoCollection mongoCollection, IMongoIndexKeys keys, IMongoIndexOptions options)
    {
        var optionsDocument = options.ToBsonDocument();
        var keysDocument = keys.ToBsonDocument();
        var indexDocument = new BsonDocument
        {
            { "ns", mongoCollection.FullName },
            { "name", GenerateIndexName(keysDocument, optionsDocument) },
            { "key", keysDocument }
        };
        if (optionsDocument != null)
        {
            indexDocument.Merge(optionsDocument);
        }
    
        return indexDocument;
    }
    
    public static string GenerateIndexName(IEnumerable<BsonElement> keys, BsonDocument options)
    {
        const string name = "name";
        if (options != null && options.Contains(name)) return options[name].AsString;
    
        return string.Join("_", keys.Select(element =>
        {
            var value = "x";
            switch (element.Value.BsonType)
            {
                case BsonType.Int32: value = ((BsonInt32)element.Value).Value.ToString(); break;
                case BsonType.Int64: value = ((BsonInt64)element.Value).Value.ToString(); break;
                case BsonType.Double: value = ((BsonDouble)element.Value).Value.ToString(); break;
                case BsonType.String: value = ((BsonString)element.Value).Value; break;
            }
            return string.Format("{0}_{1}", element.Name, value.Replace(' ', '_'));
        }));
    }
    

    【讨论】:

    • 我的 c# 生锈了,但是如果索引已经存在,createIndexes 不应该是一个空操作吗?
    • @Sammaye 是,它不会更新索引。这意味着当您更新选项时,您需要专门删除旧索引并创建新索引,CreateOrUpdateIndex 会自动执行
    • 哦ok我看到了,你只用key检测然后判断两个keyed index之间的options是否匹配,ok kool
    • @Sammaye 是的,这实际上就是 IndexExists 在内部实现的方式,关于索引键......
    • 此解决方案不包含“v”字段,因此比较总是失败。是否有可以忽略该字段或将其设置为正确值(具有未来兼容性)的更新版本?
    【解决方案2】:

    我遇到了同样的问题,我发现解决此问题的最可靠方法是捕获异常并明确指定索引名称。如果您不指定索引名称,驱动程序将使用键生成名称,即使您可以确定名称,它也不是很健壮,因为它可能会随着驱动程序的不同版本而改变。

                try
                {
                    _collection.CreateIndex(keys, options);
                }
                catch (MongoWriteConcernException ex)
                {
                    //probably index exists with different options, lets check if name is specified
                    var optionsDoc = options.ToBsonDocument();
                    if (!optionsDoc.Names.Contains("name"))
                        throw;
    
                    var indexName = optionsDoc["name"].AsString;
                    _collection.DropIndexByName(indexName);
                    _collection.CreateIndex(keys, options);
                }
    

    我知道将异常用于正常操作流程的代码很难看,而且我还知道该代码应该检查引发 WriteConcernException 的确切原因,但它确实有效。

    如果选项不包含名称属性,我只是重新抛出异常,但如果指定了名称,我会尝试删除索引,然后重新创建索引。

    如果错误是由于不同的原因(不是不同的选项/字段),第二个 CreateIndex 可能会再次抛出,然后调用者代码有责任了解实际发生的情况。

    【讨论】:

      【解决方案3】:

      我在使用 mongo c# 驱动程序 (v2.2.3) 时遇到了同样的问题,并看到了 Alkampfer 的回答。

      在 2.2.3 版中,索引通过集合的 Indexes 属性进行管理。

      我已经根据Alkampfer's的回答为index属性写了一个扩展方法,如下:

      public static async Task AddOrUpdateAsync<TDocument>(this IMongoIndexManager<TDocument> indexes, CreateIndexOptions options, IndexKeysDefinition<TDocument> keys = null, CancellationToken cancellationToken = default(CancellationToken))
              {
                  if (options == null)
                  {
                      throw new ArgumentNullException(nameof(options));
                  }
                  if (keys == null)
                  {
                      keys = Builders<TDocument>.IndexKeys.Ascending(options.Name);
                  }
                  try
                  {
                      await indexes.CreateOneAsync(keys, options, cancellationToken).ConfigureAwait(false);
                  }
                  catch (MongoCommandException e)
                  {
                      await indexes.DropOneAsync(options.Name, cancellationToken).ConfigureAwait(false);
                      await AddOrUpdateAsync(indexes, options, keys, cancellationToken).ConfigureAwait(false);
                  }
              }
      

      虽然它有点脏(捕获异常并重试不是最佳做法),但它是解决问题的最简单方法

      如果您担心代码的性能,通常每次更改索引时使用一次脏部分(我记得这种情况并不经常发生)。

      如果您想要迭代集合索引的替代方法,但这是一个相当混乱的过程(moveNexts 和访问当前位置)

      【讨论】:

        【解决方案4】:

        现在使用MongoDB.Driver,您应该可以通过这种方式re-create the index

        var client = new MongoClient("connection-string-here");
        var database = client.GetDatabase("MyDatabaseName");
        
        var index = Builders<MyDocument>.IndexKeys.Ascending(x => x.MyDocRefNumber);
        var idexModel = new CreateIndexModel<MyDocument>(index);
        database.<MyDocument>()Indexes.CreateOne(indexModel);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-12-24
          • 1970-01-01
          • 1970-01-01
          • 2014-07-01
          • 1970-01-01
          • 1970-01-01
          • 2020-07-23
          • 1970-01-01
          相关资源
          最近更新 更多