是否有一种方法来创建或更新MongoDB索引

本文关键字:创建 更新 索引 MongoDB 方法 一种 是否 | 更新日期: 2023-09-27 18:13:58

根据createIndexes命令的文档:

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

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

是否有一种方法可以在没有索引的情况下创建索引,如果有具有相同选项的索引,则不做任何操作,如果选项发生变化,则替换索引?


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

是否有一种方法来创建或更新MongoDB索引

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

我遇到了同样的问题,我发现克服这个问题的最可靠的方法是捕获异常并显式指定Index name。如果您没有指定索引名称,驱动程序将使用键生成一个名称,即使您可以确定该名称,它也不是真正健壮的,因为它可能随着不同版本的驱动程序而更改。

            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引发的确切原因,但它实际上是有效的。

如果options不包含name属性,则简单地重新抛出异常,但如果指定了名称,则尝试删除索引,然后重新创建索引。

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

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

在2.2.3版本中,索引是通过集合的Indexes属性来管理的。

我已经写了一个扩展方法的索引属性基于Alkampfer's的答案如下:

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

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

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

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

这些天与MongoDB。驱动程序,您应该能够通过以下方式重新创建索引:

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