基于EF的KYSharpCore底层框架实现
此项目说明为公司内部,外部引用不了所需要的文件。
一、怎么用
首先我们先讲下,怎么搭建和使用这个框架。
本次案例采用的是
KySharpCode 1.0.2 版本,(目前已经升级到1.2版本)
KYSharpCore.EntityFrameworkCore 2.0.0.0 版本,(目前已经升级到3.1.7.4版本)
KYSharpCore.EntityFrameWork.MySql 2.0.0.0版本,(目前已经升级到3.1.7.4版本)
EF采用的是2.2.6 版本(目前采用到3.1版本)
数据库采用 mysql
先创建一个demo的mvc项目

接着创建一个实体库,实体的类库是通过参数的方式传递的,使用默认的KYSharpCore.EntityFrameWork.MySql 连接时,是appseting.json中配置。
类库需要引用 KySharpCore 程序集,直接nuget上引用

定义一个实体类 Users,当然对应,数据库中的结构要手动创建,这个示例里面没有采用codefirst模式。
public class Users : EntityBase<string> { public string Name { get; set; } }
实体需要继承 EntityBase 基类,EntityBase 基类只定义了一个主键Id字段,其中 <string> 表示Id主键的类型为string类型,系统会自动生成Guid 的字符串。
然后再回到我们的web页面demo项目,添加实体引用。

添加数据库操作库

这样,我们就完成了引用的内容。
接下来做些数据库配置
1、数据库配置
打开appsettings.json 文件,增加数据库信息配置
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnectionString": "server=192.168.20.134;port=3306;database=eftest;uid=root;pwd=3BIW#lP211HkB4Yq;CharSet=utf8;",
"Models": "EFWebTest.Model" //实体类库的名称,用于反射使用,多个用英文逗号隔开
} }
DefaultConnectionString为默认的名称,不能修改。
2、Startup 类中完成注入
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); //注册默认数据库
//注册程序集
services.AddKYSharpCoreDbContext(Configuration); //注入数据库连接配置
services.AddKySharpService(new string[] { "EFWebTest.Service" }); //注入服务层
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
//注册默认数据库
services.AddKYSharpCoreDbContext(Configuration);
只需要增加这行的代码,即可实现数据库上下文的注册
至此,准备工作就已经好了。
services.AddKySharpService(new string[] { "EFWebTest.Service" }); //注入服务层
服务层必须是有继承接口,未继承接口的类不会注入。
kysharcore 1.2版本更新,对服务层增加了一下功能,当KYSharpCore.EntityFrameWork.MySql未升级时(目前3.1.4.7使用的kysharcore版本还是1.1,需要服务层中单独再次引用1.2版本)

