添加项目文件。

This commit is contained in:
SpecialX
2025-05-23 19:03:00 +08:00
parent 6fa7679fd3
commit d36fef2bbb
185 changed files with 13413 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
namespace SharedDATA.Api
{
using SharedDATA.Context;
using System;
using System.Collections.Generic;
/// <summary>
/// Provides some extension methods for <see cref="IEnumerable{T}"/> to provide paging capability.
/// 提供一些扩展方法来提供分页功能
/// </summary>
public static class IEnumerablePagedListExtensions
{
/// <summary>
/// Converts the specified source to <see cref="IPagedList{T}"/> by the specified <paramref name="pageIndex"/> and <paramref name="pageSize"/>.
/// 通过起始页和页大小把数据转换成页集合
/// </summary>
/// <typeparam name="T">The type of the source.源的类型</typeparam>
/// <param name="source">The source to paging.分页的源</param>
/// <param name="pageIndex">The index of the page.起始页</param>
/// <param name="pageSize">The size of the page.页大小</param>
/// <param name="indexFrom">The start index value.开始索引值</param>
/// <returns>An instance of the inherited from <see cref="IPagedList{T}"/> interface.接口继承的实例</returns>
public static IPagedList<T> ToPagedList<T>(this IEnumerable<T> source, int pageIndex, int pageSize, int indexFrom = 0) => new PagedList<T>(source, pageIndex, pageSize, indexFrom);
/// <summary>
/// Converts the specified source to <see cref="IPagedList{T}"/> by the specified <paramref name="converter"/>, <paramref name="pageIndex"/> and <paramref name="pageSize"/>
/// 通过转换器,起始页和页大小把数据转换成页集合
/// </summary>
/// <typeparam name="TSource">The type of the source.源的类型</typeparam>
/// <typeparam name="TResult">The type of the result.反馈的类型</typeparam>
/// <param name="source">The source to convert.要转换的源</param>
/// <param name="converter">The converter to change the <typeparamref name="TSource"/> to <typeparamref name="TResult"/>.转换器来改变源到反馈</param>
/// <param name="pageIndex">The page index.起始页</param>
/// <param name="pageSize">The page size.页大小</param>
/// <param name="indexFrom">The start index value.开始索引值</param>
/// <returns>An instance of the inherited from <see cref="IPagedList{T}"/> interface.接口继承的实例</returns>
public static IPagedList<TResult> ToPagedList<TSource, TResult>(this IEnumerable<TSource> source, Func<IEnumerable<TSource>, IEnumerable<TResult>> converter, int pageIndex, int pageSize, int indexFrom = 0) => new PagedList<TSource, TResult>(source, converter, pageIndex, pageSize, indexFrom);
}
}

View File

@@ -0,0 +1,52 @@
namespace SharedDATA.Api
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Context;
public static class IQueryablePageListExtensions
{
/// <summary>
/// Converts the specified source to <see cref="IPagedList{T}"/> by the specified <paramref name="pageIndex"/> and <paramref name="pageSize"/>.
/// 根据起始页和页大小转换成源
/// </summary>
/// <typeparam name="T">The type of the source.源的类型</typeparam>
/// <param name="source">The source to paging.分页的源</param>
/// <param name="pageIndex">The index of the page.起始页</param>
/// <param name="pageSize">The size of the page.页大小</param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
/// 在等待任务完成时观察
/// </param>
/// <param name="indexFrom">The start index value.值的起始索引</param>
/// <returns>An instance of the inherited from <see cref="IPagedList{T}"/> interface.接口继承的实例</returns>
public static async Task<IPagedList<T>> ToPagedListAsync<T>(this IQueryable<T> source, int pageIndex, int pageSize, int indexFrom = 0, CancellationToken cancellationToken = default(CancellationToken))
{
//如果索引比起始页大,则异常
if (indexFrom > pageIndex)
{
throw new ArgumentException($"indexFrom: {indexFrom} > pageIndex: {pageIndex}, must indexFrom <= pageIndex");
}
//数据源大小
var count = await source.CountAsync(cancellationToken).ConfigureAwait(false);
var items = await source.Skip((pageIndex - indexFrom) * pageSize)
.Take(pageSize).ToListAsync(cancellationToken).ConfigureAwait(false);
var pagedList = new PagedList<T>()
{
PageIndex = pageIndex,
PageSize = pageSize,
IndexFrom = indexFrom,
TotalCount = count,
Items = items,
TotalPages = (int)Math.Ceiling(count / (double)pageSize)
};
return pagedList;
}
}
}

View File

