有没有办法创建或更新MongoDB索引?

根据createIndexes命令的文档:

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

解决方案是删除索引并从头开始创建它,但这样做成本很高。

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


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

查看驱动程序我已经实现了一个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 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(' ', '_')); })); } 

我有同样的问题,我发现克服这个问题的最强大的方法是捕获exception并明确指定索引名称。 如果您没有指定索引名称,驱动程序将使用键生成名称,即使您可以确定名称,它也不是很强大,因为它可能会随着驱动程序的不同版本而更改。

  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); } 

我知道在正常的操作流程中使用exception的代码是丑陋的,我也知道这段代码应该检查引发WriteConcernException的确切原因,但它确实有效。

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

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

我在使用mongo c#driver( Alkampfer )时遇到了同样的问题,看到了Alkampfer的回答。

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

我根据Alkampfer's答案为index属性编写了一个扩展方法,如下所示:

 public static async Task AddOrUpdateAsync(this IMongoIndexManager indexes, CreateIndexOptions options, IndexKeysDefinition keys = null, CancellationToken cancellationToken = default(CancellationToken)) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (keys == null) { keys = Builders.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); } } 

虽然它有点脏(捕获exception并且重试不是最佳实践),但这是解决该问题的最简单方法

如果你害怕代码的性能,你通常会在每次更改索引时使用脏部分(我记得,这通常不会发生)。

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