1158 lines
35 KiB
C#
1158 lines
35 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// a Base Repository Pattern Implementation Specially for Mongo DBs using
|
|
/// EF Core Capabilities ...
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <typeparam name="TKey"></typeparam>
|
|
public abstract class XBaseMongoRepository<T, TKey> : IXBaseRepository<T, TKey>
|
|
where T : XBaseEntity<TKey>
|
|
{
|
|
//
|
|
#region Properties ...
|
|
private readonly string collectionName;
|
|
public List<WriteModel<T>> bulkCollection;
|
|
public readonly IMongoCollection<T> collection;
|
|
private readonly IXKeyGenerator<T, TKey> keyGenerator;
|
|
public readonly XDataBaseConfiguration dbConfiguration;
|
|
public readonly XDataServiceConfiguration configuration;
|
|
private readonly IXBaseRepositoryEvents<T> baseRepositoryEvents;
|
|
#endregion
|
|
|
|
//
|
|
#region Constructor ...
|
|
protected XBaseMongoRepository(
|
|
XDataBaseConfiguration dbConfiguration,
|
|
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.dbConfiguration = dbConfiguration;
|
|
this.baseRepositoryEvents = baseRepositoryEvents;
|
|
|
|
//
|
|
var client = new MongoClient(dbConfiguration.GetMongoDbURI());
|
|
var database = client.GetDatabase(dbConfiguration.GetMongoDbDatabase());
|
|
|
|
//
|
|
bulkCollection = new List<WriteModel<T>>();
|
|
collection = database.GetCollection<T>(
|
|
this.collectionName
|
|
);
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Actions ...
|
|
//
|
|
#region Add ...
|
|
/// <summary>
|
|
/// add a new Entity ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <param name="saveChanges"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<T> 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<T>(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<T>(item));
|
|
}
|
|
|
|
//
|
|
// Return result base on action Succeed ...
|
|
return isSucceed ?
|
|
item :
|
|
null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// add or update an Entity (add if not exists/update if exists) ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <param name="saveChanges"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<T> 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<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(cancellationToken);
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// add a range of new Entities ...
|
|
/// </summary>
|
|
/// <param name="items"></param>
|
|
/// <param name="saveChanges"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task AddRangeAsync(
|
|
IEnumerable<T> 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<IEnumerable<T>>(null));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Update ...
|
|
/// <summary>
|
|
/// Update an Entity values ...
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <param name="item"></param>
|
|
/// <param name="saveChanges"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<T> UpdateAsync(
|
|
TKey id,
|
|
T item,
|
|
bool saveChanges = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// 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(cancellationToken);
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update a range of Entities ...
|
|
/// </summary>
|
|
/// <param name="items"></param>
|
|
/// <param name="saveChanges"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<bool> UpdateRangeAsync(
|
|
IEnumerable<T> 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 ...
|
|
/// <summary>
|
|
/// remove an Entity by it's Id ...
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <param name="softDelete"></param>
|
|
/// <param name="saveChanges"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<T> 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<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(cancellationToken);
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// remove an Entity ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <param name="softDelete"></param>
|
|
/// <param name="saveChanges"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<T> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// remove a range of exists Entities ...
|
|
/// </summary>
|
|
/// <param name="items"></param>
|
|
/// <param name="softDelete"></param>
|
|
/// <param name="saveChanges"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task RemoveRangeAsync(
|
|
IEnumerable<T> items,
|
|
bool softDelete = true,
|
|
bool saveChanges = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// 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(cancellationToken);
|
|
|
|
//
|
|
// set isSucceed Value based on Changes ...
|
|
isSucceed = qResult > 0;
|
|
}
|
|
|
|
//
|
|
// Notify Event ...
|
|
if (
|
|
isSucceed &&
|
|
!baseRepositoryEvents.IsNull()
|
|
)
|
|
{
|
|
//
|
|
baseRepositoryEvents
|
|
.RemoveManyEvent(new XBaseEventModel<IEnumerable<T>>(null));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Count ...
|
|
/// <summary>
|
|
/// count all exists Entities ...
|
|
/// </summary>
|
|
/// <param name="ignoreSoftDeleteds"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<int> CountAsync(
|
|
bool ignoreSoftDeleteds = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<T, bool>> predicator = null;
|
|
if (ignoreSoftDeleteds)
|
|
{
|
|
predicator = x => !x.Deleted;
|
|
}
|
|
var result = await AsQueryable(
|
|
asNoTracking: true,
|
|
orderBuilder: null,
|
|
includeBuilder: null,
|
|
predicate: predicator
|
|
)
|
|
.CountAsync(cancellationToken);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// count all exists Entities Pages by providing page size ...
|
|
/// </summary>
|
|
/// <param name="pageSize"></param>
|
|
/// <param name="totalItems"></param>
|
|
/// <param name="ignoreSoftDeleteds"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<int> 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 ...
|
|
/// <summary>
|
|
/// retrieve whole items as queryable ...
|
|
/// </summary>
|
|
/// <param name="asNoTracking">a flag for Tracking behaviour</param>
|
|
/// <param name="predicate">an Expression for Filter Items ...</param>
|
|
/// <param name="orderBuilder">an Order Builder expression for Ordering Query ...</param>
|
|
/// <param name="includeBuilder">an Include Builder expression for Including Navigation Properties ...</param>
|
|
/// <returns></returns>
|
|
public IQueryable<T> AsQueryable(
|
|
bool asNoTracking = true,
|
|
Expression<Func<T, bool>> predicate = null,
|
|
Func<IQueryable<T>, IOrderedQueryable<T>> orderBuilder = null,
|
|
Func<IQueryable<T>, IIncludableQueryable<T, object>> 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<T>)includeBuilder(result);
|
|
}
|
|
|
|
//
|
|
// Apply Ordering ...
|
|
if (!orderBuilder.IsNull())
|
|
{
|
|
result = (IMongoQueryable<T>)orderBuilder(result);
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// retrieve an Entity by it's Id ...
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <param name="ignoreSoftDeleteds"></param>
|
|
/// <param name="includeBuilder"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<T> GetAsync(
|
|
TKey id,
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<T>, IIncludableQueryable<T, object>> includeBuilder = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<T, bool>> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// retrieve all exists Entities ...
|
|
/// </summary>
|
|
/// <param name="ignoreSoftDeleteds"></param>
|
|
/// <param name="orderBuilder"></param>
|
|
/// <param name="includeBuilder"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<T>> GetAllAsync(
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<T>, IOrderedQueryable<T>> orderBuilder = null,
|
|
Func<IQueryable<T>, IIncludableQueryable<T, object>> includeBuilder = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<T, bool>> predicator = null;
|
|
if (ignoreSoftDeleteds)
|
|
{
|
|
predicator = x => !x.Deleted;
|
|
}
|
|
var result = await AsQueryable(
|
|
asNoTracking: true,
|
|
predicate: predicator,
|
|
orderBuilder: orderBuilder,
|
|
includeBuilder: includeBuilder
|
|
)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// retrieve all exists Entities
|
|
/// as Async Enumerable ...
|
|
/// </summary>
|
|
/// <param name="ignoreSoftDeleteds"></param>
|
|
/// <param name="orderBuilder"></param>
|
|
/// <param name="includeBuilder"></param>
|
|
/// <returns></returns>
|
|
public IAsyncEnumerable<T> GetAllAsAsyncEnumerable(
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<T>, IOrderedQueryable<T>> orderBuilder = null,
|
|
Func<IQueryable<T>, IIncludableQueryable<T, object>> includeBuilder = null
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<T, bool>> predicator = null;
|
|
if (ignoreSoftDeleteds)
|
|
{
|
|
predicator = x => !x.Deleted;
|
|
}
|
|
var result = AsQueryable(
|
|
asNoTracking: true,
|
|
predicate: predicator,
|
|
orderBuilder: orderBuilder,
|
|
includeBuilder: includeBuilder
|
|
)
|
|
.AsAsyncEnumerable();
|
|
|
|
//
|
|
return result;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// find an Entity by providing a Conditional Expression ...
|
|
/// </summary>
|
|
/// <param name="predicate"></param>
|
|
/// <param name="ignoreSoftDeleteds"></param>
|
|
/// <param name="includeBuilder"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<T> FindOneAsync(
|
|
Expression<Func<T, bool>> predicate,
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<T>, IIncludableQueryable<T, object>> includeBuilder = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<T, bool>> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// find a collection of Entities by proving a Conditional Expression ...
|
|
/// </summary>
|
|
/// <param name="predicate"></param>
|
|
/// <param name="ignoreSoftDeleteds"></param>
|
|
/// <param name="orderBuilder"></param>
|
|
/// <param name="includeBuilder"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<T>> FindManyAsync(
|
|
Expression<Func<T, bool>> predicate,
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<T>, IOrderedQueryable<T>> orderBuilder = null,
|
|
Func<IQueryable<T>, IIncludableQueryable<T, object>> includeBuilder = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<T, bool>> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// retrieve Entities based on XQuery Pagination structure ...
|
|
/// </summary>
|
|
/// <param name="query"></param>
|
|
/// <param name="ignoreSoftDeleteds"></param>
|
|
/// <param name="predicate"></param>
|
|
/// <param name="orderBuilder"></param>
|
|
/// <param name="includeBuilder"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XQueryResult<T>> QueryAsync(
|
|
XQuery query,
|
|
bool ignoreSoftDeleteds = true,
|
|
Expression<Func<T, bool>> predicate = null,
|
|
Func<IQueryable<T>, IOrderedQueryable<T>> orderBuilder = null,
|
|
Func<IQueryable<T>, IIncludableQueryable<T, object>> 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<T>
|
|
{
|
|
Page = query.Page,
|
|
Items = items.ToList(),
|
|
PageSize = query.PageSize,
|
|
TotalPages = totalPagesCount,
|
|
TotalItems = totalItemsCount,
|
|
TotalFilteredPages = filteredPagesCount,
|
|
TotalFilteredItems = filteredItemsCount
|
|
};
|
|
|
|
//
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Exists ...
|
|
/// <summary>
|
|
/// Check an Entity exists or not ...
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <param name="ignoreSoftDeleteds"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<bool> IsExistsAsync(
|
|
TKey id,
|
|
bool ignoreSoftDeleteds = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<T, bool>> 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 ...
|
|
/// <summary>
|
|
/// Save all unsaved Transactions on DbContext ...
|
|
/// used fo Unit Of Works Design Pattern ...
|
|
/// </summary>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<int> 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 ...
|
|
/// <summary>
|
|
/// Retrieve Key of Entity ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <returns></returns>
|
|
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<TKey>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set Key of Entity ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <param name="id"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle Checking Key ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<T> 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 ...
|
|
/// <summary>
|
|
/// Detach an Entity ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
public void Detach(T item) { }
|
|
|
|
/// <summary>
|
|
/// Detach an Enumerable of Entities ...
|
|
/// </summary>
|
|
/// <param name="items"></param>
|
|
public void Detach(IEnumerable<T> items) { }
|
|
|
|
/// <summary>
|
|
/// Detach a Query Result of Entity ...
|
|
/// </summary>
|
|
/// <param name="query"></param>
|
|
public void Detach(XQueryResult<T> 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
|
|
}
|
|
} |