@@ -0,0 +1,463 @@
namespace SharedDATA.Api
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Context;
/// <summary>
/// Defines the interfaces for generic repository.
/// 为通用存储库定义接口
/// </summary>
/// <typeparam name="TEntity">The type of the entity.实体类型</typeparam>
public interface IRepository<TEntity> where TEntity : class
{
/// <summary>
/// Changes the table name. This require the tables in the same database.
/// 更改表名。这需要相同数据库中的表
/// </summary>
/// <param name="table"></param>
/// <remarks>
/// This only been used for supporting multiple tables in the same model. This require the tables in the same database.
/// 这只用于支持同一个模型中的多个表。这需要相同数据库中的表。
/// </remarks>
void ChangeTable(string table);
/// <summary>
/// Gets the <see cref="IPagedList{TEntity}"/> based on a predicate, orderby delegate and page information. This method default no-tracking query.
/// 基于谓词、orderby委托和页面信息获取<see cref="IPagedList{TEntity}"/>。此方法默认无跟踪查询。
/// </summary>
/// <param name="predicate">A function to test each element for a condition.用于测试条件的每个元素的函数</param>
/// <param name="orderBy">A function to order elements.对元素进行排序的函数</param>
/// <param name="include">A function to include navigation properties 包含导航属性的函数</param>
/// <param name="pageIndex">The index of page.起始页</param>
/// <param name="pageSize">The size of the page.页大小</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, 禁用更改跟踪<c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters 忽略查询过滤器</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by 包含满足指定条件的元素<paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
IPagedList<TEntity> GetPagedList(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
int pageIndex = 0,
int pageSize = 20,
bool disableTracking = true,
bool ignoreQueryFilters = false);
/// <summary>
/// Gets the <see cref="IPagedList{TEntity}"/> based on a predicate, orderby delegate and page information. This method default no-tracking query.
/// 基于谓词、orderby委托和页面信息获取<see cref="IPagedList{TEntity}"/>。此方法默认无跟踪查询。
/// </summary>
/// <param name="predicate">A function to test each element for a condition.用于测试条件的每个元素的函数</param>
/// <param name="orderBy">A function to order elements.对元素进行排序的函数</param>
/// <param name="include">A function to include navigation properties 包含导航属性的函数</param>
/// <param name="pageIndex">The index of page.起始页</param>
/// <param name="pageSize">The size of the page.页大小</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking;禁用更改跟踪; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
/// </param>
/// <param name="ignoreQueryFilters">Ignore query filters 忽略查询过滤器</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.此方法默认无跟踪查询</remarks>
Task<IPagedList<TEntity>> GetPagedListAsync(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
int pageIndex = 0,
int pageSize = 20,
bool disableTracking = true,
CancellationToken cancellationToken = default(CancellationToken),
bool ignoreQueryFilters = false);
/// <summary>
/// Gets the <see cref="IPagedList{TResult}"/> based on a predicate, orderby delegate and page information. This method default no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="pageIndex">The index of page.</param>
/// <param name="pageSize">The size of the page.</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TResult}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
IPagedList<TResult> GetPagedList<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
int pageIndex = 0,
int pageSize = 20,
bool disableTracking = true,
bool ignoreQueryFilters = false) where TResult : class;
/// <summary>
/// Gets the <see cref="IPagedList{TEntity}"/> based on a predicate, orderby delegate and page information. This method default no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="pageIndex">The index of page.</param>
/// <param name="pageSize">The size of the page.</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
/// </param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
Task<IPagedList<TResult>> GetPagedListAsync<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
int pageIndex = 0,
int pageSize = 20,
bool disableTracking = true,
CancellationToken cancellationToken = default(CancellationToken),
bool ignoreQueryFilters = false) where TResult : class;
/// <summary>
/// Gets the first or default entity based on a predicate, orderby delegate and include delegate. This method defaults to a read-only, no-tracking query.
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>true</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method defaults to a read-only, no-tracking query.</remarks>
TEntity GetFirstOrDefault(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true,
bool ignoreQueryFilters = false);
/// <summary>
/// Gets the first or default entity based on a predicate, orderby delegate and include delegate. This method defaults to a read-only, no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>true</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method defaults to a read-only, no-tracking query.</remarks>
TResult GetFirstOrDefault<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true,
bool ignoreQueryFilters = false);
/// <summary>
/// Gets the first or default entity based on a predicate, orderby delegate and include delegate. This method defaults to a read-only, no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>true</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>Ex: This method defaults to a read-only, no-tracking query.</remarks>
Task<TResult> GetFirstOrDefaultAsync<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true,
bool ignoreQueryFilters = false);
/// <summary>
/// Gets the first or default entity based on a predicate, orderby delegate and include delegate. This method defaults to a read-only, no-tracking query.
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>true</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>Ex: This method defaults to a read-only, no-tracking query. </remarks>
Task<TEntity> GetFirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true,
bool ignoreQueryFilters = false);
/// <summary>
/// Uses raw SQL queries to fetch the specified <typeparamref name="TEntity" /> data.
/// </summary>
/// <param name="sql">The raw SQL.</param>
/// <param name="parameters">The parameters.</param>
/// <returns>An <see cref="IQueryable{TEntity}" /> that contains elements that satisfy the condition specified by raw SQL.</returns>
IQueryable<TEntity> FromSql(string sql, params object[] parameters);
/// <summary>
/// Finds an entity with the given primary key values. If found, is attached to the context and returned. If no entity is found, then null is returned.
/// </summary>
/// <param name="keyValues">The values of the primary key for the entity to be found.</param>
/// <returns>The found entity or null.</returns>
TEntity Find(params object[] keyValues);
/// <summary>
/// Finds an entity with the given primary key values. If found, is attached to the context and returned. If no entity is found, then null is returned.
/// </summary>
/// <param name="keyValues">The values of the primary key for the entity to be found.</param>
/// <returns>A <see cref="Task{TEntity}"/> that represents the asynchronous find operation. The task result contains the found entity or null.</returns>
ValueTask<TEntity> FindAsync(params object[] keyValues);
/// <summary>
/// Finds an entity with the given primary key values. If found, is attached to the context and returned. If no entity is found, then null is returned.
/// </summary>
/// <param name="keyValues">The values of the primary key for the entity to be found.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>A <see cref="Task{TEntity}"/> that represents the asynchronous find operation. The task result contains the found entity or null.</returns>
ValueTask<TEntity> FindAsync(object[] keyValues, CancellationToken cancellationToken);
/// <summary>
/// Gets all entities. This method is not recommended
/// </summary>
/// <returns>The <see cref="IQueryable{TEntity}"/>.</returns>
IQueryable<TEntity> GetAll();
/// <summary>
/// Gets all entities. This method is not recommended
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>true</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>Ex: This method defaults to a read-only, no-tracking query.</remarks>
IQueryable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true,
bool ignoreQueryFilters = false);
/// <summary>
/// Gets all entities. This method is not recommended
/// </summary>
/// <returns>The <see cref="IQueryable{TEntity}"/>.</returns>
Task<IList<TEntity>> GetAllAsync();
/// <summary>
/// Gets all entities. This method is not recommended
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>true</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>Ex: This method defaults to a read-only, no-tracking query.</remarks>
Task<IList<TEntity>> GetAllAsync(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true,
bool ignoreQueryFilters = false);
/// <summary>
/// Gets the count based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
int Count(Expression<Func<TEntity, bool>> predicate = null);
/// <summary>
/// Gets async the count based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate = null);
/// <summary>
/// Gets the long count based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
long LongCount(Expression<Func<TEntity, bool>> predicate = null);
/// <summary>
/// Gets async the long count based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate = null);
/// <summary>
/// Gets the max based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
T Max<T>(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, T>> selector = null);
/// <summary>
/// Gets the async max based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
Task<T> MaxAsync<T>(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, T>> selector = null);
/// <summary>
/// Gets the min based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <param name="selector"></param>
/// <returns>decimal</returns>
T Min<T>(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, T>> selector = null);
/// <summary>
/// Gets the async min based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <param name="selector"></param>
/// <returns>decimal</returns>
Task<T> MinAsync<T>(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, T>> selector = null);
/// <summary>
/// Gets the average based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
decimal Average(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, decimal>> selector = null);
/// <summary>
/// Gets the async average based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
Task<decimal> AverageAsync(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, decimal>> selector = null);
/// <summary>
/// Gets the sum based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
decimal Sum(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, decimal>> selector = null);
/// <summary>
/// Gets the async sum based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
Task<decimal> SumAsync(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, decimal>> selector = null);
/// <summary>
/// Gets the Exists record based on a predicate.
/// </summary>
/// <param name="selector"></param>
/// <returns></returns>
bool Exists(Expression<Func<TEntity, bool>> selector = null);
/// <summary>
/// Gets the Async Exists record based on a predicate.
/// </summary>
/// <param name="selector"></param>
/// <returns></returns>
Task<bool> ExistsAsync(Expression<Func<TEntity, bool>> selector = null);
/// <summary>
/// Inserts a new entity synchronously.
/// </summary>
/// <param name="entity">The entity to insert.</param>
TEntity Insert(TEntity entity);
/// <summary>
/// Inserts a range of entities synchronously.
/// </summary>
/// <param name="entities">The entities to insert.</param>
void Insert(params TEntity[] entities);
/// <summary>
/// Inserts a range of entities synchronously.
/// </summary>
/// <param name="entities">The entities to insert.</param>
void Insert(IEnumerable<TEntity> entities);
/// <summary>
/// Inserts a new entity asynchronously.
/// </summary>
/// <param name="entity">The entity to insert.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous insert operation.</returns>
ValueTask<EntityEntry<TEntity>> InsertAsync(TEntity entity, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Inserts a range of entities asynchronously.
/// </summary>
/// <param name="entities">The entities to insert.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous insert operation.</returns>
Task InsertAsync(params TEntity[] entities);
/// <summary>
/// Inserts a range of entities asynchronously.
/// </summary>
/// <param name="entities">The entities to insert.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous insert operation.</returns>
Task InsertAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Updates the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
void Update(TEntity entity);
/// <summary>
/// Updates the specified entities.
/// </summary>
/// <param name="entities">The entities.</param>
void Update(params TEntity[] entities);
/// <summary>
/// Updates the specified entities.
/// </summary>
/// <param name="entities">The entities.</param>
void Update(IEnumerable<TEntity> entities);
/// <summary>
/// Deletes the entity by the specified primary key.
/// </summary>
/// <param name="id">The primary key value.</param>
void Delete(object id);
/// <summary>
/// Deletes the specified entity.
/// </summary>
/// <param name="entity">The entity to delete.</param>
void Delete(TEntity entity);
/// <summary>
/// Deletes the specified entities.
/// </summary>
/// <param name="entities">The entities.</param>
void Delete(params TEntity[] entities);
/// <summary>
/// Deletes the specified entities.
/// </summary>
/// <param name="entities">The entities.</param>
void Delete(IEnumerable<TEntity> entities);
/// <summary>
/// Change entity state for patch method on web api.
/// </summary>
/// <param name="entity">The entity.</param>
/// /// <param name="state">The entity state.</param>
void ChangeEntityState(TEntity entity, EntityState state);
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedDATA.Api
{
/// <summary>
/// Defines the interfaces for <see cref="IRepository{TEntity}"/> interfaces.
/// </summary>
public interface IRepositoryFactory
{
/// <summary>
/// Gets the specified repository for the <typeparamref name="TEntity"/>.
/// </summary>
/// <param name="hasCustomRepository"><c>True</c> if providing custom repositry</param>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <returns>An instance of type inherited from <see cref="IRepository{TEntity}"/> interface.</returns>
IRepository<TEntity> GetRepository<TEntity>(bool hasCustomRepository = false) where TEntity : class;
}
}

View File

@@ -0,0 +1,78 @@

namespace SharedDATA.Api
{
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
/// <summary>
/// Defines the interface(s) for unit of work.
/// </summary>
public interface IUnitOfWork : IDisposable
{
/// <summary>
/// Changes the database name. This require the databases in the same machine. NOTE: This only work for MySQL right now.
/// </summary>
/// <param name="database">The database name.</param>
/// <remarks>
/// This only been used for supporting multiple databases in the same model. This require the databases in the same machine.
/// </remarks>
void ChangeDatabase(string database);
/// <summary>
/// Gets the specified repository for the <typeparamref name="TEntity"/>.
/// </summary>
/// <param name="hasCustomRepository"><c>True</c> if providing custom repositry</param>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <returns>An instance of type inherited from <see cref="IRepository{TEntity}"/> interface.</returns>
IRepository<TEntity> GetRepository<TEntity>(bool hasCustomRepository = false) where TEntity : class;
/// <summary>
/// Gets the db context.
/// </summary>
/// <returns></returns>
TContext GetDbContext<TContext>() where TContext : DbContext;
/// <summary>
/// Saves all changes made in this context to the database.
/// </summary>
/// <param name="ensureAutoHistory"><c>True</c> if sayve changes ensure auto record the change history.</param>
/// <returns>The number of state entries written to the database.</returns>
int SaveChanges(bool ensureAutoHistory = false);
/// <summary>
/// Asynchronously saves all changes made in this unit of work to the database.
/// </summary>
/// <param name="ensureAutoHistory"><c>True</c> if save changes ensure auto record the change history.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous save operation. The task result contains the number of state entities written to database.</returns>
Task<int> SaveChangesAsync(bool ensureAutoHistory = false);
/// <summary>
/// Executes the specified raw SQL command.
/// </summary>
/// <param name="sql">The raw SQL.</param>
/// <param name="parameters">The parameters.</param>
/// <returns>The number of state entities written to database.</returns>
int ExecuteSqlCommand(string sql, params object[] parameters);
/// <summary>
/// Uses raw SQL queries to fetch the specified <typeparamref name="TEntity"/> data.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <param name="sql">The raw SQL.</param>
/// <param name="parameters">The parameters.</param>
/// <returns>An <see cref="IQueryable{T}"/> that contains elements that satisfy the condition specified by raw SQL.</returns>
IQueryable<TEntity> FromSql<TEntity>(string sql, params object[] parameters) where TEntity : class;
/// <summary>
/// Uses TrakGrap Api to attach disconnected entities
/// </summary>
/// <param name="rootEntity"> Root entity</param>
/// <param name="callback">Delegate to convert Object's State properities to Entities entry state.</param>
void TrackGraph(object rootEntity, Action<EntityEntryGraphNode> callback);
}
}

View File

@@ -0,0 +1,27 @@

namespace SharedDATA.Api
{
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
/// <summary>
/// Defines the interface(s) for generic unit of work.
/// </summary>
public interface IUnitOfWork<TContext> : IUnitOfWork where TContext : DbContext
{
/// <summary>
/// Gets the db context.
/// </summary>
/// <returns>The instance of type <typeparamref name="TContext"/>.</returns>
TContext DbContext { get; }
/// <summary>
/// Saves all changes made in this context to the database with distributed transaction.
/// </summary>
/// <param name="ensureAutoHistory"><c>True</c> if save changes ensure auto record the change history.</param>
/// <param name="unitOfWorks">An optional <see cref="IUnitOfWork"/> array.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous save operation. The task result contains the number of state entities written to database.</returns>
Task<int> SaveChangesAsync(bool ensureAutoHistory = false, params IUnitOfWork[] unitOfWorks);
}
}

View File

@@ -0,0 +1,942 @@

namespace SharedDATA.Api
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using SharedDATA.Context;
/// <summary>
/// Represents a default generic repository implements the <see cref="IRepository{TEntity}"/> interface.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly DbContext _dbContext;
protected readonly DbSet<TEntity> _dbSet;
/// <summary>
/// Initializes a new instance of the <see cref="Repository{TEntity}"/> class.
/// </summary>
/// <param name="dbContext">The database context.</param>
public Repository(DbContext dbContext)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_dbSet = _dbContext.Set<TEntity>();
}
/// <summary>
/// Changes the table name. This require the tables in the same database.
/// </summary>
/// <param name="table"></param>
/// <remarks>
/// This only been used for supporting multiple tables in the same model. This require the tables in the same database.
/// </remarks>
public virtual void ChangeTable(string table)
{
if (_dbContext.Model.FindEntityType(typeof(TEntity)) is IConventionEntityType relational)
{
relational.SetTableName(table);
}
}
/// <summary>
/// Gets all entities. This method is not recommended
/// </summary>
/// <returns>The <see cref="IQueryable{TEntity}"/>.</returns>
public IQueryable<TEntity> GetAll()
{
return _dbSet;
}
/// <summary>
/// Gets all entities. This method is not recommended
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>true</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>Ex: This method defaults to a read-only, no-tracking query.</remarks>
public IQueryable<TEntity> GetAll(
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null, bool disableTracking = true, bool ignoreQueryFilters = false)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return orderBy(query);
}
else
{
return query;
}
}
/// <summary>
/// Gets the <see cref="IPagedList{TEntity}"/> based on a predicate, orderby delegate and page information. This method default no-tracking query.
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="pageIndex">The index of page.</param>
/// <param name="pageSize">The size of the page.</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
public virtual IPagedList<TEntity> GetPagedList(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
int pageIndex = 0,
int pageSize = 20,
bool disableTracking = true,
bool ignoreQueryFilters = false)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return orderBy(query).ToPagedList(pageIndex, pageSize);
}
else
{
return query.ToPagedList(pageIndex, pageSize);
}
}
/// <summary>
/// Gets the <see cref="IPagedList{TEntity}"/> based on a predicate, orderby delegate and page information. This method default no-tracking query.
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="pageIndex">The index of page.</param>
/// <param name="pageSize">The size of the page.</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
/// </param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
public virtual Task<IPagedList<TEntity>> GetPagedListAsync(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
int pageIndex = 0,
int pageSize = 20,
bool disableTracking = true,
CancellationToken cancellationToken = default(CancellationToken),
bool ignoreQueryFilters = false)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return orderBy(query).ToPagedListAsync(pageIndex, pageSize, 0, cancellationToken);
}
else
{
return query.ToPagedListAsync(pageIndex, pageSize, 0, cancellationToken);
}
}
/// <summary>
/// Gets the <see cref="IPagedList{TResult}"/> based on a predicate, orderby delegate and page information. This method default no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="pageIndex">The index of page.</param>
/// <param name="pageSize">The size of the page.</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TResult}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
public virtual IPagedList<TResult> GetPagedList<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
int pageIndex = 0,
int pageSize = 20,
bool disableTracking = true,
bool ignoreQueryFilters = false)
where TResult : class
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return orderBy(query).Select(selector).ToPagedList(pageIndex, pageSize);
}
else
{
return query.Select(selector).ToPagedList(pageIndex, pageSize);
}
}
/// <summary>
/// Gets the <see cref="IPagedList{TEntity}"/> based on a predicate, orderby delegate and page information. This method default no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="pageIndex">The index of page.</param>
/// <param name="pageSize">The size of the page.</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
/// </param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
public virtual Task<IPagedList<TResult>> GetPagedListAsync<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
int pageIndex = 0,
int pageSize = 20,
bool disableTracking = true,
CancellationToken cancellationToken = default(CancellationToken),
bool ignoreQueryFilters = false)
where TResult : class
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return orderBy(query).Select(selector).ToPagedListAsync(pageIndex, pageSize, 0, cancellationToken);
}
else
{
return query.Select(selector).ToPagedListAsync(pageIndex, pageSize, 0, cancellationToken);
}
}
/// <summary>
/// Gets the first or default entity based on a predicate, orderby delegate and include delegate. This method default no-tracking query.
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
public virtual TEntity GetFirstOrDefault(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true,
bool ignoreQueryFilters = false)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return orderBy(query).FirstOrDefault();
}
else
{
return query.FirstOrDefault();
}
}
/// <inheritdoc />
public virtual async Task<TEntity> GetFirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true,
bool ignoreQueryFilters = false)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return await orderBy(query).FirstOrDefaultAsync();
}
else
{
return await query.FirstOrDefaultAsync();
}
}
/// <summary>
/// Gets the first or default entity based on a predicate, orderby delegate and include delegate. This method default no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
public virtual TResult GetFirstOrDefault<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true,
bool ignoreQueryFilters = false)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return orderBy(query).Select(selector).FirstOrDefault();
}
else
{
return query.Select(selector).FirstOrDefault();
}
}
/// <inheritdoc />
public virtual async Task<TResult> GetFirstOrDefaultAsync<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true, bool ignoreQueryFilters = false)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return await orderBy(query).Select(selector).FirstOrDefaultAsync();
}
else
{
return await query.Select(selector).FirstOrDefaultAsync();
}
}
/// <summary>
/// Uses raw SQL queries to fetch the specified <typeparamref name="TEntity" /> data.
/// </summary>
/// <param name="sql">The raw SQL.</param>
/// <param name="parameters">The parameters.</param>
/// <returns>An <see cref="IQueryable{TEntity}" /> that contains elements that satisfy the condition specified by raw SQL.</returns>
public virtual IQueryable<TEntity> FromSql(string sql, params object[] parameters) => _dbSet.FromSqlRaw(sql, parameters);
/// <summary>
/// Finds an entity with the given primary key values. If found, is attached to the context and returned. If no entity is found, then null is returned.
/// </summary>
/// <param name="keyValues">The values of the primary key for the entity to be found.</param>
/// <returns>The found entity or null.</returns>
public virtual TEntity Find(params object[] keyValues) => _dbSet.Find(keyValues);
/// <summary>
/// Finds an entity with the given primary key values. If found, is attached to the context and returned. If no entity is found, then null is returned.
/// </summary>
/// <param name="keyValues">The values of the primary key for the entity to be found.</param>
/// <returns>A <see cref="Task{TEntity}" /> that represents the asynchronous insert operation.</returns>
public virtual ValueTask<TEntity> FindAsync(params object[] keyValues) => _dbSet.FindAsync(keyValues);
/// <summary>
/// Finds an entity with the given primary key values. If found, is attached to the context and returned. If no entity is found, then null is returned.
/// </summary>
/// <param name="keyValues">The values of the primary key for the entity to be found.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>A <see cref="Task{TEntity}"/> that represents the asynchronous find operation. The task result contains the found entity or null.</returns>
public virtual ValueTask<TEntity> FindAsync(object[] keyValues, CancellationToken cancellationToken) => _dbSet.FindAsync(keyValues, cancellationToken);
/// <summary>
/// Gets the count based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public virtual int Count(Expression<Func<TEntity, bool>> predicate = null)
{
if (predicate == null)
{
return _dbSet.Count();
}
else
{
return _dbSet.Count(predicate);
}
}
/// <summary>
/// Gets async the count based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public virtual async Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate = null)
{
if (predicate == null)
{
return await _dbSet.CountAsync();
}
else
{
return await _dbSet.CountAsync(predicate);
}
}
/// <summary>
/// Gets the long count based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public virtual long LongCount(Expression<Func<TEntity, bool>> predicate = null)
{
if (predicate == null)
{
return _dbSet.LongCount();
}
else
{
return _dbSet.LongCount(predicate);
}
}
/// <summary>
/// Gets async the long count based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public virtual async Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate = null)
{
if (predicate == null)
{
return await _dbSet.LongCountAsync();
}
else
{
return await _dbSet.LongCountAsync(predicate);
}
}
/// <summary>
/// Gets the max based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
public virtual T Max<T>(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, T>> selector = null)
{
if (predicate == null)
return _dbSet.Max(selector);
else
return _dbSet.Where(predicate).Max(selector);
}
/// <summary>
/// Gets the async max based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
public virtual async Task<T> MaxAsync<T>(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, T>> selector = null)
{
if (predicate == null)
return await _dbSet.MaxAsync(selector);
else
return await _dbSet.Where(predicate).MaxAsync(selector);
}
/// <summary>
/// Gets the min based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
public virtual T Min<T>(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, T>> selector = null)
{
if (predicate == null)
return _dbSet.Min(selector);
else
return _dbSet.Where(predicate).Min(selector);
}
/// <summary>
/// Gets the async min based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
public virtual async Task<T> MinAsync<T>(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, T>> selector = null)
{
if (predicate == null)
return await _dbSet.MinAsync(selector);
else
return await _dbSet.Where(predicate).MinAsync(selector);
}
/// <summary>
/// Gets the average based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
public virtual decimal Average(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, decimal>> selector = null)
{
if (predicate == null)
return _dbSet.Average(selector);
else
return _dbSet.Where(predicate).Average(selector);
}
/// <summary>
/// Gets the async average based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
public virtual async Task<decimal> AverageAsync(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, decimal>> selector = null)
{
if (predicate == null)
return await _dbSet.AverageAsync(selector);
else
return await _dbSet.Where(predicate).AverageAsync(selector);
}
/// <summary>
/// Gets the sum based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
public virtual decimal Sum(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, decimal>> selector = null)
{
if (predicate == null)
return _dbSet.Sum(selector);
else
return _dbSet.Where(predicate).Sum(selector);
}
/// <summary>
/// Gets the async sum based on a predicate.
/// </summary>
/// <param name="predicate"></param>
/// /// <param name="selector"></param>
/// <returns>decimal</returns>
public virtual async Task<decimal> SumAsync(Expression<Func<TEntity, bool>> predicate = null, Expression<Func<TEntity, decimal>> selector = null)
{
if (predicate == null)
return await _dbSet.SumAsync(selector);
else
return await _dbSet.Where(predicate).SumAsync(selector);
}
/// <summary>
/// Gets the exists based on a predicate.
/// </summary>
/// <param name="selector"></param>
/// <returns></returns>
public bool Exists(Expression<Func<TEntity, bool>> selector = null)
{
if (selector == null)
{
return _dbSet.Any();
}
else
{
return _dbSet.Any(selector);
}
}
/// <summary>
/// Gets the async exists based on a predicate.
/// </summary>
/// <param name="selector"></param>
/// <returns></returns>
public async Task<bool> ExistsAsync(Expression<Func<TEntity, bool>> selector = null)
{
if (selector == null)
{
return await _dbSet.AnyAsync();
}
else
{
return await _dbSet.AnyAsync(selector);
}
}
/// <summary>
/// Inserts a new entity synchronously.
/// </summary>
/// <param name="entity">The entity to insert.</param>
public virtual TEntity Insert(TEntity entity)
{
return _dbSet.Add(entity).Entity;
}
/// <summary>
/// Inserts a range of entities synchronously.
/// </summary>
/// <param name="entities">The entities to insert.</param>
public virtual void Insert(params TEntity[] entities) => _dbSet.AddRange(entities);
/// <summary>
/// Inserts a range of entities synchronously.
/// </summary>
/// <param name="entities">The entities to insert.</param>
public virtual void Insert(IEnumerable<TEntity> entities) => _dbSet.AddRange(entities);
/// <summary>
/// Inserts a new entity asynchronously.
/// </summary>
/// <param name="entity">The entity to insert.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous insert operation.</returns>
public virtual ValueTask<EntityEntry<TEntity>> InsertAsync(TEntity entity, CancellationToken cancellationToken = default(CancellationToken))
{
return _dbSet.AddAsync(entity, cancellationToken);
// Shadow properties?
//var property = _dbContext.Entry(entity).Property("Created");
//if (property != null) {
//property.CurrentValue = DateTime.Now;
//}
}
/// <summary>
/// Inserts a range of entities asynchronously.
/// </summary>
/// <param name="entities">The entities to insert.</param>
/// <returns>A <see cref="Task" /> that represents the asynchronous insert operation.</returns>
public virtual Task InsertAsync(params TEntity[] entities) => _dbSet.AddRangeAsync(entities);
/// <summary>
/// Inserts a range of entities asynchronously.
/// </summary>
/// <param name="entities">The entities to insert.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous insert operation.</returns>
public virtual Task InsertAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default(CancellationToken)) => _dbSet.AddRangeAsync(entities, cancellationToken);
/// <summary>
/// Updates the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
public virtual void Update(TEntity entity)
{
_dbSet.Update(entity);
}
/// <summary>
/// Updates the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
public virtual void UpdateAsync(TEntity entity)
{
_dbSet.Update(entity);
}
/// <summary>
/// Updates the specified entities.
/// </summary>
/// <param name="entities">The entities.</param>
public virtual void Update(params TEntity[] entities) => _dbSet.UpdateRange(entities);
/// <summary>
/// Updates the specified entities.
/// </summary>
/// <param name="entities">The entities.</param>
public virtual void Update(IEnumerable<TEntity> entities) => _dbSet.UpdateRange(entities);
/// <summary>
/// Deletes the specified entity.
/// </summary>
/// <param name="entity">The entity to delete.</param>
public virtual void Delete(TEntity entity) => _dbSet.Remove(entity);
/// <summary>
/// Deletes the entity by the specified primary key.
/// </summary>
/// <param name="id">The primary key value.</param>
public virtual void Delete(object id)
{
// using a stub entity to mark for deletion
var typeInfo = typeof(TEntity).GetTypeInfo();
var key = _dbContext.Model.FindEntityType(typeInfo).FindPrimaryKey().Properties.FirstOrDefault();
var property = typeInfo.GetProperty(key?.Name);
if (property != null)
{
var entity = Activator.CreateInstance<TEntity>();
property.SetValue(entity, id);
_dbContext.Entry(entity).State = EntityState.Deleted;
}
else
{
var entity = _dbSet.Find(id);
if (entity != null)
{
Delete(entity);
}
}
}
/// <summary>
/// Deletes the specified entities.
/// </summary>
/// <param name="entities">The entities.</param>
public virtual void Delete(params TEntity[] entities) => _dbSet.RemoveRange(entities);
/// <summary>
/// Deletes the specified entities.
/// </summary>
/// <param name="entities">The entities.</param>
public virtual void Delete(IEnumerable<TEntity> entities) => _dbSet.RemoveRange(entities);
/// <summary>
/// Gets all entities. This method is not recommended
/// </summary>
/// <returns>The <see cref="IQueryable{TEntity}"/>.</returns>
public async Task<IList<TEntity>> GetAllAsync()
{
return await _dbSet.ToListAsync();
}
/// <summary>
/// Gets all entities. This method is not recommended
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>true</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <param name="ignoreQueryFilters">Ignore query filters</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>Ex: This method defaults to a read-only, no-tracking query.</remarks>
public async Task<IList<TEntity>> GetAllAsync(Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true, bool ignoreQueryFilters = false)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (ignoreQueryFilters)
{
query = query.IgnoreQueryFilters();
}
if (orderBy != null)
{
return await orderBy(query).ToListAsync();
}
else
{
return await query.ToListAsync();
}
}
/// <summary>
/// Change entity state for patch method on web api.
/// </summary>
/// <param name="entity">The entity.</param>
/// /// <param name="state">The entity state.</param>
public void ChangeEntityState(TEntity entity, EntityState state)
{
_dbContext.Entry(entity).State = state;
}
}
}

