0. 前言

在上一篇,我们搭建了一个项目框架,基本上是一个完整的项目。目前而言,大部分的应用基本都是这个结构。好的,不废话了,进入今天的议题:完成并实现数据层的基础实现。

1. 数据实体

通常情况下,一个项目的数据实体中字段并不是完全没有规律可寻。通常情况下,必须有一个主键。有些时候,会要求在数据表中增加上次修改时间和创建时间,以及创建人和修改人的主键。

所以,我们可以创建一个泛型父类,来帮我们定义这些公共字段:

using System;

namespace Data.Infrastructure
{
public class BaseEntity<T>
{
public T Id { get; set; } public string ModifyUserId { get; set; } public DateTime? ModifyTime { get; set; } public string CreatorId { get; set; }
public DateTime? CreateTime { get; set; }
}
}

看上述代码里,命名空间并不在Data里,而是在Data.Infrastructure里。这个命名空间 Infrastructure 用来存放一些项目的架构类或者接口,里面还会其他的类。

那么,给这个类补充一些可能有用的方法:

public void Create(object userId)
{
CreatorId = userId.ToString();
CreateTime = DateTime.Now;
} public void Create(object userId, DateTime createTime)
{
CreatorId = userId.ToString();
CreateTime = createTime;
} public void Modify(object userId)
{
ModifyUserId = userId.ToString();
ModifyTime = DateTime.Now;
} public void Modify(object userId, DateTime modifyTime)
{
ModifyUserId = userId.ToString();
ModifyTime = modifyTime;
}

这里用来保存用户ID的字段,我都用了字符串做保存,是借用字符串类型保存数据时能容纳更多的数据类型。

2. 常见数据操作接口

在正常开发中,一个完整的数据操作接口会有很多分类,但是很多时候我们需要分开增删改和查询这两种操作。对于数据库而言,视图和有些数据表都是不被允许改变的,这时候就需要我们只对调用方开放查询接口,而不开放修改接口。

所以,在Domain下应该有以下两个接口:

using System;
using System.Collections.Generic;
using System.Linq.Expressions; namespace Domain.Infrastructure
{
/// <summary>
/// 修改接口
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IModifyRepository<T>
{
/// <summary>
/// 插入数据
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
T Insert(T entity);
/// <summary>
/// 插入数据
/// </summary>
/// <param name="entities"></param>
void Insert(params T[] entities);
/// <summary>
/// 插入数据
/// </summary>
/// <param name="entities"></param>
void Insert(IEnumerable<T> entities);
/// <summary>
/// 保存已提交的修改
/// </summary>
/// <param name="entity"></param>
void Update(T entity);
/// <summary>
/// 保存已提交的修改
/// </summary>
/// <param name="entities"></param>
void Update(params T[] entities);
/// <summary>
/// 更新数据
/// </summary>
/// <param name="predicate"></param>
/// <param name="updator"></param>
void Update(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> updator);
/// <summary>
/// 删除
/// </summary>
/// <param name="entity"></param>
void Delete(T entity);
/// <summary>
/// 删除数据
/// </summary>
/// <param name="entities"></param>
void Delete(params T[] entities);
/// <summary>
/// 根据条件删除数据
/// </summary>
/// <param name="predicate"></param>
void Delete(Expression<Func<T,bool>> predicate);
/// <summary>
/// 删除主键对应的数据
/// </summary>
/// <param name="key"></param>
void DeleteByKey(object key);
/// <summary>
/// 删除主键对应的数据
/// </summary>
/// <param name="keys"></param>
void DeleteByKeys(params object[] keys);
}
}

上述是更新接口,那么我们回过头来写查询接口,查询接口的方法有很多。我们先创建一个接口文件:

using System;
using System.Linq.Expressions; namespace Domain.Infrastructure
{
/// <summary>
/// 查询接口
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ISearchRepository<T>
{ }
}

一个查询接口应该包括以下方法:

  • 获取单个数据
/// <summary>
/// 根据主键获取数据
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
T Get(object key);
/// <summary>
/// 查询
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
T Get(Expression<Func<T,bool>> predicate);
  • 统计数量:
