using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; 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.MongoRepositories { /// /// Base MongoDb Base Entity Repository Pattern implementation ... /// Only Used on MongoDb ... /// /// is the Entity type /// is the Entity Key Type /// is the DbContext Type public abstract class XBaseMongoRepository : IXBaseRepository where T : XBaseEntity { // private readonly string collectionName; public List> bulkCollection; public readonly IMongoCollection collection; private readonly IXKeyGenerator keyGenerator; public readonly XDataServiceConfiguration configuration; private readonly IXBaseRepositoryEvents baseRepositoryEvents; // public abstract IMongoQueryable GetFullDbSet (); // #region Constructor ... protected XBaseMongoRepository ( 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.baseRepositoryEvents = baseRepositoryEvents; // var client = new MongoClient (configuration.GetMongoDbURI ()); var database = client.GetDatabase (configuration.GetMongoDbDatabase ()); // bulkCollection = new List> (); collection = database.GetCollection ( this.collectionName ); } #endregion // #region Add ... public async Task AddAsync ( T item, bool saveChanges = true ) { // // Handle Key ... item = await HandleKeyAsync (item); // // 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 (); // // 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; } public async Task AddOrUpdateAsync ( T item, bool saveChanges = true ) { // var isExists = await IsExistsAsync (GetKey (item)); if (!isExists) { // // Handle Key ... item = await HandleKeyAsync (item); // // 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 (); // // 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; } public async Task AddRangeAsync ( IEnumerable items, bool saveChanges = true ) { // foreach (var item in items) { // // Handle Key ... var keyHandledItem = await HandleKeyAsync (item); // // Add Action model to Bulk Collection ... bulkCollection.Add (new InsertOneModel (keyHandledItem)); } // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync (); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull () ) { baseRepositoryEvents .AddManyEvent (new XBaseEventModel> (null)); } } #endregion // #region Remove ... public async Task RemoveAsync ( TKey id, bool softDelete = true, bool saveChanges = true ) { // // Retrieve Item ... var item = await GetAsync ( id, ignoreSoftDeleteds : softDelete ); 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 (); // // 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; } public async Task RemoveAsync ( T item, bool softDelete = true, bool saveChanges = true ) { // // Check item Exists ... var isExists = await IsExistsAsync (item.Id); if (item.IsNull ()) { return null; } // // Filter ... var filter = Builders.Filter.Eq (i => i.Id, item.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 (); // // 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; } public async Task RemoveRangeAsync ( IEnumerable items, bool softDelete = true, bool saveChanges = true ) { // // 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 (); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull () ) { baseRepositoryEvents .RemoveManyEvent (new XBaseEventModel> (null)); } } #endregion // #region Retrieve ... public IQueryable AsQueryable () { return collection.AsQueryable (); } public async Task GetAsync ( TKey id, bool ignoreSoftDeleteds = true, bool containsDetail = false ) { // // Retrieve Result ... var result = await FindOneAsync ( whereClause: x => x.Id .ToString () .ToNormalString () == id .ToString () .ToNormalString (), ignoreSoftDeleteds : true, containsDetail : containsDetail ); // await Task.CompletedTask; // // Resturn Result ... return result; } public async Task> GetAllAsync ( bool ignoreSoftDeleteds = true, bool containsDetail = false ) { // var result = GetDbSet (containsDetail: containsDetail) .AsQueryable () .AsEnumerable (); // if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { result = result.Where (i => i.Deleted == false); } // await Task.CompletedTask; // return result; } public async Task FindOneAsync ( Expression> whereClause, bool ignoreSoftDeleteds = true, bool containsDetail = false ) { // // Generate Where Function ... var whereFunc = whereClause.Compile (); // // Handle Soft Deleted Items ... Func ignoreSoftDeletedsWhereFunc = null; if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { // Expression> ignoreSoftDeletedsWhereClause = x => x.Deleted == false; ignoreSoftDeletedsWhereFunc = ignoreSoftDeletedsWhereClause.Compile (); } // // Get Enumerable ... var enumerator = await GetAsyncEnumerable (containsDetail); // T result = null; await foreach (var entity in enumerator) { // var isApproved = whereFunc (entity) && (ignoreSoftDeletedsWhereFunc.IsNull () ? true : ignoreSoftDeletedsWhereFunc (entity)); if (isApproved) { // result = entity; break; } } // return result; } public async Task> FindManyAsync ( Expression> whereClause, bool ignoreSoftDeleteds = true, bool containsDetail = false ) { // // Generate Where Function ... var whereFunc = whereClause.Compile (); // // Handle Soft Deleted Items ... Func ignoreSoftDeletedsWhereFunc = null; if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { // Expression> ignoreSoftDeletedsWhereClause = x => x.Deleted == false; ignoreSoftDeletedsWhereFunc = ignoreSoftDeletedsWhereClause.Compile (); } // // Get Enumerable ... var enumerator = await GetAsyncEnumerable (containsDetail); // var result = new List (); await foreach (var entity in enumerator) { // var isApproved = whereFunc (entity) && (ignoreSoftDeletedsWhereFunc.IsNull () ? true : ignoreSoftDeletedsWhereFunc (entity)); if (isApproved) { result.Add (entity); } } // return result.AsEnumerable (); } public async Task> QueryAsync ( XQuery query, bool ignoreSoftDeleteds = true ) { // if (query.PageSize < configuration .PagingConfiguration .MinAvailablePageSize) { query.PageSize = configuration .PagingConfiguration .DefaultPageSize; } // if (query.PageSize > configuration .PagingConfiguration .MaxAvailablePageSize) { query.PageSize = configuration .PagingConfiguration .MaxAvailablePageSize; } // // Where Filter Handler ... Expression> whereClause = i => i.PropValuesContains (query.Filter); var whereFunc = whereClause.Compile (); // // Handle Soft Deleted Items ... Func ignoreSoftDeletedsWhereFunc = null; if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { // Expression> ignoreSoftDeletedsWhereClause = x => x.Deleted == false; ignoreSoftDeletedsWhereFunc = ignoreSoftDeletedsWhereClause.Compile (); } // // Get Enumerable ... var enumerator = await GetAsyncEnumerable ( containsDetail: query.ContainsDetail ); // // Count Total Items ... var totalItemsCount = GetQueryable ( containsDetail: query.ContainsDetail ) .Count (); // var filteredItems = new List (); await foreach (var entity in enumerator) { // var isApproved = (query.Filter .IsNullOrEmpty () ? true : whereFunc (entity) ) && (ignoreSoftDeletedsWhereFunc.IsNull () ? true : ignoreSoftDeletedsWhereFunc (entity)); if (isApproved) { filteredItems.Add (entity); } } // // Count Filtered Items ... int totalFilteredItemsCount = filteredItems.Count (); // // Apply Paging ... var items = filteredItems .ApplyPaging ( query.Page, query.PageSize); // // Apply Sorting ... if (!query.SortBy.IsNullOrEmpty ()) { items = items .ApplySorting ( query.SortBy, query.IsAscending); } // // Generate Result Object ... var queryResult = new XQueryResult { Items = filteredItems.AsEnumerable (), Page = query.Page, PageSize = query.PageSize, TotalItems = totalItemsCount, TotalPages = await PagesCountAsync ( query.PageSize, totalFilteredItemsCount ), TotalFilteredItems = totalFilteredItemsCount }; // return queryResult; } public async Task> ConditionalQueryAsync ( Expression> whereClause, XQuery query, bool ignoreSoftDeleteds = true ) { // if (query.PageSize < configuration .PagingConfiguration .MinAvailablePageSize) { query.PageSize = configuration .PagingConfiguration .DefaultPageSize; } // if (query.PageSize > configuration .PagingConfiguration .MaxAvailablePageSize) { query.PageSize = configuration .PagingConfiguration .MaxAvailablePageSize; } // // Generate Where Func ... var whereFunc = whereClause.Compile (); // // Where Filter Handler ... Expression> whereFilterClause = i => i.PropValuesContains (query.Filter); var whereFilterFunc = whereClause.Compile (); // // Handle Soft Deleted Items ... Func ignoreSoftDeletedsWhereFunc = null; if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { // Expression> ignoreSoftDeletedsWhereClause = x => x.Deleted == false; ignoreSoftDeletedsWhereFunc = ignoreSoftDeletedsWhereClause.Compile (); } // // Get Enumerable ... var enumerator = await GetAsyncEnumerable ( containsDetail: query.ContainsDetail ); // // Count Total Items ... var totalItemsCount = GetQueryable ( containsDetail: query.ContainsDetail ) .Count (); // var filteredItems = new List (); await foreach (var entity in enumerator) { // var isApproved = whereFunc (entity) && (query.Filter .IsNullOrEmpty () ? true : whereFilterFunc (entity) ) && (ignoreSoftDeletedsWhereFunc.IsNull () ? true : ignoreSoftDeletedsWhereFunc (entity)); if (isApproved) { filteredItems.Add (entity); } } // // Count Filtered Items ... int totalFilteredItemsCount = filteredItems.Count (); // // Apply Paging ... var items = filteredItems .ApplyPaging ( query.Page, query.PageSize); // // Apply Sorting ... if (!query.SortBy.IsNullOrEmpty ()) { items = items .ApplySorting ( query.SortBy, query.IsAscending); } // // Generate Result Object ... var queryResult = new XQueryResult { Items = filteredItems.AsEnumerable (), Page = query.Page, PageSize = query.PageSize, TotalItems = totalItemsCount, TotalPages = await PagesCountAsync ( query.PageSize, totalFilteredItemsCount ), TotalFilteredItems = totalFilteredItemsCount }; // return queryResult; } #endregion // #region Update ... public async Task UpdateAsync ( TKey id, T item, bool saveChanges = true ) { // // 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 (); // // 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; } public async Task UpdateRangeAsync ( IEnumerable items, bool saveChanges = true ) { // // loop through items ... foreach (var item in items) { // // 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 (); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull () ) { baseRepositoryEvents .UpdateManyEvent (new XBaseEventModel> (null)); } // // Return result base on action Succeed ... return isSucceed; } #endregion // #region Count ... public async Task CountAsync (bool ignoreSoftDeleteds = true) { // // Filter ... var filter = Builders.Filter.Eq (i => i.Deleted, false); // var result = ignoreSoftDeleteds && configuration.EnableSoftDelete ? (int) GetDbSet () .Count (x => filter.Inject ()) : (int) GetDbSet () .Count (); // await Task.CompletedTask; // return result; } public async Task PagesCountAsync ( int pageSize, int? totalItems = null ) { // int count = totalItems.HasValue ? totalItems.Value : await CountAsync (); int pagesCount = count / pageSize; // if (count % pageSize > 0) { pagesCount++; } // return pagesCount; } #endregion // #region Exists ... public async Task IsExistsAsync (TKey id, bool ignoreSoftDeleteds = true) { // var result = await FindOneAsync (i => GetKey (i) .ToString () == id .ToString (), ignoreSoftDeleteds : ignoreSoftDeleteds, containsDetail : false ); // return !result.IsNull (); } #endregion // #region Unit Of Work ... public async Task SaveChangesAsync () { // try { // // Write all Bulk Models which stored inside bulkCollection ... var result = await collection .BulkWriteAsync (bulkCollection); // // 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 Keys ... public void SetKey ( ref T item, TKey id ) { // var props = item.GetType ().GetProperties (); var keyProp = props.FirstOrDefault (p => p.Name == nameof (XBaseEntity.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); } public TKey GetKey (T item) { // var props = item.GetType ().GetProperties (); var keyProp = props.FirstOrDefault (p => p.Name == nameof (XBaseEntity.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 (); } public async Task HandleKeyAsync (T item) { // var keyType = typeof (TKey); // // Handle Guid Key Type ... if (!keyGenerator.IsNull () && keyGenerator.IsEmpty (item.Id) ) { // var newKey = await keyGenerator.GenerateKey (this); // // InCrease Key if Type of TKey is Int and BulkDocs Contaisn Items ... if (keyType == typeof (int)) { // var lastBulkedInsertedItem = bulkCollection .Where (i => i.GetType () == typeof (InsertOneModel)) .Select (i => (i as InsertOneModel).Document) .OrderByDescending (nameof (XBaseEntity.Id)) .FirstOrDefault (); // // Renew Key if Exists inside bulkCollection ... if (!lastBulkedInsertedItem.IsNull ()) { // var increasedKey = (Convert.ToInt32 (lastBulkedInsertedItem.Id)) + 1; // newKey = Convert.ChangeType ( increasedKey.ToDynamicObject (), typeof (TKey) ); } } // SetKey (ref item, newKey); } // return item; } #endregion // #region Detach ... public void Detach (T item) { } public void Detach (IEnumerable items) { } public void Detach (XQueryResult query) { } public void Detach (XPageResponse page) { } #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 ... private IMongoQueryable GetDbSet ( bool containsDetail = false ) { // IMongoQueryable result = null; if (!containsDetail) { result = collection .AsQueryable (); } else { result = GetFullDbSet (); } // return result; } private IMongoQueryable GetQueryable ( bool containsDetail = false ) { return GetDbSet ( containsDetail: containsDetail ); // .AsQueryable (); } private async Task> GetAsyncCursor ( bool containsDetail = false ) { return await GetQueryable ( containsDetail: containsDetail ) .ToCursorAsync (); } private async Task> GetAsyncEnumerable ( bool containsDetail = false ) { return (await GetAsyncCursor ( containsDetail: containsDetail )) .ToAsyncEnumerable (); } #endregion } }