View File

@@ -0,0 +1,225 @@

namespace SharedDATA.Api
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Transactions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
/// <summary>
/// Represents the default implementation of the <see cref="IUnitOfWork"/> and <see cref="IUnitOfWork{TContext}"/> interface.
/// </summary>
/// <typeparam name="TContext">The type of the db context.</typeparam>
public class UnitOfWork<TContext> : IRepositoryFactory, IUnitOfWork<TContext>, IUnitOfWork where TContext : DbContext
{
private readonly TContext _context;
private bool disposed = false;
private Dictionary<Type, object> repositories;
/// <summary>
/// Initializes a new instance of the <see cref="UnitOfWork{TContext}"/> class.
/// </summary>
/// <param name="context">The context.</param>
public UnitOfWork(TContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
/// <summary>
/// Gets the db context.
/// </summary>
/// <returns>The instance of type <typeparamref name="TContext"/>.</returns>
public TContext DbContext => _context;
/// <summary>
/// Changes the database name. This require the databases in the same machine. NOTE: This only work for MySQL right now.
/// </summary>
/// <param name="database">The database name.</param>
/// <remarks>
/// This only been used for supporting multiple databases in the same model. This require the databases in the same machine.
/// </remarks>
public void ChangeDatabase(string database)
{
var connection = _context.Database.GetDbConnection();
if (connection.State.HasFlag(ConnectionState.Open))
{
connection.ChangeDatabase(database);
}
else
{
var connectionString = Regex.Replace(connection.ConnectionString.Replace(" ", ""), @"(?<=[Dd]atabase=)\w+(?=;)", database, RegexOptions.Singleline);
connection.ConnectionString = connectionString;
}
// Following code only working for mysql.
var items = _context.Model.GetEntityTypes();
foreach (var item in items)
{
if (item is IConventionEntityType entityType)
{
entityType.SetSchema(database);
}
}
}
/// <summary>
/// Gets the db context.
/// </summary>
/// <returns></returns>
public TDbContext GetDbContext<TDbContext>() where TDbContext : DbContext
{
if (_context == null)
return null;
return _context as TDbContext;
}
/// <summary>
/// Gets the specified repository for the <typeparamref name="TEntity"/>.
/// </summary>
/// <param name="hasCustomRepository"><c>True</c> if providing custom repositry</param>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <returns>An instance of type inherited from <see cref="IRepository{TEntity}"/> interface.</returns>
public IRepository<TEntity> GetRepository<TEntity>(bool hasCustomRepository = false) where TEntity : class
{
if (repositories == null)
{
repositories = new Dictionary<Type, object>();
}
// what's the best way to support custom reposity?
if (hasCustomRepository)
{
var customRepo = _context.GetService<IRepository<TEntity>>();
if (customRepo != null)
{
return customRepo;
}
}
var type = typeof(TEntity);
if (!repositories.ContainsKey(type))
{
repositories[type] = new Repository<TEntity>(_context);
}
return (IRepository<TEntity>)repositories[type];
}
/// <summary>
/// Executes the specified raw SQL command.
/// </summary>
/// <param name="sql">The raw SQL.</param>
/// <param name="parameters">The parameters.</param>
/// <returns>The number of state entities written to database.</returns>
public int ExecuteSqlCommand(string sql, params object[] parameters) => _context.Database.ExecuteSqlRaw(sql, parameters);
/// <summary>
/// Uses raw SQL queries to fetch the specified <typeparamref name="TEntity" /> data.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <param name="sql">The raw SQL.</param>
/// <param name="parameters">The parameters.</param>
/// <returns>An <see cref="IQueryable{T}" /> that contains elements that satisfy the condition specified by raw SQL.</returns>
public IQueryable<TEntity> FromSql<TEntity>(string sql, params object[] parameters) where TEntity : class => _context.Set<TEntity>().FromSqlRaw(sql, parameters);
/// <summary>
/// Saves all changes made in this context to the database.
/// </summary>
/// <param name="ensureAutoHistory"><c>True</c> if save changes ensure auto record the change history.</param>
/// <returns>The number of state entries written to the database.</returns>
public int SaveChanges(bool ensureAutoHistory = false)
{
if (ensureAutoHistory)
{
_context.EnsureAutoHistory();
}
return _context.SaveChanges();
}
/// <summary>
/// Asynchronously saves all changes made in this unit of work to the database.
/// </summary>
/// <param name="ensureAutoHistory"><c>True</c> if save changes ensure auto record the change history.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous save operation. The task result contains the number of state entities written to database.</returns>
public async Task<int> SaveChangesAsync(bool ensureAutoHistory = false)
{
if (ensureAutoHistory)
{
_context.EnsureAutoHistory();
}
return await _context.SaveChangesAsync();
}
/// <summary>
/// Saves all changes made in this context to the database with distributed transaction.
/// </summary>
/// <param name="ensureAutoHistory"><c>True</c> if save changes ensure auto record the change history.</param>
/// <param name="unitOfWorks">An optional <see cref="IUnitOfWork"/> array.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous save operation. The task result contains the number of state entities written to database.</returns>
public async Task<int> SaveChangesAsync(bool ensureAutoHistory = false, params IUnitOfWork[] unitOfWorks)
{
using (var ts = new TransactionScope())
{
var count = 0;
foreach (var unitOfWork in unitOfWorks)
{
count += await unitOfWork.SaveChangesAsync(ensureAutoHistory);
}
count += await SaveChangesAsync(ensureAutoHistory);
ts.Complete();
return count;
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="disposing">The disposing.</param>
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// clear repositories
if (repositories != null)
{
repositories.Clear();
}
// dispose the db context.
_context.Dispose();
}
}
disposed = true;
}
public void TrackGraph(object rootEntity, Action<EntityEntryGraphNode> callback)
{
_context.ChangeTracker.TrackGraph(rootEntity, callback);
}
}
}

