Files
xDataService/MongoRepositories/XBaseMongoRepository.cs
T
2024-01-25 04:41:17 +03:30

1098 lines
36 KiB
C#

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 {
/// <summary>
/// Base MongoDb Base Entity Repository Pattern implementation ...
/// Only Used on MongoDb ...
/// </summary>
/// <typeparam name="T">is the Entity type</typeparam>
/// <typeparam name="TKey">is the Entity Key Type</typeparam>
/// <typeparam name="TDbContext">is the DbContext Type</typeparam>
public abstract class XBaseMongoRepository<T, TKey> : IXBaseRepository<T, TKey>
where T : XBaseEntity<TKey> {
//
private readonly string collectionName;
public List<WriteModel<T>> bulkCollection;
public readonly IMongoCollection<T> collection;
private readonly IXKeyGenerator<T, TKey> keyGenerator;
public readonly XDataServiceConfiguration configuration;
private readonly IXBaseRepositoryEvents<T> baseRepositoryEvents;
//
public abstract IMongoQueryable<T> GetFullDbSet ();
//
#region Constructor ...
protected XBaseMongoRepository (
XDataServiceConfiguration configuration,
string collectionName = null,
IXKeyGenerator<T, TKey> keyGenerator = null,
IXBaseRepositoryEvents<T> 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<WriteModel<T>> ();
collection = database.GetCollection<T> (
this.collectionName
);
}
#endregion
//
#region Add ...
public async Task<T> AddAsync (
T item,
bool saveChanges = true
) {
//
// Handle Key ...
item = await HandleKeyAsync (item);
//
// Add Action model to Bulk Collection ...
bulkCollection.Add (new InsertOneModel<T> (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<T> (item));
}
//
// Return result base on action Succeed ...
return isSucceed ?
item :
null;
}
public async Task<T> 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<T> (item));
} else {
//
// Filter ...
var filter = Builders<T>.Filter.Eq (i => i.Id, item.Id);
//
// Add Action model to Bulk Collection ...
bulkCollection
.Add (new ReplaceOneModel<T> (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<T> (item));
} else {
baseRepositoryEvents
.AddEvent (new XBaseEventModel<T> (item));
}
}
//
// Return result base on action Succeed ...
return isSucceed ?
item :
null;
}
public async Task AddRangeAsync (
IEnumerable<T> 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<T> (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<IEnumerable<T>> (null));
}
}
#endregion
//
#region Remove ...
public async Task<T> 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<T>.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<T> (filter, item) { IsUpsert = true });
} else {
//
// Delete ...
// Add Action model to Bulk Collection ...
bulkCollection.Add (new DeleteOneModel<T> (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<T> (item));
}
//
// Return result base on action Succeed ...
return isSucceed ?
item :
null;
}
public async Task<T> 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<T>.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<T> (filter, item) { IsUpsert = true });
} else {
//
// Delete ...
// Add Action model to Bulk Collection ...
bulkCollection.Add (new DeleteOneModel<T> (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<T> (item));
}
//
// Return result base on action Succeed ...
return isSucceed ?
item :
null;
}
public async Task RemoveRangeAsync (
IEnumerable<T> items,
bool softDelete = true,
bool saveChanges = true
) {
//
// loop through items ...
foreach (var item in items) {
//
// Filter ...
var filter = Builders<T>.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<T> (filter, item) { IsUpsert = true });
} else {
//
// Delete ...
// Add Action model to Bulk Collection ...
bulkCollection.Add (new DeleteOneModel<T> (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<IEnumerable<T>> (null));
}
}
#endregion
//
#region Retrieve ...
public IQueryable<T> AsQueryable () {
return collection.AsQueryable ();
}
public async Task<T> 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<IEnumerable<T>> 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<T> FindOneAsync (
Expression<Func<T, bool>> whereClause,
bool ignoreSoftDeleteds = true,
bool containsDetail = false
) {
//
// Generate Where Function ...
var whereFunc = whereClause.Compile ();
//
// Handle Soft Deleted Items ...
Func<T, bool> ignoreSoftDeletedsWhereFunc = null;
if (ignoreSoftDeleteds && configuration.EnableSoftDelete) {
//
Expression<Func<T, bool>> 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<IEnumerable<T>> FindManyAsync (
Expression<Func<T, bool>> whereClause,
bool ignoreSoftDeleteds = true,
bool containsDetail = false
) {
//
// Generate Where Function ...
var whereFunc = whereClause.Compile ();
//
// Handle Soft Deleted Items ...
Func<T, bool> ignoreSoftDeletedsWhereFunc = null;
if (ignoreSoftDeleteds && configuration.EnableSoftDelete) {
//
Expression<Func<T, bool>> ignoreSoftDeletedsWhereClause = x => x.Deleted == false;
ignoreSoftDeletedsWhereFunc = ignoreSoftDeletedsWhereClause.Compile ();
}
//
// Get Enumerable ...
var enumerator = await GetAsyncEnumerable (containsDetail);
//
var result = new List<T> ();
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<XQueryResult<T>> 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<Func<T, bool>> whereClause = i =>
i.PropValuesContains (query.Filter);
var whereFunc = whereClause.Compile ();
//
// Handle Soft Deleted Items ...
Func<T, bool> ignoreSoftDeletedsWhereFunc = null;
if (ignoreSoftDeleteds && configuration.EnableSoftDelete) {
//
Expression<Func<T, bool>> 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<T> ();
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<T, TKey> (
query.SortBy,
query.IsAscending);
}
//
// Generate Result Object ...
var queryResult = new XQueryResult<T> {
Items = filteredItems.AsEnumerable (),
Page = query.Page,
PageSize = query.PageSize,
TotalItems = totalItemsCount,
TotalPages = await PagesCountAsync (
query.PageSize,
totalFilteredItemsCount
),
TotalFilteredItems = totalFilteredItemsCount
};
//
return queryResult;
}
public async Task<XQueryResult<T>> ConditionalQueryAsync (
Expression<Func<T, bool>> 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<Func<T, bool>> whereFilterClause = i =>
i.PropValuesContains (query.Filter);
var whereFilterFunc = whereClause.Compile ();
//
// Handle Soft Deleted Items ...
Func<T, bool> ignoreSoftDeletedsWhereFunc = null;
if (ignoreSoftDeleteds && configuration.EnableSoftDelete) {
//
Expression<Func<T, bool>> 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<T> ();
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<T, TKey> (
query.SortBy,
query.IsAscending);
}
//
// Generate Result Object ...
var queryResult = new XQueryResult<T> {
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<T> UpdateAsync (
TKey id,
T item,
bool saveChanges = true
) {
//
// Filter ...
var filter = Builders<T>.Filter.Eq (i => i.Id, id);
//
// Add Action model to Bulk Collection ...
bulkCollection.Add (new ReplaceOneModel<T> (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<T> (item));
}
//
// Return result base on action Succeed ...
return isSucceed ?
item :
null;
}
public async Task<bool> UpdateRangeAsync (
IEnumerable<T> items,
bool saveChanges = true
) {
//
// loop through items ...
foreach (var item in items) {
//
// Filter ...
var filter = Builders<T>.Filter.Eq (i => i.Id, item.Id);
//
// Add Action model to Bulk Collection ...
bulkCollection.Add (new ReplaceOneModel<T> (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<IEnumerable<T>> (null));
}
//
// Return result base on action Succeed ...
return isSucceed;
}
#endregion
//
#region Count ...
public async Task<int> CountAsync (bool ignoreSoftDeleteds = true) {
//
// Filter ...
var filter = Builders<T>.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<int> 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<bool> 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<int> 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<TKey>.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<TKey>.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<TKey> ();
}
public async Task<T> 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<T>))
.Select (i => (i as InsertOneModel<T>).Document)
.OrderByDescending (nameof (XBaseEntity<TKey>.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<T> items) { }
public void Detach (XQueryResult<T> query) { }
public void Detach (XPageResponse<T> 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<T> GetDbSet (
bool containsDetail = false
) {
//
IMongoQueryable<T> result = null;
if (!containsDetail) {
result = collection
.AsQueryable ();
} else {
result = GetFullDbSet ();
}
//
return result;
}
private IMongoQueryable<T> GetQueryable (
bool containsDetail = false
) {
return GetDbSet (
containsDetail: containsDetail
);
// .AsQueryable ();
}
private async Task<IAsyncCursor<T>> GetAsyncCursor (
bool containsDetail = false
) {
return await GetQueryable (
containsDetail: containsDetail
)
.ToCursorAsync ();
}
private async Task<IAsyncEnumerable<T>> GetAsyncEnumerable (
bool containsDetail = false
) {
return (await GetAsyncCursor (
containsDetail: containsDetail
))
.ToAsyncEnumerable ();
}
#endregion
}
}