注:此博客仅适合于刚入门的Asp.net Core初学者,仅做参考。

学了3个月的Asp.net Core,将之前一个系统(http://caijt.com/it)的PHP后端用Asp.net Core重写了一遍,http://it.caijt.com:1001 (注:是日本服务器,比较慢),刚入门时,我是想用DDD或ABP这种高大上的框架来重写我之前的系统,后面我发现这些概念对我这个刚入门的初学者来说,理解起来还是有点困难,也可能我经历系统还是比较简单,用这些框架反而会比较麻烦。

代码:https://github.com/Caijt/ItSysAspNetCore

以下是我的分层图,非常简单的分层,连标准三层都不是,用了EF我觉得Respository仓储层没必要,如果是用Sql或Dapper的话,就会加个Respository层

  • ItSys:UI层,Asp.net Core 项目类型为WebApi接口
  • ItSys.Dto:数据传输对象
  • ItSys.Entity:实体层,一般是一个实体对应数据库一个表,也有一个实体对应视图
  • ItSys.EntityFramework:EF Core层
  • ItSys.Service:服务层,封装了几个主要Service基层,里面主要封装了GetList(获取列表)、GetPageList(获取分页列表)、Create(创建实体)、Update(更新实体)、Delete(删除实体)通用方法,实体的Service类只要继承了某个Service类,就具备了GetList、GetPageList等方法了。

用框架的目标都是一致的,不写重复的代码!对于框架,我的理解就是把通用的重复的代码提取出来,写成一个基类,然后在那么需要个性化的地方挖坑,派生类中再对这些坑进行补充,这样就实现了每个派生类有基类的通用代码,也能有派生类独特的代码。每个派生类只写跟别人不一样的代码,不写重复性代码。

可能说得还不太能准确表达我想说的意思,下面以代码展示。

例如查询列表GetList功能,我用EF的话,那我IT资产及IT合同的Service类,需要这样写

//IT资产查询列表方法
public List<ItAssetDto> GetList(ItAssetQueryDto queryParams)
{
var query = dbContext.Set<ItAsset>().AsQueryable(); query = query.Include(e => e.CreateUser); #region 资产编号
if (!string.IsNullOrWhiteSpace(queryParams.no))
{
query = query.Where(e => e.no.Contains(queryParams.no));
}
#endregion
#region 资产型号
if (!string.IsNullOrWhiteSpace(queryParams.model))
{
query = query.Where(e => e.no.Contains(queryParams.model));
}
#endregion
#region 标识号
if (!string.IsNullOrWhiteSpace(queryParams.diy_no))
{
query = query.Where(e => e.diy_no.Contains(queryParams.diy_no));
}
#endregion
if (queryParams.sortOrder == "no")
{
query = query.OrderBy(e => e.no);
}
if (queryParams.sortOrder == "inbound_date")
{
query = query.OrderBy(e => e.inbound_date);
}
return query.Select(e => new ItAssetDto
{
no = e.no,
inbound_date = e.inbound_date,
id = e.Id
})
.ToList();
}
//IT合同查询列表方法
public List<ItContractDto> GetList(ItContractQueryDto queryParams)
{
var query = dbContext.Set<ItContract>().AsQueryable(); query = query.Include(e => e.CreateUser).Include(e=>e.Supplier); #region 合同编号
if (!string.IsNullOrWhiteSpace(queryParams.no))
{
query = query.Where(e => e.no.Contains(queryParams.no));
}
#endregion
#region 合同名称
if (!string.IsNullOrWhiteSpace(queryParams.name))
{
query = query.Where(e => e.name.Contains(queryParams.name));
}
#endregion
return query.Select(e => new ItContractDto
{
no = e.no,
name = e.name,
id = e.Id
})
.ToList();
}

有没有从其中发现一些重复,但又不重复的地方,重复的是 dbContext.Set<实体>().Include().Where().OrderBy().Select().ToList();不同的是每个实体表它的Where、Include、OrderBy、Select都不太一样。那我就在这些地方挖坑。

如下代码,我定义了一个基类,里面有selectExpression、 onInclude、onWhere、orderProp属性,这些都是我挖的坑,哈哈。然后定义了一个通用的GetList方法,那么派生类继承于这个基类后,不用写任何方法,就有了通用的GetList方法,如果需要具有字段查询功能或字段排序功能的话,就在派生类的构造方法里对这些坑进行赋值。

using AutoMapper;
using ItSys.Dto;
using ItSys.EntityFramework;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text; namespace ItSys.Service.Base
{
public class BaseService<TEntity, TDto, TQueryDto>
where TEntity : class
where TQueryDto : IQueryDto
{
protected ItSysDbContext dbContext;
protected IMapper mapper; /// <summary>
/// 实体转化为Dto对象的表达式
/// </summary>
protected Expression<Func<TEntity, TDto>> selectExpression { get; set; } /// <summary>
/// 构建Include关联属性数据
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
protected Func<IQueryable<TEntity>, IQueryable<TEntity>> onInclude { get; set; } /// <summary>
/// 构建Where查询
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
protected Func<IQueryable<TEntity>, TQueryDto, IQueryable<TEntity>> onWhere { get; set; }
/// <summary>
/// 根据排序字段的字符串返回一个排序表达式
/// </summary>
protected Func<string, Expression<Func<TEntity, dynamic>>> orderProp { get; set; } public BaseService(ItSysDbContext dbContext, IMapper mapper)
{
this.dbContext = dbContext;
this.mapper = mapper;
selectExpression = e => mapper.Map<TDto>(e);
}
protected List<TDto> GetList(TQueryDto queryParams)
{
var query = dbContext.Set<TEntity>().AsNoTracking(); #region 加载导航属性
if (onInclude != null)
{
query = onInclude(query);
}
#endregion #region 查询条件
if (onWhere != null)
{
query = onWhere(query, queryParams);
}
#endregion #region 排序
var exp = orderProp != null ? orderProp(queryParams.orderProp) : null;
if (exp != null)
{
query = queryParams.orderDesc.GetValueOrDefault(true) ? query.OrderByDescending(exp) : query.OrderBy(exp);
}
#endregion return query.Select(selectExpression).ToList();
}
}
}

下面是上面代码IQueryDto对象接口的定义代码

namespace ItSys.Dto
{
public interface IQueryDto
{
/// <summary>
/// 每页数量
/// </summary>
int pageSize { get; set; }
/// <summary>
/// 当前页
/// </summary>
int currentPage { get; set; }/// <summary>
/// 排序字段
/// </summary>
string orderProp { get; set; }
/// <summary>
/// 是否倒序排序
/// </summary>
bool? orderDesc { get; set; }
}
}

现在的IT资产的Service类就可以很简单了,继承BaseService,泛型类型第一个是实体类型ItAsset,第二个是对应的Dto对象ItAssetDto,第三个是实现了IQueryDto接口的查询参数对象ItAssetQueryDto,然后不用写一个方法,只要在构造方法里对onWhere、OrderProp及SelectExpression属性配置就好了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AutoMapper;
using ItSys.Dto;
using ItSys.Entity;
using ItSys.EntityFramework;
using ItSys.Service.Base; namespace ItSys.Service.It
{
public class ItAssetService : BaseService<ItAsset, ItAssetDto, ItAssetQueryDto>
{
public ItAssetService(ItSysDbContext dbContext, IMapper mapper) : base(dbContext, mapper)
{
//定义Where的坑
onWhere = (query, queryParams) =>
{
#region 资产编号
if (!string.IsNullOrWhiteSpace(queryParams.no))
{
query = query.Where(e => e.no.Contains(queryParams.no));
}
#endregion
#region 资产型号
if (!string.IsNullOrWhiteSpace(queryParams.model))
{
query = query.Where(e => e.no.Contains(queryParams.model));
}
#endregion
return query;
};
//定义Order的坑
orderProp = prop =>
{
switch (prop)
{
case "create_time":
return e => e.CreateTime;
case "update_time":
return e => e.UpdateTime;
case "no":
return e => e.no;
}
return null;
};
//定义Select的坑
selectExpression = e => new ItAssetDto
{
id = e.Id,
no = e.no,
model = e.model
};
}
}
}

按照这个思路,给Create,Update,Delete方法也挖坑,我是在三个方法的之前跟之后分别挖了两个坑,因为有些实体创建时我需要给某些字段定义初始值,例如create_time字段,我可以在onBeforeCreate给实体初始化create_time值,有些实体我需要在更新时定义字段值,例如update_time,我在onBeforeUpdate初始化update_time的值,有些实体的删除,我需要在删除之前查询此实体跟其它表还有没有关联,我可以在onBeforeDelete中查询。

下面以Create代码为例

protected Action<TEntity, TCreateDto> onBeforeCreate { get; set; }
protected Action<TEntity, TCreateDto> onAfterCreate { get; set; } /// <summary>
/// 创建实体
/// </summary>
/// <returns></returns>
public virtual TDto Create(TCreateDto createDto)
{
var entity = mapper.Map<TEntity>(createDto);
if (onBeforeCreate != null)
{
onBeforeCreate(entity, createDto);
}
dbSet.Add(entity);
dbContext.SaveChanges();
if (onAfterCreate != null)
{
onAfterCreate(entity, createDto);
}
return mapper.Map<TDto>(entity);
}

如果从github下载了我的代码看后会发现里面的代码跟上面的代码还有很大差别,因为我把方法拆得更细,主要考虑一些特殊情况,方法拆细点,可以实现更多特殊操作,不过思路是一样的,都是按上面的方式,在适合的地点挖坑。

介绍我这几个主要的Service基类,我是实体的一些通用特点(例如说某些实体都有Id主键,某些实体都有create_time、update_time字段)进行定义的

  • ViewService<TViewEntity, TDto, TQueryDto>:这个主要用于视图查询,没有增删改操作;
  • EntityViewService<TEntity,TViewEntity,TDto,TCreateDto,TUpdateDto,TQueryDto> :需定义实体与视图实体,因为我有一些实体的查询列表会比较麻烦,比如查询时还要统计某些关联记录的值,在EF中查询起来很不方便,所以在数据库再创建对应的视图查询,同时在系统中定义对应的视图实体,实体就用来增删改,视图实体就用来查;
  • EntityService<TEntity, TDto, TCreateDto, TUpdateDto, TQueryDto> :当实体的查询列表没那么复杂时,可只定义一个实体,也就是实体跟视图实体是一致的
  • IdEntityViewService<TEntity, TViewEntity,TDto,TCreateDto,TUpdateDto,TQueryDto> :实体都具有Id主键,这个基类里默认会对Id主键字段进行统一的配置,例如默认对Id排序
  • IdEntityService<TEntity, TDto, TCreateDto, TUpdateDto, TQueryDto>不需要额外定义视图实体
  •  AuditViewService<TEntity, TViewEntity, TDto, TCreateDto, TUpdateDto, TQueryDto> :实体都具有主键Id字段、create_time字段、create_user_id字段、update_time字段、update_user_id字段,这个基层默认会在创建时更新时对create_time、update_time字段进行赋值以及排序字段的配置;
  • AuditService<TEntity, TDto, TCreateDto, TUpdateDto, TQueryDto>不需要额外定义视图实体
  • AuditCompanyViewService<TEntity, TViewEntity, TDto, TCreateDto, TUpdateDto, TQueryDto>:在AuditViewService的基础上,实体还具有Company_id字段,因为我的系统里,很多数据都是需要根据当前登录用户的所具有公司管理权限过滤相应的数据,这个Service默认会在查询时进行Company_id字段的过滤
  • AuditCompanyService<TEntity, TDto, TCreateDto, TUpdateDto, TQueryDto>:不需要额外定义视图实体

写到后面,发现有点乱了,不知怎么表达我想表达的东西了,就这样吧,也不是多么有技术含量的设计,有兴趣地看我代码吧。

基于EF的一个简单实战型分层架构的更多相关文章

  1. 基于PHP实现一个简单的在线聊天功能(轮询ajax )

    基于PHP实现一个简单的在线聊天功能(轮询ajax ) 一.总结 1.用的轮询ajax 二.基于PHP实现一个简单的在线聊天功能 一直很想试着做一做这个有意思的功能,感觉复杂的不是数据交互和表结构,麻 ...

  2. 基于 Roslyn 实现一个简单的条件解析引擎

    基于 Roslyn 实现一个简单的条件解析引擎 Intro 最近在做一个勋章的服务,我们想定义一些勋章的获取条件,满足条件之后就给用户颁发一个勋章,定义条件的时候会定义需要哪些参数,参数的类型,获取勋 ...

  3. 使用CEF(二)— 基于VS2019编写一个简单CEF样例

    使用CEF(二)- 基于VS2019编写一个简单CEF样例 在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进 ...

  4. 基于SOUI开发一个简单的小工具

    基于DriectUI有很多库,比如 Duilib (免费) soui (免费) DuiVision (免费) 炫彩 (界面库免费,UI设计器付费,不提供源码) skinui (免费使用,但不开放源码, ...

  5. 基于node实现一个简单的脚手架工具(node控制台交互项目)

    实现控制台输入输出 实现文件读写操作 全原生实现一个简单的脚手架工具 实现vue-cli2源码 一.实现控制台输入输出 关于控制台的输入输出依然是基于node进程管理对象process,在proces ...

  6. 【玩转开源】BananaPi R2 —— 第三篇 基于Openwrt开发一个简单的路由器

    上一篇讲解了R2的网口配置,这一篇我们以BananaPi R2为例子来实现一个简单的路由器:那么一个简单的路由器应该具备什么样的功能呢?最简单的说就是wan+lan+ap这三个功能. 首先wan+la ...

  7. 基于MFC的一个简单计算器

    写一个简单的计算器并不是什么很难的事,主要目的是要通过这个程序来学习和分析其中的核心算法.这个简易计算器的核心部分就是对输入的表达式的正确性判断与求值,其中包括对表达式的解析.中缀表达式转后缀表达式. ...

  8. 基于ABP做一个简单的系统——实战篇:1.项目准备

    现阶段需要做一个小项目,体量很小,业务功能比较简单,就想到用最熟悉的.net来做,更何况现在.net core已经跨平台,也可以在linux服务器上部署.所以决定用.net core 3.1+mysq ...

  9. 基于ABP做一个简单的系统——实战篇:2.代码生成器

    上一篇正说着呢,代码生成器就来了. 1.适用于ABP官网的Startup Template V3.x的包含了登录.用户等页面的MPA应用模板2.当前view仅支持文本框生成,远期规划根据字段类型生成不 ...

随机推荐

  1. 《Java知识应用》Java压缩文件和解压缩

    今天通过Java实现一下:文件的压缩和解压缩. 1. 压缩 准备文件: 准备代码:(压缩) import java.io.BufferedInputStream; import java.io.Buf ...

  2. [系列] Go 使用 defer 函数 要注意的几个点

    概述 defer 函数大家肯定都用过,它在声明时不会立刻去执行,而是在函数 return 后去执行的. 它的主要应用场景有异常处理.记录日志.清理数据.释放资源 等等. 这篇文章不是分享 defer ...

  3. js问题记录(一) -- 关于for in, sort(), 及prototype

    1.关于for in for in : 遍历对象中的可枚举的属性 例子1:for in 遍历对象的键为String类型,所以调用时用Object[key]形式,而不用Object.key形式 < ...

  4. 简单实现vue列表点击某个高亮显示

    比如ul下有4个li元素. 给每个li绑定点击事件@click="select_li(index),然后这个点击时间会将一个全局变量 selectLi 赋值为 index 的值. 然后在每个 ...

  5. Idea集成及使用svn插件

    1 idea集成svn 1.1 svn是什么? SVN是subversion的缩写,是一个开放源代码的版本控制系统,通过采用分支管理系统的高效管理,简而言之就是用于多个人共同开发同一个项目,实现共享资 ...

  6. MongoDB(六):选择字段、限制记录数、排序记录

    1. 选择字段 在MongoDB中,选择字段又叫投影,表示仅选择所需要字段的数据,而不是选择整个文档字段的数据.如果某个文档有5个字段,但只要显示3个字段,那么就只选择3个字段吧,这样做是非常有好处的 ...

  7. GeoSpark入门-可视化

        GeoSpark是一种用于大规模空间数据处理的集群计算. GeoSpark通过一组out-of-the-box空间弹性分布式数据集( SRDDs ) 扩展 Apache Spark,它可以跨机 ...

  8. 拖动条(SeekBar)的功能与用法

    拖动条和进度条非常相似,只是进度条采用颜色填充来表明进度完成的程度,而拖动条则通过滑块的位置来标识数值——而且拖动条允许用户拖动滑块来改变值,因此拖动条通常用于对系统的某种数值进行调节,比如调节音量等 ...

  9. Android 线性布局 LinearLayout

    垂直布局 vertical <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

  10. Filter List Views 筛选器列表视图

    In this lesson, you will learn how to filter a List View. Three techniques, based on different scena ...