View File

@@ -0,0 +1,118 @@

namespace SharedDATA.Api
{
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extension methods for setting up unit of work related services in an <see cref="IServiceCollection"/>.
/// </summary>
public static class UnitOfWorkServiceCollectionExtensions
{
/// <summary>
/// Registers the unit of work given context as a service in the <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TContext">The type of the db context.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
/// <remarks>
/// This method only support one db context, if been called more than once, will throw exception.
/// </remarks>
public static IServiceCollection AddUnitOfWork<TContext>(this IServiceCollection services) where TContext : DbContext
{
services.AddScoped<IRepositoryFactory, UnitOfWork<TContext>>();
// Following has a issue: IUnitOfWork cannot support multiple dbcontext/database,
// that means cannot call AddUnitOfWork<TContext> multiple times.
// Solution: check IUnitOfWork whether or null
services.AddScoped<IUnitOfWork, UnitOfWork<TContext>>();
services.AddScoped<IUnitOfWork<TContext>, UnitOfWork<TContext>>();
return services;
}
/// <summary>
/// Registers the unit of work given context as a service in the <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TContext1">The type of the db context.</typeparam>
/// <typeparam name="TContext2">The type of the db context.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
/// <remarks>
/// This method only support one db context, if been called more than once, will throw exception.
/// </remarks>
public static IServiceCollection AddUnitOfWork<TContext1, TContext2>(this IServiceCollection services)
where TContext1 : DbContext
where TContext2 : DbContext
{
services.AddScoped<IUnitOfWork<TContext1>, UnitOfWork<TContext1>>();
services.AddScoped<IUnitOfWork<TContext2>, UnitOfWork<TContext2>>();
return services;
}
/// <summary>
/// Registers the unit of work given context as a service in the <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TContext1">The type of the db context.</typeparam>
/// <typeparam name="TContext2">The type of the db context.</typeparam>
/// <typeparam name="TContext3">The type of the db context.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
/// <remarks>
/// This method only support one db context, if been called more than once, will throw exception.
/// </remarks>
public static IServiceCollection AddUnitOfWork<TContext1, TContext2, TContext3>(this IServiceCollection services)
where TContext1 : DbContext
where TContext2 : DbContext
where TContext3 : DbContext
{
services.AddScoped<IUnitOfWork<TContext1>, UnitOfWork<TContext1>>();
services.AddScoped<IUnitOfWork<TContext2>, UnitOfWork<TContext2>>();
services.AddScoped<IUnitOfWork<TContext3>, UnitOfWork<TContext3>>();
return services;
}
/// <summary>
/// Registers the unit of work given context as a service in the <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TContext1">The type of the db context.</typeparam>
/// <typeparam name="TContext2">The type of the db context.</typeparam>
/// <typeparam name="TContext3">The type of the db context.</typeparam>
/// <typeparam name="TContext4">The type of the db context.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
/// <remarks>
/// This method only support one db context, if been called more than once, will throw exception.
/// </remarks>
public static IServiceCollection AddUnitOfWork<TContext1, TContext2, TContext3, TContext4>(this IServiceCollection services)
where TContext1 : DbContext
where TContext2 : DbContext
where TContext3 : DbContext
where TContext4 : DbContext
{
services.AddScoped<IUnitOfWork<TContext1>, UnitOfWork<TContext1>>();
services.AddScoped<IUnitOfWork<TContext2>, UnitOfWork<TContext2>>();
services.AddScoped<IUnitOfWork<TContext3>, UnitOfWork<TContext3>>();
services.AddScoped<IUnitOfWork<TContext4>, UnitOfWork<TContext4>>();
return services;
}
/// <summary>
/// Registers the custom repository as a service in the <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TRepository">The type of the custom repositry.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
public static IServiceCollection AddCustomRepository<TEntity, TRepository>(this IServiceCollection services)
where TEntity : class
where TRepository : class, IRepository<TEntity>
{
services.AddScoped<IRepository<TEntity>, TRepository>();
return services;
}
}
}