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

1093 lines
36 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
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>
/// Base EFCore Base Entity Repository Pattern implementation ...
/// Only Used on EFCore ...
/// </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 XBaseEFRepository<T, TKey, TDbContext> : IXBaseRepository<T, TKey>
where T : XBaseEntity<TKey>
where TDbContext : XDbContext {
//
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;
//
public abstract IQueryable<T> GetFullDbSet ();
//
#region Constructor ...
public XBaseEFRepository (
IXUnitOfWorks<TDbContext> unitOfWorks,
XDataServiceConfiguration configuration,
IXKeyGenerator<T, TKey> keyGenerator = null,
IXBaseRepositoryEvents<T> baseRepositoryEvents = null
) {
this.keyGenerator = keyGenerator;
//
this.unitOfWorks = unitOfWorks;
this.configuration = configuration;
this.dbSet = unitOfWorks.GetDbSet<T, TKey> ();
this.baseRepositoryEvents = baseRepositoryEvents;
}
#endregion
//
#region Add ...
public async Task<T> AddAsync (
T item,
bool saveChanges = true
) {
//
// Handle Key ...
item = await HandleKeyAsync (item);
//
var entry = await dbSet.AddAsync (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> (entry.Entity));
}
//
// Return result base on action Succeed ...
return isSucceed ?
entry.Entity :
null;
}
public async Task<T> AddOrUpdateAsync (
T item,
bool saveChanges = true
) {
//
var isExists = await IsExistsAsync (GetKey (item));
if (!isExists) {
return await AddAsync (
item,
saveChanges
);
} else {
return await UpdateAsync (
GetKey (item),
item,
saveChanges
);
}
}
public async Task AddRangeAsync (
IEnumerable<T> items,
bool saveChanges = true
) {
//
await dbSet.AddRangeAsync (
await Task
.WhenAll (
items
.Select (async i => await HandleKeyAsync (i))
)
);
//
// 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 : false
);
if (item.IsNull ()) {
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 ();
//
// 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;
}
public async Task<T> RemoveAsync (
T item,
bool softDelete = true,
bool saveChanges = true
) {
//
// Check item Exists ...
var isExists = await IsExistsAsync (
GetKey (item),
ignoreSoftDeleteds : false
);
if (!isExists) {
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 ();
//
// 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;
}
public async Task RemoveRangeAsync (
IEnumerable<T> items,
bool softDelete = true,
bool saveChanges = true
) {
//
// 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 ();
//
// 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 dbSet.AsQueryable ();
}
public async Task<T> GetAsync (
TKey id,
bool ignoreSoftDeleteds = true,
bool containsDetail = false
) {
//
var result = await FindOneAsync (i =>
GetKey (i)
.ToString () ==
id
.ToString (),
ignoreSoftDeleteds : ignoreSoftDeleteds,
containsDetail : containsDetail
);
//
return result;
}
public async Task<IEnumerable<T>> GetAllAsync (
bool ignoreSoftDeleteds = true,
bool containsDetail = false
) {
return await Task.Run (() => {
//
var result = GetDbSet (
containsDetail: containsDetail
);
//
// Handle Soft Deleted Items ...
if (ignoreSoftDeleteds && configuration.EnableSoftDelete) {
result = result.Where (x => x.Deleted == false);
}
//
return result.AsEnumerable ();
});
}
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 = GetDbSet (
containsDetail: containsDetail
)
.AsAsyncEnumerable ();
//
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 = GetDbSet (
containsDetail: containsDetail
)
.AsAsyncEnumerable ();
//
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;
}
//
// 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 = GetDbSet (
containsDetail: query.ContainsDetail
)
.AsAsyncEnumerable ();
//
var resultItems = new List<T> ();
await
foreach (var entity in enumerator) {
//
var isApproved = (ignoreSoftDeletedsWhereFunc.IsNull () ?
true :
ignoreSoftDeletedsWhereFunc (entity));
if (isApproved) {
resultItems.Add (entity);
}
}
//
var result = resultItems.AsEnumerable ();
//
// Count Total Items ...
var totalItemsCount = result.Count ();
//
// Apply Filter ...
if (!query.Filter.IsNullOrEmpty ()) {
result = await result
.ApplyFilterAsync<T, TKey> (query.Filter);
}
//
// Count Total Items ...
int totalFilteredItemsCount = result.Count ();
//
// Apply Paging ...
result = result
.ApplyPaging (
query.Page,
query.PageSize);
//
// Apply Sorting ...
if (!query.SortBy.IsNullOrEmpty ()) {
result = result
.ApplySorting<T, TKey> (
query.SortBy,
query.IsAscending);
}
//
// Generate Result Object ...
// Query = query,
var queryResult = new XQueryResult<T> {
Items = result.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>> condition,
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 Function ...
var whereFunc = condition.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 = GetDbSet (
containsDetail: query.ContainsDetail
)
.AsAsyncEnumerable ();
//
var resultItems = new List<T> ();
await
foreach (var entity in enumerator) {
//
var isApproved = whereFunc (entity) &&
(ignoreSoftDeletedsWhereFunc.IsNull () ?
true :
ignoreSoftDeletedsWhereFunc (entity));
if (isApproved) {
resultItems.Add (entity);
}
}
//
var result = resultItems.AsQueryable ();
//
// Count Total Items ...
var totalItemsCount = result.Count ();
//
// Apply Filter ...
if (totalItemsCount > 0 &&
!query.Filter.IsNullOrEmpty ()) {
result = result
.ApplyFilter<T, TKey> (query.Filter);
}
//
// Count Total Items ...
var totalFilteredItemsCount = result.Count ();
//
// Apply Paging ...
if (totalItemsCount > 0 &&
totalFilteredItemsCount > 0) {
result = result
.ApplyPaging (
query.Page,
query.PageSize);
//
// Apply Sorting ...
if (!query.SortBy.IsNullOrEmpty ()) {
result = result
.ApplySorting<T, TKey> (
query.SortBy,
query.IsAscending);
}
}
//
// Generate Result Object ...
var queryResult = new XQueryResult<T> {
Items = result.AsEnumerable (),
Page = query.Page,
PageSize = query.PageSize,
TotalItems = totalItemsCount,
TotalPages = await PagesCountAsync (
query.PageSize,
totalFilteredItemsCount
),
TotalFilteredItems = totalFilteredItemsCount
};
//
return queryResult;
}
//
// TODO: Fix this ...
// public Task<XPageResponse<T>> RequestPageAsync (
// XPageRequest request,
// bool ignoreSoftDeleteds = true,
// bool containsDetail = true
// ) {
// //
// var result = GetDbSet (
// containsDetail: containsDetail
// );
// //
// // Handle Soft Deleted Items ...
// if (ignoreSoftDeleteds) {
// result = result.Where (x => x.Deleted == false);
// }
// //
// if (request.First.HasValue) {
// //
// if (!request.After.IsNullOrEmpty ()) {
// //
// TKey lastId = XCursorHelper.FromCursor<TKey> (request.After);
// result = result.Where (x => GetKey (x).ToString() > lastId.ToString());
// }
// //
// result = result.Take (request.First.Value);
// }
// //
// // Apply Sorting ...
// List<T> nodes = null;
// if (!request.SortBy.IsNullOrEmpty ()) {
// nodes = result
// .ApplySorting<T, TKey> (
// request.SortBy, !request.DescendingSort
// ).ToList ();
// }
// //
// // Claculate Required Info ...
// int totalCount = result.CountAsync ().Result;
// int maxId = nodes.Max (x => Convert.ToInt32 (GetKey (x)));
// int minId = nodes.Min (x => Convert.ToInt32 (GetKey (x)));
// bool hasNextPage = nodes.Any (x => Convert.ToInt32 (GetKey (x)) > maxId);
// bool hasPreviousPage = nodes.Any (x => Convert.ToInt32 (GetKey (x)) < minId);
// //
// return Task.FromResult (new XPageResponse<T> {
// Nodes = nodes,
// TotalCount = totalCount,
// HasNextPage = hasNextPage,
// HasPreviousPage = hasPreviousPage
// });
// }
//
// TODO: Fix this ...
// public Task<XPageResponse<T>> RequestConditionalPageAsync (
// Expression<Func<T, bool>> condition,
// XPageRequest request,
// bool ignoreSoftDeleteds = true,
// bool containsDetail = true
// ) {
// //
// var result = GetDbSet (
// containsDetail: containsDetail
// )
// .Where (condition);
// //
// // Handle Soft Deleted Items ...
// if (ignoreSoftDeleteds) {
// result = result.Where (x => x.Deleted == false);
// }
// //
// if (request.First.HasValue) {
// //
// if (!request.After.IsNullOrEmpty ()) {
// //
// int lastId = XCursorHelper.FromCursor (request.After);
// result = result.Where (x => Convert.ToInt32 (GetKey (x)) > lastId);
// }
// //
// result = result.Take (request.First.Value);
// }
// //
// // Apply Sorting ...
// List<T> nodes = null;
// if (!request.SortBy.IsNullOrEmpty ()) {
// nodes = result
// .ApplySorting<T, TKey> (
// request.SortBy, !request.DescendingSort)
// .ToList ();
// }
// //
// // Claculate Required Info ...
// int totalCount = result.CountAsync ().Result;
// int maxId = nodes.Max (x => Convert.ToInt32 (GetKey (x)));
// int minId = nodes.Min (x => Convert.ToInt32 (GetKey (x)));
// bool hasNextPage = result.Any (x => Convert.ToInt32 (GetKey (x)) > maxId);
// bool hasPreviousPage = result.Any (x => Convert.ToInt32 (GetKey (x)) < minId);
// //
// return Task.FromResult (new XPageResponse<T> {
// Nodes = nodes,
// TotalCount = totalCount,
// HasNextPage = hasNextPage,
// HasPreviousPage = hasPreviousPage
// });
// }
#endregion
//
#region Update ...
public async Task<T> UpdateAsync (
TKey id,
T item,
bool saveChanges = true
) {
//
// Get Exists Entity ...
var existsEntity = await GetAsync (
id,
containsDetail : true,
ignoreSoftDeleteds : false
);
//
// 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 ();
//
// 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;
}
public async Task<bool> UpdateRangeAsync (
IEnumerable<T> items,
bool saveChanges = true
) {
//
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 ();
//
// 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) {
//
var dbSet = GetDbSet ();
//
if (ignoreSoftDeleteds && configuration.EnableSoftDelete) {
return await dbSet.CountAsync (i => i.Deleted == false);
} else {
return await dbSet.CountAsync ();
}
}
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
);
//
Detach (result);
//
return !result.IsNull ();
}
#endregion
//
#region Unit Of Work ...
public async Task<int> SaveChangesAsync () {
return await unitOfWorks.SaveChangesAsync ();
}
#endregion
//
#region Keys ...
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);
}
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> ();
}
public async Task<T> HandleKeyAsync (T item) {
//
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 ...
public void Detach (T item) {
//
if (item.IsNull ()) {
return;
}
//
Entry (item).State = EntityState.Detached;
}
public void Detach (IEnumerable<T> items) {
//
if (items.IsNull () || !items.HasChild ()) {
return;
}
//
items
.ToList ()
.ForEach (item => {
Detach (item);
});
}
public void Detach (XQueryResult<T> query) {
//
if (query.IsNull () || query.Items.HasChild ()) {
return;
}
//
Detach (query.Items);
}
public void Detach (XPageResponse<T> page) {
//
if (page.IsNull () || !page.Nodes.HasChild ()) {
return;
}
//
Detach (page.Nodes);
}
#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);
}
public IQueryable<T> GetDbSet (
bool containsDetail = false
) {
//
IQueryable<T> result = null;
if (!containsDetail) {
result = dbSet;
} else {
result = GetFullDbSet ();
}
//
return result;
}
#endregion
}
}