/// <summary>
/// 返回数据库中的数据条目
/// </summary>
/// <returns></returns>
int Count();
/// <summary>
/// 返回数据库中的数据条目,类型为Long
/// </summary>
/// <returns></returns>
long LongCount();
/// <summary>
/// 返回符合条件的数据数目
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
int Count(Expression<Func<T,bool>> predicate);
/// <summary>
/// 返回长整形的符合条件的数目
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
long LongCount(Expression<Func<T,bool>> predicate);
  • 存在性判断
/// <summary>
/// 是否存在满足条件的数据
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
bool IsExists(Expression<Func<T, bool>> predicate);
  • 查询
// <summary>
/// 返回数据库中所有记录
/// </summary>
/// <returns></returns>
List<T> Search();
/// <summary>
/// 返回所有符合条件的数据
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
List<T> Search(Expression<Func<T,bool>> predicate);
/// <summary>
/// 返回一个延迟查询的对象
/// </summary>
/// <returns></returns>
IEnumerable<T> Query();
/// <summary>
/// 返回一个延迟查询的对象,并预设了一个查询条件
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
IEnumerable<T> Query(Expression<Func<T,bool>> predicate);
  • 排序
/// <summary>
/// 排序查询,默认升序
/// </summary>
/// <param name="predicate"></param>
/// <param name="order"></param>
/// <typeparam name="P"></typeparam>
/// <returns></returns>
List<T> Search<P>(Expression<Func<T, bool>> predicate, Expression<Func<T, P>> order);
/// <summary>
/// 排序查找,指定是否降序排列
/// </summary>
/// <param name="predicate"></param>
/// <param name="order"></param>
/// <param name="isDesc"></param>
/// <typeparam name="P"></typeparam>
/// <returns></returns>
List<T> Search<P>(Expression<Func<T, bool>> predicate, Expression<Func<T, P>> order, bool isDesc);
  • 分页

实际上分页的接口定义模型需要两个类的辅助,如果没有这两个类,接口的定义会变得十分复杂,不利于代码的可读性:

using System;
using System.Collections.Generic;
using System.Linq.Expressions; namespace Data.Infrastructure
{
/// <summary>
/// 分页条件模型
/// </summary>
/// <typeparam name="T"></typeparam>
public class PageCondition<T>
{
/// <summary>
/// 查询条件
/// </summary>
/// <value></value>
public Expression<Func<T, bool>> Predicate { get; set; }
/// <summary>
/// 排序字段
/// </summary>
/// <value></value>
public string OrderProperty { get; set; } /// <summary>
/// 升序排序或者降序排序,升序为 asc或者空,降序为desc
/// </summary>
/// <value></value>
public string Sort{get;set;}
/// <summary>
/// 每页最大数据容量
/// </summary>
/// <value></value>
public int PerpageSize { get; set; }
/// <summary>
/// 当前页
/// </summary>
/// <value></value>
public int CurrentPage { get; set; }
}
/// <summary>
/// 分页结果
/// </summary>
/// <typeparam name="T"></typeparam>
public class PageModel<T>
{
/// <summary>
/// 数据
/// </summary>
/// <value></value>
public List<T> Items { get; set; }
/// <summary>
/// 当前页码
/// </summary>
/// <value></value>
public int CurrentPage { get; set; }
/// <summary>
/// 每页最大数据容量
/// </summary>
/// <value></value>
public int PerpageSize { get; set; }
/// <summary>
/// 查询数据总数
/// </summary>
/// <value></value>
public long TotalCount { get; set; }
/// <summary>
/// 总页码
/// </summary>
/// <value></value>
public int TotalPages { get; set; }
}
}

这是两个辅助类,可以简单看一下如果这些参数不进行封装直接传给方法,可以预见方法的参数列表会特别长,这对于可读性和可维护性来说简直就是灾难。我曾经接手过一个项目的维护,上一位开发者在一个方法写了近15个参数,而且还有大量的可选参数,嗯,十分头疼。所以,我不建议大家这样写,一个方法参数超过4个我建议还是封装一下。

那么,看一看方法的声明:

/// <summary>
/// 根据分页参数设置,进行分页查询
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
PageModel<T> Search(PageCondition<T> condition);

这是使用参数封装了请求的写法,小伙伴们可以试试不用封装,方法是如何声明的。

3. 总结

在这一篇带领大家梳理了一下数据访问的接口定义,对一个系统来说,这些方法都是有必要的(但不是每个方法使用频率都一样高)。也是简单的跟大家分享一下我在实际工作中写代码的总结。

