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