DataEntity 标签 标签


0. 前言

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

1. 数据实体

通常情况下,一个项目的数据实体中字段并不是完全没有规律可寻。通常情况下,必须有一个主键。有些时候,会要求在数据表中增加上次修改时间和创建时间,以及创建人和修改人的主键。
所以,我们可以创建一个泛型父类,来帮我们定义这些公共字段:

  1. using System;
  2. namespace Data.Infrastructure
  3. {
  4. public class BaseEntity<T>
  5. {
  6. public T Id { get; set; }
  7. public string ModifyUserId { get; set; }
  8. public DateTime? ModifyTime { get; set; }
  9. public string CreatorId { get; set; }
  10. public DateTime? CreateTime { get; set; }
  11. }
  12. }

看上述代码里,命名空间并不在Data里,而是在Data.Infrastructure里。这个命名空间 Infrastructure 基础(结构)用来存放一些项目的架构类或者接口,里面还会其他的类。
那么,给这个类补充一些可能有用的方法:

  1. public void Create(object userId)
  2. {
  3. CreatorId = userId.ToString();
  4. CreateTime = DateTime.Now;
  5. }
  6. public void Create(object userId, DateTime createTime)
  7. {
  8. CreatorId = userId.ToString();
  9. CreateTime = createTime;
  10. }
  11. public void Modify(object userId)
  12. {
  13. ModifyUserId = userId.ToString();
  14. ModifyTime = DateTime.Now;
  15. }
  16. public void Modify(object userId, DateTime modifyTime)
  17. {
  18. ModifyUserId = userId.ToString();
  19. ModifyTime = modifyTime;
  20. }

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