更多内容烦请关注我的博客《高先生小屋》

【asp.net core】7 实战之 数据访问层定义的更多相关文章

  1. 企业级应用架构(三)三层架构之数据访问层的改进以及测试DOM的发布

    在上一篇我们在宏观概要上对DAL层进行了封装与抽象.我们的目的主要有两个:第一,解除BLL层对DAL层的依赖,这一点我们通过定义接口做到了:第二,使我们的DAL层能够支持一切数据访问技术,如Ado.n ...

  2. 数据访问层的改进以及测试DOM的发布

    数据访问层的改进以及测试DOM的发布 在上一篇我们在宏观概要上对DAL层进行了封装与抽象.我们的目的主要有两个:第一,解除BLL层对DAL层的依赖,这一点我们通过定义接口做到了:第二,使我们的DAL层 ...

  3. 【无私分享:ASP.NET CORE 项目实战(第四章)】Code First 创建数据库和数据表

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 本章我们来介绍下Asp.net Core 使用 CodeFirst 创建数据库和表,通过 控制台 和 dotnet ef 两种 ...

  4. Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  5. Asp.Net Core 项目实战之权限管理系统(5) 用户登录

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  6. Asp.Net Core 项目实战之权限管理系统(6) 功能管理

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  7. 【无私分享:ASP.NET CORE 项目实战(第十三章)】Asp.net Core 使用MyCat分布式数据库,实现读写分离

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 MyCat2.0版本很快就发布了,关于MyCat的动态和一些问题,大家可以加一下MyCat的官方QQ群:106088787.我 ...

  8. ASP.NET Core Identity 实战(4)授权过程

    这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程 前情提要 在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity ...

  9. 2月送书福利:ASP.NET Core开发实战

    大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...

随机推荐

  1. 前端面试题-http和https区别

    说一下http和https https的SSL加密是在传输层实现的. (1)http和https的基本概念 http: 超文本传输协议,是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求 ...

  2. vue学习-第三个DEMO(计算属性和监视) v-model基础用法

    <div id="demo"> 姓:<input type="text" placeholder="First Name" ...

  3. React:Refs and DOM

    React的哲学是在JS层面构建UI,并把UI组件以功能单位拆分成更小的组件单元,以利于复用和整合,父组件通过props作为操作子组件的唯一通道,甚至子组件想和父组件交互时,也只能依靠父组件通过pro ...

  4. DOM的使用

    1. 修改: 3样: 1. 内容: 3个属性: 1. 获取或修改原始HTML片段: 元素.innerHTML 2. 获取或修改纯文本内容: 元素.textContent vs innerHTML 1. ...

  5. Linux下分析bin文件的10种方法

    这世界有10种人,一种人懂二进制,另一种人不懂二进制. --鲁迅 大家好,我是良许. 二进制文件是我们几乎每天都需要打交道的文件类型,但很少人知道他们的工作原理.这里所讲的二进制文件,是指一些可执行文 ...

  6. haproxy mycat mysql 读写分离MHA高可用

    主机IP信息 hostname IP 172.16.3.140 haproxy01 172.16.3.141 haproxy02 172.16.3.142 mycat01 172.16.3.143 m ...

  7. [转载] Objectiv-C 入门一二三

    http://my.oschina.net/fuckphp/blog/92993 http://my.oschina.net/fuckphp/blog/93217 http://my.oschina. ...

  8. Android gradle 自定义插件

    Gradle 的插件有三种打包方式: 构建脚本:插件逻辑写在 build.gradle 中,适用于逻辑简单的任务,但是该方式实现的插件在该构建脚本之外是不可见的,只能用于当前脚本. buildSrc项 ...

  9. Istio DestinationRule 目标规则

    概念及示例 与VirtualService一样,DestinationRule也是 Istio 流量路由功能的关键部分.您可以将虚拟服务视为将流量如何路由到给定目标地址,然后使用目标规则来配置该目标的 ...

  10. 你确信 X-Forwarded-For 拿到的就是用户真实 IP 吗?

    X-Forwarded-For 拿到的就是真实 IP 吗? 1.故事 在这个小节开始前,我先讲一个开发中的小故事,可以加深一下大家对这个字段的理解. 前段时间要做一个和风控相关的需求,需要拿到用户的 ...