1108 lines
32 KiB
C#
1108 lines
32 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.ChangeTracking;
|
|
using Microsoft.EntityFrameworkCore.Query;
|
|
using xCommons.Extensions;
|
|
using xDataService.Configuration;
|
|
using xDataService.Db;
|
|
using xDataService.Extensions;
|
|
using xDataService.Interfaces;
|
|
using xDataService.Models;
|
|
using xModels.Base;
|
|
using xModels.Dtos;
|
|
|
|
namespace xDataService.EFRepositories
|
|
{
|
|
/// <summary>
|
|
/// a Base Repository Pattern Implementation Specially for EF DBs using
|
|
/// EF Core Capabilities ...
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <typeparam name="TKey"></typeparam>
|
|
/// <typeparam name="TDbContext"></typeparam>
|
|
public abstract class XBaseEFRepository<T, TKey, TDbContext> : IXBaseRepository<T, TKey>
|
|
where T : XBaseEntity<TKey>
|
|
where TDbContext : XDbContext
|
|
{
|
|
//
|
|
#region Properties ...
|
|
//
|
|
public readonly DbSet<T> dbSet;
|
|
private readonly IXKeyGenerator<T, TKey> keyGenerator;
|
|
public readonly IXUnitOfWorks<TDbContext> unitOfWorks;
|
|
public readonly XDataServiceConfiguration configuration;
|
|
private readonly IXBaseRepositoryEvents<T> baseRepositoryEvents;
|
|
#endregion
|
|
|
|
//
|
|
#region Constructor ...
|
|
public XBaseEFRepository(
|
|
IXUnitOfWorks<TDbContext> unitOfWorks,
|
|
XDataServiceConfiguration configuration,
|
|
IXKeyGenerator<T, TKey> keyGenerator = null,
|
|
IXBaseRepositoryEvents<T> baseRepositoryEvents = null
|
|
)
|
|
{
|
|
//
|
|
this.unitOfWorks = unitOfWorks;
|
|
this.keyGenerator = keyGenerator;
|
|
this.configuration = configuration;
|
|
this.dbSet = unitOfWorks.GetDbSet<T, TKey>();
|
|
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<T> AddAsync(
|
|
T item,
|
|
bool saveChanges = true,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Handle Key ...
|
|
item = await HandleKeyAsync(
|
|
item: item,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
var entry = await dbSet.AddAsync(
|
|
entity: item,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// 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>(entry.Entity));
|
|
}
|
|
|
|
//
|
|
// Return result base on action Succeed ...
|
|
return isSucceed ?
|
|
entry.Entity :
|
|
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),
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (!isExists)
|
|
{
|
|
//
|
|
return await AddAsync(
|
|
item: item,
|
|
saveChanges: saveChanges,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
return await UpdateAsync(
|
|
item: item,
|
|
id: GetKey(item),
|
|
saveChanges: saveChanges,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
}
|
|
|
|
/// <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
|
|
)
|
|
{
|
|
//
|
|
// Preparing Ids and Keys ...
|
|
await dbSet.AddRangeAsync(
|
|
await Task
|
|
.WhenAll(
|
|
items
|
|
.Select(async i => await HandleKeyAsync(i))
|
|
),
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// 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
|
|
.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
|
|
)
|
|
{
|
|
//
|
|
// Get Exists Entity ...
|
|
var existsEntity = await GetAsync(
|
|
id: id,
|
|
ignoreSoftDeleteds: false,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Update Data ...
|
|
existsEntity.UpdateData(item);
|
|
var entry = dbSet.Attach(existsEntity);
|
|
entry.State = EntityState.Modified;
|
|
|
|
//
|
|
// 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>(entry.Entity));
|
|
}
|
|
|
|
//
|
|
// Return result base on action Succeed ...
|
|
return isSucceed ?
|
|
entry.Entity :
|
|
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
|
|
)
|
|
{
|
|
//
|
|
items
|
|
.ToList()
|
|
.ForEach(i =>
|
|
{
|
|
var entry = dbSet.Attach(i);
|
|
entry.State = EntityState.Modified;
|
|
});
|
|
|
|
//
|
|
// 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
|
|
.UpdateManyEvent(new XBaseEventModel<IEnumerable<T>>(null));
|
|
}
|
|
|
|
//
|
|
// Return result base on action Succeed ...
|
|
return isSucceed;
|
|
}
|
|
#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,
|
|
ignoreSoftDeleteds: false,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (item.IsNullOrDefault())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
//
|
|
// Handle Remove ...
|
|
EntityEntry<T> entry = null;
|
|
if (softDelete && configuration.EnableSoftDelete)
|
|
{
|
|
//
|
|
item.Deleted = true;
|
|
entry = dbSet.Update(item);
|
|
}
|
|
else
|
|
{
|
|
entry = dbSet.Remove(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
|
|
.RemoveEvent(new XBaseEventModel<T>(entry.Entity));
|
|
}
|
|
|
|
//
|
|
// Return result base on action Succeed ...
|
|
return isSucceed ?
|
|
entry.Entity :
|
|
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
|
|
)
|
|
{
|
|
//
|
|
// Handle Remove ...
|
|
if (softDelete && configuration.EnableSoftDelete)
|
|
{
|
|
//
|
|
foreach (var item in items)
|
|
{
|
|
item.Deleted = true;
|
|
}
|
|
|
|
//
|
|
dbSet.UpdateRange(items);
|
|
}
|
|
else
|
|
{
|
|
dbSet.RemoveRange(items);
|
|
}
|
|
|
|
//
|
|
// 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
|
|
)
|
|
{
|
|
//
|
|
var result = 0;
|
|
|
|
//
|
|
if (ignoreSoftDeleteds)
|
|
{
|
|
//
|
|
result = await dbSet.CountAsync(
|
|
predicate: x => !x.Deleted,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
else
|
|
{
|
|
result = await dbSet.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 = dbSet
|
|
.AsQueryable();
|
|
|
|
//
|
|
// Apply Predicate ...
|
|
if (!predicate.IsNull())
|
|
{
|
|
result = result
|
|
.Where(predicate);
|
|
}
|
|
|
|
//
|
|
// Apply Includes ...
|
|
if (!includeBuilder.IsNull())
|
|
{
|
|
result = includeBuilder(result);
|
|
}
|
|
|
|
//
|
|
// Apply Ordering ...
|
|
if (!orderBuilder.IsNull())
|
|
{
|
|
result = 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
|
|
)
|
|
{
|
|
return await unitOfWorks.SaveChangesAsync(cancellationToken);
|
|
}
|
|
#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)
|
|
{
|
|
//
|
|
if (item.IsNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
Entry(item).State = EntityState.Detached;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detach an Enumerable of Entities ...
|
|
/// </summary>
|
|
/// <param name="items"></param>
|
|
public void Detach(IEnumerable<T> items)
|
|
{
|
|
//
|
|
if (items.IsNull() || !items.HasChild())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
items
|
|
.ToList()
|
|
.ForEach(item =>
|
|
{
|
|
Detach(item);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detach a Query Result of Entity ...
|
|
/// </summary>
|
|
/// <param name="query"></param>
|
|
public void Detach(XQueryResult<T> query)
|
|
{
|
|
//
|
|
if (query.IsNull() || query.Items.HasChild())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
Detach(query.Items);
|
|
}
|
|
#endregion
|
|
#endregion
|
|
|
|
//
|
|
#region Others ...
|
|
public void Dispose()
|
|
{
|
|
unitOfWorks.Dispose();
|
|
}
|
|
|
|
public string GetPropValues(T item)
|
|
{
|
|
//
|
|
var props = item.GetType().GetProperties();
|
|
var vals = props.Select(p => p.GetValue(p.Name));
|
|
|
|
//
|
|
return vals.ToJSON();
|
|
}
|
|
|
|
public EntityEntry<T> Entry(T item)
|
|
{
|
|
return unitOfWorks.DbContext.Entry(item);
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Private ...
|
|
#endregion
|
|
}
|
|
} |