using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Query; using MongoDB.Driver; using MongoDB.Driver.Linq; using xCommons.Extensions; using xDataService.Configuration; using xDataService.Extensions; using xDataService.Interfaces; using xDataService.Models; using xModels.Base; using xModels.Dtos; namespace xDataService.Providers { /// /// a Base Repository Pattern Implementation Specially for Mongo DBs using /// EF Core Capabilities ... /// /// /// public abstract class XBaseMongoRepository : IXBaseRepository where T : XBaseEntity { // #region Properties ... private readonly string collectionName; public List> bulkCollection; public readonly IMongoCollection collection; private readonly IXKeyGenerator keyGenerator; public readonly XDataBaseConfiguration dbConfiguration; public readonly XDataServiceConfiguration configuration; private readonly IXBaseRepositoryEvents baseRepositoryEvents; #endregion // #region Constructor ... protected XBaseMongoRepository( XDataBaseConfiguration dbConfiguration, XDataServiceConfiguration configuration, string collectionName = null, IXKeyGenerator keyGenerator = null, IXBaseRepositoryEvents baseRepositoryEvents = null ) { // this.keyGenerator = keyGenerator; this.configuration = configuration; this.collectionName = collectionName .IsNullOrEmpty() ? typeof(T).Name : collectionName; this.dbConfiguration = dbConfiguration; this.baseRepositoryEvents = baseRepositoryEvents; // var client = new MongoClient(dbConfiguration.GetMongoDbURI()); var database = client.GetDatabase(dbConfiguration.GetMongoDbDatabase()); // bulkCollection = new List>(); collection = database.GetCollection( this.collectionName ); } #endregion // #region Actions ... // #region Add ... /// /// add a new Entity ... /// /// /// /// /// public async Task AddAsync( T item, bool saveChanges = true, CancellationToken cancellationToken = default ) { // // Handle Key ... item = await HandleKeyAsync( item: item, cancellationToken: cancellationToken ); // // Add Action model to Bulk Collection ... bulkCollection.Add(new InsertOneModel(item)); // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(cancellationToken); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .AddEvent(new XBaseEventModel(item)); } // // Return result base on action Succeed ... return isSucceed ? item : null; } /// /// add or update an Entity (add if not exists/update if exists) ... /// /// /// /// /// public async Task AddOrUpdateAsync( T item, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var isExists = await IsExistsAsync( id: GetKey(item), ignoreSoftDeleteds: true, cancellationToken: cancellationToken ); if (!isExists) { // // Handle Key ... item = await HandleKeyAsync( item: item, cancellationToken: cancellationToken ); // // Add Action model to Bulk Collection ... bulkCollection .Add(new InsertOneModel(item)); } else { // // Filter ... var filter = Builders.Filter.Eq(i => i.Id, item.Id); // // Add Action model to Bulk Collection ... bulkCollection .Add(new ReplaceOneModel(filter, item) { IsUpsert = true }); } // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(cancellationToken); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // if (isExists) { // baseRepositoryEvents .UpdateEvent(new XBaseEventModel(item)); } else { // baseRepositoryEvents .AddEvent(new XBaseEventModel(item)); } } // // Return result base on action Succeed ... return isSucceed ? item : null; } /// /// add a range of new Entities ... /// /// /// /// /// public async Task AddRangeAsync( IEnumerable items, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var isValid = !items.IsNull() && items.HasChild(); if (!isValid) { return; } // foreach (var item in items) { // await AddAsync( item: item, saveChanges: saveChanges, cancellationToken: cancellationToken ); } // // Notify Event ... if ( isValid && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .AddManyEvent(new XBaseEventModel>(null)); } } #endregion // #region Update ... /// /// Update an Entity values ... /// /// /// /// /// /// public async Task UpdateAsync( TKey id, T item, bool saveChanges = true, CancellationToken cancellationToken = default ) { // // Filter ... var filter = Builders.Filter.Eq(i => i.Id, id); // // Add Action model to Bulk Collection ... bulkCollection.Add(new ReplaceOneModel(filter, item) { IsUpsert = false }); // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(cancellationToken); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .UpdateEvent(new XBaseEventModel(item)); } // // Return result base on action Succeed ... return isSucceed ? item : null; } /// /// Update a range of Entities ... /// /// /// /// /// public async Task UpdateRangeAsync( IEnumerable items, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var result = false; var isValid = !items.IsNull() && items.HasChild(); if (!isValid) { return result; } // foreach (var item in items) { // var updated = await UpdateAsync( item: item, id: item.Id, saveChanges: saveChanges, cancellationToken: cancellationToken ); if (!updated.IsNullOrDefault() && !result) { result = true; } } // return result; } #endregion // #region Remove ... /// /// remove an Entity by it's Id ... /// /// /// /// /// /// public async Task RemoveAsync( TKey id, bool softDelete = true, bool saveChanges = true, CancellationToken cancellationToken = default ) { // // Retrieve Item ... var item = await GetAsync( id: id, ignoreSoftDeleteds: true, cancellationToken: cancellationToken ); if (item.IsNull()) { return null; } // // Filter ... var filter = Builders.Filter.Eq(i => i.Id, id); // // Check SoftDelete ... if (softDelete && configuration.EnableSoftDelete) { // // Set Soft Delete ... item.Deleted = true; // // Update ... // Add Action model to Bulk Collection ... bulkCollection.Add(new ReplaceOneModel(filter, item) { IsUpsert = true }); } else { // // Delete ... // Add Action model to Bulk Collection ... bulkCollection.Add(new DeleteOneModel(filter)); } // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(cancellationToken); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .RemoveEvent(new XBaseEventModel(item)); } // // Return result base on action Succeed ... return isSucceed ? item : null; } /// /// remove an Entity ... /// /// /// /// /// /// public async Task RemoveAsync( T item, bool softDelete = true, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var result = await RemoveAsync( id: item.Id, softDelete: softDelete, saveChanges: saveChanges, cancellationToken: cancellationToken ); // return result; } /// /// remove a range of exists Entities ... /// /// /// /// /// /// public async Task RemoveRangeAsync( IEnumerable items, bool softDelete = true, bool saveChanges = true, CancellationToken cancellationToken = default ) { // // loop through items ... foreach (var item in items) { // // Filter ... var filter = Builders.Filter.Eq(i => i.Id, item.Id); // // Check Soft Delete ... if (softDelete && configuration.EnableSoftDelete) { // // Set Soft Delete ... item.Deleted = true; // // Update ... // Add Action model to Bulk Collection ... bulkCollection.Add(new ReplaceOneModel(filter, item) { IsUpsert = true }); } else { // // Delete ... // Add Action model to Bulk Collection ... bulkCollection.Add(new DeleteOneModel(filter)); } } // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(cancellationToken); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .RemoveManyEvent(new XBaseEventModel>(null)); } } #endregion // #region Count ... /// /// count all exists Entities ... /// /// /// /// public async Task CountAsync( bool ignoreSoftDeleteds = true, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, includeBuilder: null, predicate: predicator ) .CountAsync(cancellationToken); // return result; } /// /// count all exists Entities Pages by providing page size ... /// /// /// /// /// /// public async Task PagesCountAsync( int pageSize, int? totalItems = null, bool ignoreSoftDeleteds = true, CancellationToken cancellationToken = default ) { // int count = totalItems.HasValue ? totalItems.Value : await CountAsync( cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); int pagesCount = count / pageSize; // if (count % pageSize > 0) { pagesCount++; } // return pagesCount; } #endregion // #region Retrieve ... /// /// retrieve whole items as queryable ... /// /// a flag for Tracking behaviour /// an Expression for Filter Items ... /// an Order Builder expression for Ordering Query ... /// an Include Builder expression for Including Navigation Properties ... /// public IQueryable AsQueryable( bool asNoTracking = true, Expression> predicate = null, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null ) { // var result = collection .AsQueryable(); // // Apply Predicate ... if (!predicate.IsNull()) { result = result .Where(predicate); } // // Apply Includes ... if (!includeBuilder.IsNull()) { // // Since Mongo Queryable doesnt Support Includes ... // we ignore this ... result = (IMongoQueryable)includeBuilder(result); } // // Apply Ordering ... if (!orderBuilder.IsNull()) { result = (IMongoQueryable)orderBuilder(result); } // return result; } /// /// retrieve an Entity by it's Id ... /// /// /// /// /// /// public async Task GetAsync( TKey id, bool ignoreSoftDeleteds = true, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, predicate: predicator, includeBuilder: includeBuilder ) .Where(x => GetKey(x).ToString() == id.ToString()) .FirstOrDefaultAsync(cancellationToken); // return result; } /// /// retrieve all exists Entities ... /// /// /// /// /// /// public async Task> GetAllAsync( bool ignoreSoftDeleteds = true, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, predicate: predicator, orderBuilder: orderBuilder, includeBuilder: includeBuilder ) .ToListAsync(cancellationToken); // return result; } /// /// retrieve all exists Entities /// as Async Enumerable ... /// /// /// /// /// public IAsyncEnumerable GetAllAsAsyncEnumerable( bool ignoreSoftDeleteds = true, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = AsQueryable( asNoTracking: true, predicate: predicator, orderBuilder: orderBuilder, includeBuilder: includeBuilder ) .AsAsyncEnumerable(); // return result; } /// /// find an Entity by providing a Conditional Expression ... /// /// /// /// /// /// public async Task FindOneAsync( Expression> predicate, bool ignoreSoftDeleteds = true, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, predicate: predicator, includeBuilder: includeBuilder ) .Where(predicate) .FirstOrDefaultAsync(cancellationToken); // return result; } /// /// find a collection of Entities by proving a Conditional Expression ... /// /// /// /// /// /// /// public async Task> FindManyAsync( Expression> predicate, bool ignoreSoftDeleteds = true, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, predicate: predicator, orderBuilder: orderBuilder, includeBuilder: includeBuilder ) .Where(predicate) .ToListAsync(cancellationToken); // return result; } /// /// retrieve Entities based on XQuery Pagination structure ... /// /// /// /// /// /// /// /// public async Task> QueryAsync( XQuery query, bool ignoreSoftDeleteds = true, Expression> predicate = null, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Normalize Query ... query = query.NormalizeQuery(configuration); // var items = await GetAllAsync(); var totalItemsCount = items.Count(); // // Apply Filter ... if (!query.Filter.IsNullOrEmpty()) { // items = items .ApplyFilter(query.Filter); } int filteredItemsCount = items.Count(); // // Count Pages ... var totalPagesCount = query.CountPages(totalItemsCount); var filteredPagesCount = query.CountPages(filteredItemsCount); // // Apply Paging and Sorting ... if (totalItemsCount > 0 && filteredItemsCount > 0) { // // Apply Sorting ... items = items .ToList() .ApplySorting( query.SortBy, query.IsAscending ); // // Apply Paging ... items = items .ToList() .ApplyPaging( query.Page, query.PageSize ); } // // Prepare Result ... var result = new XQueryResult { Page = query.Page, Items = items.ToList(), PageSize = query.PageSize, TotalPages = totalPagesCount, TotalItems = totalItemsCount, TotalFilteredPages = filteredPagesCount, TotalFilteredItems = filteredItemsCount }; // return result; } #endregion // #region Exists ... /// /// Check an Entity exists or not ... /// /// /// /// /// public async Task IsExistsAsync( TKey id, bool ignoreSoftDeleteds = true, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, includeBuilder: null, predicate: predicator ) .AnyAsync( cancellationToken: cancellationToken, predicate: x => GetKey(x).ToString() == id.ToString()); // return result; } #endregion // #region Unit Of Work ... /// /// Save all unsaved Transactions on DbContext ... /// used fo Unit Of Works Design Pattern ... /// /// /// public async Task SaveChangesAsync( CancellationToken cancellationToken = default ) { // try { // // Write all Bulk Models which stored inside bulkCollection ... var result = await collection .BulkWriteAsync( options: null, requests: bulkCollection, cancellationToken: cancellationToken ); // // Clear Bulk Collection ... bulkCollection.Clear(); // // Return number of Modified Documents ... var resultCount = (int)result.ModifiedCount + (int)result.InsertedCount + (int)result.DeletedCount; // return resultCount; } catch (Exception ex) { // // Log Thrown Exception ... Console.WriteLine($"XMongo Repository Exception: {ex.Message} ..."); // // Return less than zero value ... return -1; } } #endregion // #region Key ... /// /// Retrieve Key of Entity ... /// /// /// public TKey GetKey(T item) { // var props = item.GetType().GetProperties(); var keyProp = props.FirstOrDefault(p => p.Name == "Id"); // var keyString = string.Empty; if (keyProp.IsNull()) { keyString = string.Empty; } else { keyString = keyProp.GetValue(item).ToString(); } // if (keyString.IsNullOrEmpty()) { return default(TKey); } // // Prevent Deserializing issues throug JsonReader ... if (keyString.IsGuid() && typeof(TKey) == typeof(Guid)) { return item.Id; } // return keyString.FromJSON(); } /// /// Set Key of Entity ... /// /// /// public void SetKey( ref T item, TKey id ) { // var props = item.GetType().GetProperties(); var keyProp = props.FirstOrDefault(p => p.Name == "Id"); if (keyProp.IsNull()) { return; } // Type t = Nullable.GetUnderlyingType(keyProp.PropertyType) ?? keyProp.PropertyType; object safeValue = (id == null) ? null : Convert.ChangeType(id, t); keyProp.SetValue(item, safeValue, null); } /// /// Handle Checking Key ... /// /// /// /// public async Task HandleKeyAsync( T item, CancellationToken cancellationToken = default ) { // var keyType = typeof(TKey); // // Handle Guid Key Type ... // Since EFCore has AutoIncrement on int Ids, there is no need to handle int Key types ... if ( ( keyType == typeof(Guid) || keyType == typeof(string) ) && keyGenerator.IsEmpty(item.Id) ) { // var newKey = await keyGenerator.GenerateKey(this); // SetKey(ref item, newKey); } // return item; } #endregion // #region Detach ... /// /// Detach an Entity ... /// /// public void Detach(T item) { } /// /// Detach an Enumerable of Entities ... /// /// public void Detach(IEnumerable items) { } /// /// Detach a Query Result of Entity ... /// /// public void Detach(XQueryResult query) { } #endregion #endregion // #region Others ... public void Dispose() { } public string GetPropValues(T item) { // var props = item.GetType().GetProperties(); var vals = props.Select(p => p.GetValue(p.Name)); // return vals.ToJSON(); } #endregion // #region Private ... #endregion } }