using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Infrastructure.Utils;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Serilog;
namespace adas_core.Infrastructure.Repositories;
///
/// Abstract base repository providing common MongoDB persistence operations for a given document type .
/// Concrete repositories must implement to identify the target collection,
/// and may override , , and
/// to customize schema setup, seeding, and insertion behavior.
///
/// The domain model type persisted in the MongoDB collection.
public abstract class MongoRepository : IMongoRepository
{
///
/// The MongoDB database instance used by the repository.
///
protected readonly IMongoDatabase Db;
private IMongoCollection? _collection;
// protected MongoRepository(IOptions dbSettings)
// {
// Db = MongoDbHostBuilderExtension.GetMongoDb(dbSettings);
// }
///
/// Initializes a new instance of the class using the provided database.
///
/// The MongoDB database instance used to access collections. Must not be .
protected MongoRepository(IMongoDatabase database)
{
Db = database;
}
///
/// When implemented in a derived class, returns the name of the MongoDB collection used to store documents of type .
///
/// The MongoDB collection name as a string.
public abstract string GetCollectionName();
///
/// Gets the underlying for the repository.
/// On first access, ensures the collection exists (creating it if necessary), then triggers asynchronous index creation
/// and initial data loading via and .
///
/// The MongoDB collection of documents.
public IMongoCollection Collection
{
get
{
if (_collection == null)
{
var collectionName = GetCollectionName();
if (!CollectionExists(collectionName)) Db.CreateCollection(collectionName);
_collection = Db.GetCollection(collectionName);
_ = CreateIndexes();
_ = InsertInitialLoad();
}
return _collection;
}
set => _collection = value;
}
///
/// Asynchronously inserts a single document into the collection. Errors are logged and swallowed.
///
/// The document of type to insert.
/// A representing the asynchronous insert operation.
public virtual async Task InsertOneAsync(T obj)
{
try
{
await Collection.InsertOneAsync(obj);
}
catch (Exception ex)
{
Log.Error("An error occurred: {ExMessage}", ex.Message);
}
}
///
/// Asynchronously replaces (or upserts) a single document identified by its _id.
/// Uses with set to
/// so that the document is created if it does not exist. Errors are logged and swallowed.
///
/// The value of the document's _id field.
/// The replacement document of type .
/// A representing the asynchronous replace/upsert operation.
public async Task UpdateOneAsync(ObjectId id, T obj)
{
try
{
var filter = Builders.Filter.Eq("_id", id);
await Collection.ReplaceOneAsync(filter, obj, new ReplaceOptions { IsUpsert = true });
}
catch (Exception ex)
{
Log.Error("Error updating id: {Id}. Exception:{Ex}, stackTrace: {Trace}", id.ToString(), ex.Message,
ex.StackTrace);
}
}
///
/// Asynchronously finds and deletes a single document identified by its _id.
/// Returns the deleted document, or the default value of if not found or on error.
///
/// The value of the document's _id field.
///
/// A representing the asynchronous operation.
/// The task result contains the deleted document, or / default if no document matched or an error occurred.
///
public async Task DeleteAsync(ObjectId id)
{
try
{
var filter = Builders.Filter.Eq("_id", id);
return await Collection.FindOneAndDeleteAsync(filter);
}
catch (Exception ex)
{
Log.Error("An error occurred: {ExMessage}", ex.Message);
return default;
}
}
///
/// Creates the indexes required for the collection. The base implementation is a no-op;
/// derived classes should override this method to define their own indexes.
///
/// A representing the asynchronous index creation operation.
public virtual Task CreateIndexes()
{
return Task.CompletedTask;
}
///
/// Performs an initial data load (seeding) for the collection. The base implementation is a no-op;
/// derived classes should override this method to provide custom seeding logic.
///
/// A representing the asynchronous seeding operation.
public virtual Task InsertInitialLoad()
{
return Task.CompletedTask;
}
///
/// Asynchronously inserts multiple documents into the collection using unordered semantics
/// (a single failed insert does not abort the batch). Errors are logged and swallowed.
///
/// The list of documents of type to insert.
/// A representing the asynchronous bulk insert operation.
public virtual async Task InsertManyAsync(List obj)
{
try
{
InsertManyOptions options = new() { IsOrdered = false };
await Collection.InsertManyAsync(obj, options);
}
catch (Exception ex)
{
Log.Error("An error occurred: {ExMessage}", ex.Message);
}
}
///
/// Asynchronously updates all documents in the collection where the specified field equals ,
/// setting that field to the new . Errors are logged and swallowed.
///
/// The name of the field to match and update.
/// The new value to assign.
/// The existing value to replace.
/// A representing the asynchronous update operation.
protected async Task UpdateManyObjectIdAsync(string nameId, ObjectId id, ObjectId oldId)
{
try
{
var update = Builders.Update.Set(nameId, id);
var filter = Builders.Filter.Eq(nameId, oldId);
await Collection.UpdateManyAsync(filter, update);
}
catch (Exception ex)
{
Log.Error("An error occurred: {ExMessage}", ex.Message);
}
}
///
/// Asynchronously finds and deletes a single document identified by a string _id.
/// Returns the deleted document, or the default value of if not found or on error.
///
/// The string value of the document's _id field.
///
/// A representing the asynchronous operation.
/// The task result contains the deleted document, or / default if no document matched or an error occurred.
///
protected async Task DeleteAsync(string id)
{
try
{
var filter = Builders.Filter.Eq("_id", id);
return await Collection.FindOneAndDeleteAsync(filter);
}
catch (Exception ex)
{
Log.Error("An error occurred: {ExMessage}", ex.Message);
return default;
}
}
///
/// Determines whether a collection with the specified name exists in the current database.
/// Errors are logged and the method returns in that case.
///
/// The name of the collection to check.
///
/// if a collection with the given name exists; otherwise, .
/// Returns when an exception is thrown while querying the database.
///
protected bool CollectionExists(string collectionName)
{
try
{
var filter = new BsonDocument("name", collectionName);
var options = new ListCollectionNamesOptions { Filter = filter };
return Db.ListCollectionNames(options).Any();
}
catch (Exception ex)
{
Log.Error("An error occurred: {ExMessage}", ex.Message);
return false;
}
}
}