109 lines
6.4 KiB
C#
109 lines
6.4 KiB
C#
using MongoDB.Bson;
|
|
using MongoDB.Bson.Serialization;
|
|
using MongoDB.Driver;
|
|
|
|
namespace adas_core.Infrastructure.Utils;
|
|
|
|
/// <summary>
|
|
/// Provides utility methods for interacting with MongoDB.
|
|
/// </summary>
|
|
public class MongoUtils
|
|
{
|
|
/// <summary>
|
|
/// Ensures that the indexes defined in <paramref name="expectedIndexes"/> are present on the <see cref="IMongoCollection{TDocument}"/> supplied via <paramref name="collection"/>. For each expected index, an existing index with the same key specification is left unchanged when its options match, or dropped and recreated when they differ (except for the built-in <c>_id_</c> index, which is never dropped); if no matching index exists the index is created, and MongoDB conflicts reported with code 85 are logged and tolerated.
|
|
/// </summary>
|
|
/// <param name="collection">The <see cref="IMongoCollection{TDocument}"/> whose indexes are inspected and reconciled.</param>
|
|
/// <param name="expectedIndexes">The set of <see cref="CreateIndexModel{TDocument}"/> definitions that the collection should contain.</param>
|
|
/// <!-- aidoc:v1 sig=e61054a body=29c4862 -->
|
|
public static async Task EnsureIndexes<TDocument>(IMongoCollection<TDocument> collection,
|
|
List<CreateIndexModel<TDocument>> expectedIndexes)
|
|
{
|
|
var existingIndexes = await (await collection.Indexes.ListAsync()).ToListAsync();
|
|
var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer<TDocument>();
|
|
var serializerRegistry = BsonSerializer.SerializerRegistry;
|
|
var renderArgs = new RenderArgs<TDocument>(documentSerializer, serializerRegistry);
|
|
foreach (var expectedIndexModel in expectedIndexes)
|
|
{
|
|
var expectedIndexKeys = expectedIndexModel.Keys
|
|
.Render(renderArgs).ToString();
|
|
var existingIndexWithSameKeys = existingIndexes.FirstOrDefault(index =>
|
|
{
|
|
var keys = index.Elements.FirstOrDefault(e => e.Name == "key").Value?.ToString();
|
|
return keys == expectedIndexKeys;
|
|
});
|
|
|
|
if (existingIndexWithSameKeys != null)
|
|
{
|
|
// Existe un índice con las mismas claves, verificar las opciones
|
|
if (!IndexOptionsMatch(expectedIndexModel, existingIndexWithSameKeys, renderArgs))
|
|
{
|
|
// Las opciones no coinciden, eliminar el índice existente y crear el nuevo
|
|
var indexName = existingIndexWithSameKeys.Elements.FirstOrDefault(e => e.Name == "name").Value
|
|
?.AsString;
|
|
if (!string.IsNullOrEmpty(indexName) && indexName != "_id_")
|
|
try
|
|
{
|
|
await collection.Indexes.DropOneAsync(indexName);
|
|
await collection.Indexes.CreateOneAsync(expectedIndexModel);
|
|
Console.WriteLine($"Info: Índice con claves '{expectedIndexKeys}' actualizado.");
|
|
}
|
|
catch (MongoCommandException ex) when (ex.Code == 85)
|
|
{
|
|
Console.WriteLine(
|
|
$"Warning: No se pudo actualizar el índice con claves '{expectedIndexKeys}'. Error: {ex.Message}");
|
|
}
|
|
else
|
|
try
|
|
{
|
|
await collection.Indexes.CreateOneAsync(expectedIndexModel);
|
|
}
|
|
catch (MongoCommandException ex) when (ex.Code == 85)
|
|
{
|
|
Console.WriteLine(
|
|
$"Warning: El índice con claves '{expectedIndexKeys}' ya existe con diferentes opciones y no se pudo actualizar automáticamente.");
|
|
}
|
|
}
|
|
// Si las opciones coinciden, no se hace nada
|
|
}
|
|
else
|
|
{
|
|
// No existe un índice con estas claves, crear el nuevo
|
|
try
|
|
{
|
|
await collection.Indexes.CreateOneAsync(expectedIndexModel);
|
|
}
|
|
catch (MongoCommandException ex) when (ex.Code == 85)
|
|
{
|
|
Console.WriteLine($"Warning: El índice con claves '{expectedIndexKeys}' ya existe.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the options of an existing index match the expected <see cref="CreateIndexModel{TDocument}"/> options by comparing the <c>unique</c>, <c>background</c>, and <c>partialFilterExpression</c> values.
|
|
/// Missing boolean fields in <paramref name="existingIndex"/> are treated as <c>false</c>.
|
|
/// </summary>
|
|
/// <typeparam name="TDocument">The type of the document the index is defined on.</typeparam>
|
|
/// <param name="expectedIndexModel">The <see cref="CreateIndexModel{TDocument}"/> containing the expected <c>Unique</c>, <c>Background</c>, and <c>PartialFilterExpression</c> values.</param>
|
|
/// <param name="existingIndex">The <see cref="BsonDocument"/> representing the existing index whose options should be checked.</param>
|
|
/// <param name="renderArgs">The <see cref="RenderArgs{TDocument}"/> used to render the expected partial filter expression for comparison.</param>
|
|
/// <returns><c>true</c> if the unique flag, background flag, and rendered partial filter expression all match the expected values; otherwise <c>false</c>.</returns>
|
|
/// <!-- aidoc:v1 sig=04d4638 body=0088af8 -->
|
|
private static bool IndexOptionsMatch<TDocument>(CreateIndexModel<TDocument> expectedIndexModel,
|
|
BsonDocument existingIndex, RenderArgs<TDocument> renderArgs)
|
|
{
|
|
var expectedIndexOptions = expectedIndexModel.Options;
|
|
|
|
var unique = existingIndex.Elements.FirstOrDefault(e => e.Name == "unique").Value?.AsBoolean ?? false;
|
|
var background = existingIndex.Elements.FirstOrDefault(e => e.Name == "background").Value?.AsBoolean ?? false;
|
|
var partialFilter = existingIndex.Elements.FirstOrDefault(e => e.Name == "partialFilterExpression").Value
|
|
?.ToString();
|
|
var expectedPartialFilter = expectedIndexOptions.PartialFilterExpression
|
|
?.Render(renderArgs).ToString();
|
|
|
|
return unique == expectedIndexOptions.Unique &&
|
|
background == expectedIndexOptions.Background &&
|
|
partialFilter == expectedPartialFilter;
|
|
}
|
|
} |