1022 lines
30 KiB
C#
1022 lines
30 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
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 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 InMemory Stores using
|
|
/// EF Core Capabilities ...
|
|
/// </summary>
|
|
/// <typeparam name="TEntity"></typeparam>
|
|
/// <typeparam name="TKey"></typeparam>
|
|
public abstract class XBaseInMemoryRepository<TEntity, TKey> : IXBaseRepository<TEntity, TKey>
|
|
where TEntity : XBaseEntity<TKey>
|
|
{
|
|
//
|
|
#region Props ...
|
|
private readonly XDataServiceConfiguration configuration;
|
|
private readonly IXKeyGenerator<TEntity, TKey> keyGenerator;
|
|
private readonly IXBaseRepositoryEvents<TEntity> baseRepositoryEvents;
|
|
private static readonly ConcurrentDictionary<TKey, TEntity> store = new ConcurrentDictionary<TKey, TEntity>();
|
|
#endregion
|
|
|
|
//
|
|
#region Constructor ...
|
|
public XBaseInMemoryRepository(
|
|
XDataServiceConfiguration configuration,
|
|
IXKeyGenerator<TEntity, TKey> keyGenerator = null,
|
|
IXBaseRepositoryEvents<TEntity> baseRepositoryEvents = null
|
|
)
|
|
{
|
|
this.keyGenerator = keyGenerator;
|
|
this.configuration = configuration;
|
|
this.baseRepositoryEvents = baseRepositoryEvents;
|
|
}
|
|
#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<TEntity> AddAsync(
|
|
TEntity item,
|
|
bool saveChanges = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Handle Key ...
|
|
item = await HandleKeyAsync(
|
|
item: item,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
var isSucceed = false;
|
|
|
|
//
|
|
// Add Item to Dictionary ...
|
|
try
|
|
{
|
|
//
|
|
store[item.Id] = item;
|
|
isSucceed = true;
|
|
}
|
|
catch
|
|
{
|
|
isSucceed = false;
|
|
}
|
|
|
|
//
|
|
// Notify Event ...
|
|
if (
|
|
isSucceed &&
|
|
!baseRepositoryEvents.IsNull()
|
|
)
|
|
{
|
|
//
|
|
baseRepositoryEvents
|
|
.AddEvent(new XBaseEventModel<TEntity>(item));
|
|
}
|
|
|
|
//
|
|
// Return Item ...
|
|
return item;
|
|
}
|
|
|
|
/// <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<TEntity> AddOrUpdateAsync(
|
|
TEntity item,
|
|
bool saveChanges = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var isExists = await IsExistsAsync(
|
|
id: item.Id,
|
|
ignoreSoftDeleteds: true,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
var isSucceed = false;
|
|
if (isExists)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
item = await UpdateAsync(
|
|
item: item,
|
|
id: item.Id,
|
|
saveChanges: saveChanges,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
isSucceed = true;
|
|
}
|
|
catch
|
|
{
|
|
isSucceed = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
item = await AddAsync(
|
|
item: item,
|
|
saveChanges: saveChanges,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
isSucceed = true;
|
|
}
|
|
catch
|
|
{
|
|
isSucceed = false;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Notify Event ...
|
|
if (
|
|
isSucceed &&
|
|
!baseRepositoryEvents.IsNull()
|
|
)
|
|
{
|
|
//
|
|
if (isExists)
|
|
{
|
|
//
|
|
baseRepositoryEvents
|
|
.UpdateEvent(new XBaseEventModel<TEntity>(item));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
baseRepositoryEvents
|
|
.AddEvent(new XBaseEventModel<TEntity>(item));
|
|
}
|
|
}
|
|
|
|
//
|
|
return item;
|
|
}
|
|
|
|
/// <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<TEntity> 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<TEntity>>(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<TEntity> UpdateAsync(
|
|
TKey id,
|
|
TEntity item,
|
|
bool saveChanges = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var isExists = await IsExistsAsync(
|
|
id: id,
|
|
ignoreSoftDeleteds: true,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (!isExists)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
//
|
|
var isSucceed = false;
|
|
var exists = store[id];
|
|
|
|
//
|
|
try
|
|
{
|
|
//
|
|
exists = exists.UpdateData(
|
|
exists,
|
|
propertyBlackList: new List<string> { nameof(exists.Id) }
|
|
);
|
|
store[id] = exists;
|
|
|
|
//
|
|
isSucceed = true;
|
|
}
|
|
catch
|
|
{
|
|
isSucceed = false;
|
|
}
|
|
|
|
//
|
|
// Notify Event ...
|
|
if (
|
|
isSucceed &&
|
|
!baseRepositoryEvents.IsNull()
|
|
)
|
|
{
|
|
//
|
|
baseRepositoryEvents
|
|
.UpdateEvent(new XBaseEventModel<TEntity>(item));
|
|
}
|
|
|
|
//
|
|
return exists;
|
|
}
|
|
|
|
/// <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<TEntity> 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<TEntity> RemoveAsync(
|
|
TKey id,
|
|
bool softDelete = true,
|
|
bool saveChanges = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var isExists = await IsExistsAsync(
|
|
id: id,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (!isExists)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
//
|
|
TEntity result = null;
|
|
if (softDelete && configuration.EnableSoftDelete)
|
|
{
|
|
//
|
|
store[id].Deleted = true;
|
|
result = store[id];
|
|
}
|
|
else
|
|
{
|
|
//
|
|
result = store[id];
|
|
store.TryRemove(id, out _);
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <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<TEntity> RemoveAsync(
|
|
TEntity 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<TEntity> items,
|
|
bool softDelete = true,
|
|
bool saveChanges = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var isValid = !items.IsNull() && items.HasChild();
|
|
if (!isValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
foreach (var item in items)
|
|
{
|
|
//
|
|
await RemoveAsync(
|
|
item: item,
|
|
softDelete: softDelete,
|
|
saveChanges: saveChanges,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
}
|
|
#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<TEntity, 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 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<TEntity, 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 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<TEntity> AsQueryable(
|
|
bool asNoTracking = true,
|
|
Expression<Func<TEntity, bool>> predicate = null,
|
|
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBuilder = null,
|
|
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> includeBuilder = null
|
|
)
|
|
{
|
|
//
|
|
var result = store.Values.AsQueryable();
|
|
|
|
//
|
|
// Predicate ...
|
|
if (!predicate.IsNull())
|
|
{
|
|
result = result.Where(predicate);
|
|
}
|
|
|
|
//
|
|
// Apply Orders ...
|
|
if (!orderBuilder.IsNull())
|
|
{
|
|
result = orderBuilder(result);
|
|
}
|
|
|
|
//
|
|
// Apply Includes ...
|
|
if (!includeBuilder.IsNull())
|
|
{
|
|
result = includeBuilder(result);
|
|
}
|
|
|
|
//
|
|
if (asNoTracking)
|
|
{
|
|
result = result.AsNoTracking();
|
|
}
|
|
|
|
//
|
|
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<TEntity> GetAsync(
|
|
TKey id,
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> includeBuilder = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<TEntity, 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<TEntity>> GetAllAsync(
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBuilder = null,
|
|
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> includeBuilder = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<TEntity, bool>> predicator = null;
|
|
if (ignoreSoftDeleteds)
|
|
{
|
|
predicator = x => !x.Deleted;
|
|
}
|
|
var result = await AsQueryable(
|
|
asNoTracking: true,
|
|
orderBuilder: null,
|
|
predicate: predicator,
|
|
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<TEntity> GetAllAsAsyncEnumerable(
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBuilder = null,
|
|
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> includeBuilder = null
|
|
)
|
|
{
|
|
//
|
|
Expression<Func<TEntity, 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<TEntity> FindOneAsync(
|
|
Expression<Func<TEntity, bool>> predicate,
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> includeBuilder = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<TEntity, 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<TEntity>> FindManyAsync(
|
|
Expression<Func<TEntity, bool>> predicate,
|
|
bool ignoreSoftDeleteds = true,
|
|
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBuilder = null,
|
|
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> includeBuilder = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Prepare Ignore Soft Deleted Predicator ...
|
|
Expression<Func<TEntity, bool>> predicator = null;
|
|
if (ignoreSoftDeleteds)
|
|
{
|
|
predicator = x => !x.Deleted;
|
|
}
|
|
var result = await AsQueryable(
|
|
asNoTracking: true,
|
|
orderBuilder: null,
|
|
predicate: predicator,
|
|
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<TEntity>> QueryAsync(
|
|
XQuery query,
|
|
bool ignoreSoftDeleteds = true,
|
|
Expression<Func<TEntity, bool>> predicate = null,
|
|
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBuilder = null,
|
|
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, 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<TEntity>
|
|
{
|
|
Page = query.Page,
|
|
Items = items.ToList(),
|
|
PageSize = query.PageSize,
|
|
TotalPages = totalPagesCount,
|
|
TotalItems = totalItemsCount,
|
|
TotalFilteredPages = filteredPagesCount,
|
|
TotalFilteredItems = filteredItemsCount
|
|
};
|
|
|
|
//
|
|
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
|
|
)
|
|
{
|
|
//
|
|
return await Task.Run(
|
|
() => 0,
|
|
cancellationToken
|
|
);
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Key ...
|
|
/// <summary>
|
|
/// Retrieve Key of Entity ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <returns></returns>
|
|
public TKey GetKey(TEntity 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 TEntity 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<TEntity> HandleKeyAsync(
|
|
TEntity 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(TEntity item) { }
|
|
|
|
/// <summary>
|
|
/// Detach an Enumerable of Entities ...
|
|
/// </summary>
|
|
/// <param name="items"></param>
|
|
public void Detach(IEnumerable<TEntity> items) { }
|
|
|
|
/// <summary>
|
|
/// Detach a Query Result of Entity ...
|
|
/// </summary>
|
|
/// <param name="query"></param>
|
|
public void Detach(XQueryResult<TEntity> query) { }
|
|
#endregion
|
|
#endregion
|
|
|
|
//
|
|
#region Others ...
|
|
public void Dispose()
|
|
{ }
|
|
#endregion
|
|
|
|
//
|
|
#region Private ...
|
|
#endregion
|
|
}
|
|
} |