2. 常见数据操作接口

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq.Expressions;
  4. namespace Domain.Infrastructure
  5. {
  6. /// <summary>
  7. /// 修改接口
  8. /// </summary>
  9. /// <typeparam name="T"></typeparam>
  10. public interface IModifyRepository<T>
  11. {
  12. /// <summary>
  13. /// 插入数据
  14. /// </summary>
  15. /// <param name="entity"></param>
  16. /// <returns></returns>
  17. T Insert(T entity);
  18. /// <summary>
  19. /// 插入数据
  20. /// </summary>
  21. /// <param name="entities"></param>
  22. void Insert(params T[] entities);
  23. /// <summary>
  24. /// 插入数据
  25. /// </summary>
  26. /// <param name="entities"></param>
  27. void Insert(IEnumerable<T> entities);
  28. /// <summary>
  29. /// 保存已提交的修改
  30. /// </summary>
  31. /// <param name="entity"></param>
  32. void Update(T entity);
  33. /// <summary>
  34. /// 保存已提交的修改
  35. /// </summary>
  36. /// <param name="entities"></param>
  37. void Update(params T[] entities);
  38. /// <summary>
  39. /// 更新数据
  40. /// </summary>
  41. /// <param name="predicate"></param>
  42. /// <param name="updator"></param>
  43. void Update(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> updator);
  44. /// <summary>
  45. /// 删除
  46. /// </summary>
  47. /// <param name="entity"></param>
  48. void Delete(T entity);
  49. /// <summary>
  50. /// 删除数据
  51. /// </summary>
  52. /// <param name="entities"></param>
  53. void Delete(params T[] entities);
  54. /// <summary>
  55. /// 根据条件删除数据
  56. /// </summary>
  57. /// <param name="predicate"></param>
  58. void Delete(Expression<Func<T,bool>> predicate);
  59. /// <summary>
  60. /// 删除主键对应的数据
  61. /// </summary>
  62. /// <param name="key"></param>
  63. void DeleteByKey(object key);
  64. /// <summary>
  65. /// 删除主键对应的数据
  66. /// </summary>
  67. /// <param name="keys"></param>
  68. void DeleteByKeys(params object[] keys);
  69. }
  70. }

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

  1. using System;
  2. using System.Linq.Expressions;
  3. namespace Domain.Infrastructure
  4. {
  5. /// <summary>
  6. /// 查询接口
  7. /// </summary>
  8. /// <typeparam name="T"></typeparam>
  9. public interface ISearchRepository<T>
  10. {
  11. }
  12. }

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

  • 获取单个数据

    1. /// <summary>
    2. /// 根据主键获取数据
    3. /// </summary>
    4. /// <param name="key"></param>
    5. /// <returns></returns>
    6. T Get(object key);
    7. /// <summary>
    8. /// 查询
    9. /// </summary>
    10. /// <param name="predicate"></param>
    11. /// <returns></returns>
    12. T Get(Expression<Func<T,bool>> predicate);
  • 统计数量:

    1. /// <summary>
    2. /// 返回数据库中的数据条目
    3. /// </summary>
    4. /// <returns></returns>
    5. int Count();
    6. /// <summary>
    7. /// 返回数据库中的数据条目,类型为Long
    8. /// </summary>
    9. /// <returns></returns>
    10. long LongCount();
    11. /// <summary>
    12. /// 返回符合条件的数据数目
    13. /// </summary>
    14. /// <param name="predicate"></param>
    15. /// <returns></returns>
    16. int Count(Expression<Func<T,bool>> predicate);
    17. /// <summary>
    18. /// 返回长整形的符合条件的数目
    19. /// </summary>
    20. /// <param name="predicate"></param>
    21. /// <returns></returns>
    22. long LongCount(Expression<Func<T,bool>> predicate);
  • 存在性判断

    1. /// <summary>
    2. /// 是否存在满足条件的数据
    3. /// </summary>
    4. /// <param name="predicate"></param>
    5. /// <returns></returns>
    6. bool IsExists(Expression<Func<T, bool>> predicate);
  • 查询

    1. // <summary>
    2. /// 返回数据库中所有记录
    3. /// </summary>
    4. /// <returns></returns>
    5. List<T> Search();
    6. /// <summary>
    7. /// 返回所有符合条件的数据
    8. /// </summary>
    9. /// <param name="predicate"></param>
    10. /// <returns></returns>
    11. List<T> Search(Expression<Func<T,bool>> predicate);
    12. /// <summary>
    13. /// 返回一个延迟查询的对象
    14. /// </summary>
    15. /// <returns></returns>
    16. IEnumerable<T> Query();
    17. /// <summary>
    18. /// 返回一个延迟查询的对象,并预设了一个查询条件
    19. /// </summary>
    20. /// <param name="predicate"></param>
    21. /// <returns></returns>
    22. IEnumerable<T> Query(Expression<Func<T,bool>> predicate);
  • 排序

    1. /// <summary>
    2. /// 排序查询,默认升序
    3. /// </summary>
    4. /// <param name="predicate"></param>
    5. /// <param name="order"></param>
    6. /// <typeparam name="P"></typeparam>
    7. /// <returns></returns>
    8. List<T> Search<P>(Expression<Func<T, bool>> predicate, Expression<Func<T, P>> order);
    9. /// <summary>
    10. /// 排序查找,指定是否降序排列
    11. /// </summary>
    12. /// <param name="predicate"></param>
    13. /// <param name="order"></param>
    14. /// <param name="isDesc"></param>
    15. /// <typeparam name="P"></typeparam>
    16. /// <returns></returns>
    17. List<T> Search<P>(Expression<Func<T, bool>> predicate, Expression<Func<T, P>> order, bool isDesc);
  • 分页

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq.Expressions;
  4. namespace Data.Infrastructure
  5. {
  6. /// <summary>
  7. /// 分页条件模型
  8. /// </summary>
  9. /// <typeparam name="T"></typeparam>
  10. public class PageCondition<T>
  11. {
  12. /// <summary>
  13. /// 查询条件
  14. /// </summary>
  15. /// <value></value>
  16. public Expression<Func<T, bool>> Predicate { get; set; }
  17. /// <summary>
  18. /// 排序字段
  19. /// </summary>
  20. /// <value></value>
  21. public string OrderProperty { get; set; }
  22. /// <summary>
  23. /// 升序排序或者降序排序,升序为 asc或者空,降序为desc
  24. /// </summary>
  25. /// <value></value>
  26. public string Sort{get;set;}
  27. /// <summary>
  28. /// 每页最大数据容量
  29. /// </summary>
  30. /// <value></value>
  31. public int PerpageSize { get; set; }
  32. /// <summary>
  33. /// 当前页
  34. /// </summary>
  35. /// <value></value>
  36. public int CurrentPage { get; set; }
  37. }
  38. /// <summary>
  39. /// 分页结果
  40. /// </summary>
  41. /// <typeparam name="T"></typeparam>
  42. public class PageModel<T>
  43. {
  44. /// <summary>
  45. /// 数据
  46. /// </summary>
  47. /// <value></value>
  48. public List<T> Items { get; set; }
  49. /// <summary>
  50. /// 当前页码
  51. /// </summary>
  52. /// <value></value>
  53. public int CurrentPage { get; set; }
  54. /// <summary>
  55. /// 每页最大数据容量
  56. /// </summary>
  57. /// <value></value>
  58. public int PerpageSize { get; set; }
  59. /// <summary>
  60. /// 查询数据总数
  61. /// </summary>
  62. /// <value></value>
  63. public long TotalCount { get; set; }
  64. /// <summary>
  65. /// 总页码
  66. /// </summary>
  67. /// <value></value>
  68. public int TotalPages { get; set; }
  69. }
  70. }

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

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

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

3. 总结

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


  • 本文作者:GeekPower - Felix Sun
  • 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!