增加了可以指定注入的依赖周期,提供依赖注入的3种生命周期。
未指定时,默认为IScopeDependency类型,(1.2以前的版本默认是瞬时类型),建议更新
下面,我们来看看怎么使用。
打开Home控制器,采用构造函数注入User
private readonly IRepository<Users, string> _userRepository; public HomeController(IRepository<Users, string> userRepository) { _userRepository = userRepository; }
public async Task<IActionResult> Index() { //插入一个用户信息 var m=new Users() { Name = "令狐冲"}; int i=await _userRepository.InsertAsync(m); return Content(i.ToString()); }
运行起来,然后看看数据是否写入

这时候,我们就看到了,数据写入了。
至此,也说明,我们连接数据库操作成功。
二、操作方法说明
下面,我们 重点来说明下,仓储层,都提供了哪些操作方法
首先,我们上接口源码
/// <summary> /// 仓储层接口 /// </summary> /// <typeparam name="TEntity">实体对象</typeparam> /// <typeparam name="TKey">主键类型</typeparam> public interface IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey> { #region 同步方法 /// <summary> /// 插入实体 /// </summary> /// <param name="entities"></param> int Insert(params TEntity[] entities); /// <summary> /// 删除实体集合 /// </summary> /// <param name="entities"></param> int Delete(params TEntity[] entities); /// <summary> /// 删除实体集合 /// </summary> /// <param name="entities"></param> int DeleteRange(TEntity[] entities); /// <summary> /// 批量删除符合查询条件的数据 /// </summary> /// <param name="predicate">查询条件表达式</param> /// <returns></returns> int DeleteBatch(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 按主键删除 /// </summary> /// <param name="key"></param> int Delete(TKey key); /// <summary> /// 更新数据,当非跟踪状态下时,需要更改数据时,需要执行此方法 /// </summary> /// <param name="entities">实体对象集合</param> int Update(params TEntity[] entities); /// <summary> /// 获取符合条件的第一条 /// </summary> /// <param name="predicate">查询条件,lambda表达式</param> /// <returns></returns> TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 统计符合条件的记录数 /// </summary> /// <param name="predicate">查询条件,lambda表达式</param> /// <returns>返回符合的记录数</returns> int Count(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 统计符合条件的记录数 /// </summary> /// <param name="predicate">查询条件,lambda表达式</param> /// <returns>返回符合的记录数</returns> long LongCount(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 查找指定主键的实体 /// </summary> /// <param name="key">实体主键</param> /// <returns>符合主键的实体,不存在时返回null</returns> TEntity Get(TKey key); /// <summary> /// 获取<typeparamref name="TEntity"/>不跟踪数据更改(NoTracking)的查询数据源 /// </summary> /// <returns>符合条件的数据集</returns> IQueryable<TEntity> Query(); /// <summary> /// 查询数据,不跟踪 /// </summary> /// <param name="predicate">查询表达式</param> /// <returns></returns> IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 获取数据,不跟踪 /// </summary> /// <param name="predicate">查询条件</param> /// <param name="includes">导航属性</param> /// <returns></returns> IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includes); /// <summary> /// 数据查询,跟踪 /// </summary> /// <returns></returns> IQueryable<TEntity> TrackQuery(); /// <summary> /// 获取<typeparamref name="TEntity"/>跟踪数据更改(Tracking)的查询数据源 /// </summary> /// <returns>符合条件的数据集</returns> IQueryable<TEntity> TrackQuery(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 获取<typeparamref name="TEntity"/>跟踪数据更改(Tracking)的查询数据源,并可Include导航属性 /// </summary> /// <param name="predicate">查询条件</param> /// <param name="includes">要Include操作的属性表达式</param> /// <returns>符合条件的数据集</returns> IQueryable<TEntity> TrackQuery(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includes); /// <summary> /// 分页,不跟踪数据 /// </summary> /// <param name="pageIndex">当前请求的页码</param> /// <param name="pageSize">每页返回的记录数</param> /// <param name="predicate">查询条件表达式</param> /// <param name="sortConditions">排序对象</param> /// <param name="selector">要返回的字段</param> /// <param name="includes">导航属性</param> /// <returns>返回符合条件的数据和条数,返回pageData对象类型,包含Count和Data</returns> PageData<TEntity> Page(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, List<SortCondition<TEntity>> sortConditions, Expression<Func<TEntity, TEntity>> selector = null, params Expression<Func<TEntity, object>>[] includes); /// <summary> /// 分页,不跟踪数据 /// </summary> /// <param name="pageIndex">当前请求的页码</param> /// <param name="pageSize">每页返回的记录数</param> /// <param name="predicate">查询条件表达式</param> /// <param name="sortConditions">排序对象</param> /// <param name="includes">导航属性</param> /// <returns>返回符合条件的数据和条数,返回pageData对象类型,包含Count和Data</returns> PageData<TEntity> Page(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, List<SortCondition<TEntity>> sortConditions, params Expression<Func<TEntity, object>>[] includes); /// <summary> /// 分页,跟踪数据 /// </summary> /// <param name="pageIndex">当前请求的页码</param> /// <param name="pageSize">每页返回的记录数</param> /// <param name="predicate">查询条件表达式</param> /// <param name="sortConditions">排序对象</param> /// <param name="selector">返回的字段</param> /// <param name="includes">导航属性</param> /// <returns>返回符合条件的数据和条数,返回pageData对象类型,包含Count和Data</returns> PageData<TEntity> TrackPage(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, List<SortCondition<TEntity>> sortConditions, Expression<Func<TEntity, TEntity>> selector = null, params Expression<Func<TEntity, object>>[] includes); /// <summary> /// 分页,跟踪数据 /// </summary> /// <param name="pageIndex">当前请求的页码</param> /// <param name="pageSize">每页返回的记录数</param> /// <param name="predicate">查询条件表达式</param> /// <param name="sortConditions">排序对象</param> /// <param name="includes">导航属性</param> /// <returns>返回符合条件的数据和条数,返回pageData对象类型,包含Count和Data</returns> PageData<TEntity> TrackPage(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, List<SortCondition<TEntity>> sortConditions, params Expression<Func<TEntity, object>>[] includes); #endregion 同步方法 #region 异步 /// <summary> /// 插入实体 /// </summary> /// <param name="entities"></param> Task<int> InsertAsync(params TEntity[] entities); /// <summary> /// 删除实体集合 /// </summary> /// <param name="entities"></param> Task<int> DeleteAsync(params TEntity[] entities); /// <summary> /// 删除实体集合 /// </summary> /// <param name="entities"></param> Task<int> DeleteRangeAsync(TEntity[] entities); /// <summary> /// /// </summary> /// <param name="predicate"></param> /// <returns></returns> Task<int> DeleteBatchAsync(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 按主键删除 /// </summary> /// <param name="key"></param> Task<int> DeleteAsync(TKey key); /// <summary> /// 更新数据,当非跟踪状态下时,需要更改数据时,需要执行此方法 /// </summary> /// <param name="entities">实体对象集合</param> Task<int> UpdateAsync(params TEntity[] entities); /// <summary> /// 获取符合条件的第一条 /// </summary> /// <param name="predicate">查询条件,lambda表达式</param> /// <returns></returns> Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 统计符合条件的记录数 /// </summary> /// <param name="predicate">查询条件,lambda表达式</param> /// <returns>返回符合的记录数</returns> Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 统计符合条件的记录数 /// </summary> /// <param name="predicate">查询条件,lambda表达式</param> /// <returns>返回符合的记录数</returns> Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate); /// <summary> /// 查找指定主键的实体 /// </summary> /// <param name="key">实体主键</param> /// <returns>符合主键的实体,不存在时返回null</returns> Task<TEntity> GetAsync(TKey key); /// <summary> /// 分页,不跟踪 /// </summary> /// <param name="pageIndex">当前请求的页码</param> /// <param name="pageSize">每页记录数</param> /// <param name="predicate">查询条件表达式</param> /// <param name="sortConditions">排序条件</param> /// <param name="selector">返回的字段内容</param> /// <param name="includes">导航属性</param> /// <returns>返回总条数和符合的记录数</returns> Task<PageData<TEntity>> PageAsync(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, List<SortCondition<TEntity>> sortConditions, Expression<Func<TEntity, TEntity>> selector = null, params Expression<Func<TEntity, object>>[] includes); /// <summary> /// 分页,不跟踪 /// </summary> /// <param name="pageIndex">当前请求的页码</param> /// <param name="pageSize">每页记录数</param> /// <param name="predicate">查询条件表达式</param> /// <param name="sortConditions">排序条件</param> /// <param name="includes">导航属性</param> /// <returns>返回总条数和符合的记录数</returns> Task<PageData<TEntity>> PageAsync(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, List<SortCondition<TEntity>> sortConditions, params Expression<Func<TEntity, object>>[] includes); /// <summary> /// 分页,跟踪 /// </summary> /// <param name="pageIndex">当前请求的页码</param> /// <param name="pageSize">每页记录数</param> /// <param name="predicate">查询条件表达式</param> /// <param name="sortConditions">排序条件</param> /// <param name="includes">要包含的导航属性</param> /// <param name="selector">返回的字段内容</param> /// <returns>返回总条数和符合的记录数</returns> Task<PageData<TEntity>> PageTrackAsync(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, List<SortCondition<TEntity>> sortConditions, Expression<Func<TEntity, TEntity>> selector = null, params Expression<Func<TEntity, object>>[] includes); /// <summary> /// 分页,跟踪 /// </summary> /// <param name="pageIndex">当前请求的页码</param> /// <param name="pageSize">每页记录数</param> /// <param name="predicate">查询条件表达式</param> /// <param name="sortConditions">排序条件</param> /// <param name="includes">要包含的导航属性</param> /// <returns>返回总条数和符合的记录数</returns> Task<PageData<TEntity>> PageTrackAsync(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, List<SortCondition<TEntity>> sortConditions, params Expression<Func<TEntity, object>>[] includes); #endregion 异步 }
通过接口类源码,我们可以很直观的看到整个数据操作提供的所有函数。
至于使用,也很简单,都是仓储对象.方法名 比如 _userRepository.InsertAsync
但有几点需要说明
1、所有的增删改的方法,都是直接执行的,也就是非事务提交,如执行3个写入方法,前2个成功,第3个失败,则数据不会回滚,前2个数据会进数据库,第3个数据不进库。
那如果要事务提交的话,该如何处理?
采用UnitOfWork来定义上下文和定义事务。看下面示例
private readonly IRepository<Users, string> _userRepository; private readonly IUnitOfWork _unitOfWork; public HomeController(IRepository<Users, string> userRepository, IUnitOfWork unitOfWork) { _userRepository = userRepository; _unitOfWork = unitOfWork; }
构造函数注入 unitOfWork 对象
public async Task<IActionResult> Index() { //开启事务 _unitOfWork.BeginTransaction(); //插入一个用户信息 var m = new Users() { Name = "令狐冲" }; int i = await _userRepository.InsertAsync(m); //提交事务 _unitOfWork.Commit(); return Content(i.ToString()); }
从上面的代码我们可以看到,在最前面,我们开启了事务,最后提交整个事务。这时,就能达到事务提交的目的。如上面举的例子,3个都将回滚,一个都不会写入到数据库中。
2、查询默认是非跟踪状态
为了提供整体查询性能,查询默认都采用AsNoTracking 模式,若要跟踪,则可使用Track开头的方法,比如 TrackQuery 方法。
3、数据的update修改
在跟踪状态下执行修改,只需要对字段重新赋值,然后执行SaveChanges方法即可。
例如下面的代码
public async Task<IActionResult> Index() { //查询出用户 var user = await _userRepository.TrackQuery(m => m.Name == "令狐冲").FirstOrDefaultAsync(); if (user != null) { user.Name = "张无忌"; //执行提交 await _unitOfWork.SaveChangesAsync(); } return Content("Hello World"); }
运行起来

我们看到 原来的令狐冲,已经修改为 张无忌了。
那如果是非跟踪状态下呢,即把 TrackQuery 方法替换成 Query,这时候代码是不会执行变更的,因为没有跟踪。
非跟踪状态下,就需要采用update方法,如
public async Task<IActionResult> Index() { //查询出用户 var user = await _userRepository.Query(m => m.Id == "b29bf6f3e8144cb59952ab4900fd042a").FirstOrDefaultAsync(); if (user != null) { user.Name = "段誉"; //执行提交 await _userRepository.UpdateAsync(user); } return Content("Hello World"); }
变化的地方,我已经标红了。
运行起来

我们看到,数据变更过来了,在执行update的时候就直接提交。
若是多个update,为了事务执行,还是需要跟上面一样,引入unitofwork的事务提交方式(前面开启事务,最后提交事务)。
4、所有的查询返回,默认是 IQueryable 类型,方面开发人员自行转换和提高性能。
5、分页查询
分页采用的是定义了一个新的对象PageData 来进行接收,该对象有两个变量
/// <summary> /// 当异步分页时,不能使用out和ref,需要定义变量来存储Count和数据 /// </summary> public class PageData<T> where T:class { /// <summary> /// 总条数 /// </summary> public int Count { get;set;} /// <summary> /// 符合条件的记录 /// </summary> public IEnumerable<T> Data { get; set; } }
Count,返回总条数,Data返回的数据。
需要注意的是,分页返回,直接返回泛型列表数据,而非IQueryable类型。
以上,我们就介绍完了这个框架的使用。
demo地址:https://github.com/fei686868/KySharpCodeDemo

浙公网安备 33010602011771号