net core开发技巧之映射和Model (三)

568 阅读4分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

引言

在开发当中,最难管理的一般不是那些参与逻辑的类(例如你用某类实现了登录),数量最多且最难管理的莫过于Model了(例如你为了实现登录而写的LoginModel等实体,业务系统增大时类与类之间关系变的错综复杂)。

今一文,来整理一下 管理Model和使用Model的常用技巧

正文

📌 Request WebApi 参数绑定

  • 在web项目的根目录新建一个Models文件夹,然后再区分模块新建子文件夹。例如:登录和注销所使用的Model可以放在Member模块(文件夹)下面,主要是方便后期维护或者他人浏览代码

  • 如果需要和其他项目常常用到相同字段的,那么我建议抽出一个类库用来存放Models

  • 尽量使用继承的方式来归类一下有关联的Model,但是不强求。(例如类1 和 类2 都有相同的字段。但他们处理的业务功能完全不相干,这种情况下就别继承了。以免产生不必要的误解...)

  • 可使用record(需C# 9.0 & .net 5)来简化一下我们的Model,看下面示例,哈哈。瞬间生成了一堆Models:

namespace WebApi.Member
{
    public record SignUpReq(string UserId,string Pwd);
    public record SignOutReq(string UserId,string Pwd);
    public record SignInReq(string UserId,string Pwd);
    public record ForgetPwdReq(string UserId,string OldPwd,string Pwd);
}

📌 Option(配置信息)和 Dto(数据传输对象)

🗝 2.1 Option pattern

日常开发当中,大多数配置可能都是从配置文件读取的(方便在程序运行时也可以修改其配置),为了解决这个需求,微软提供了Option模式。详见文档

我建议把Options全部放在一个项目,这样便可以清晰的看到哪些项目使用了哪些配置(通过查看类引用),而不用东一个项目写了一个配置(Option),西边儿一个项目也有这个配置(Option)。还可以在把我上期做的使用反射优化Option的代码应用上去。这样,改代码改配置再也不是个麻烦事儿...


🗝 2.2 多写Dto(建议使用AutoMapper完成映射)

  • 利于数据查询:(例如)一张User表内可能有30个字段,很多人为了省事直接使用ef core的查询功能将整张表的所有字段都查询出来。这样查我是强烈不推荐的,因为我们本来也只需要用到表中寥寥几个字段。我们把必要字段查出来即可,没必要全部查出而造成不必要的开销

  • 利于服务(Service)告知调用方需要哪些字段,这一点是和第一段相辅相成的:确的告诉方法我需要哪些字段,而不是要把整个表的实体当做对象传给其他方法,大家可以通过这段代码感受一下:

public class User
{
    public string UserId { get; set; }
    public string Name {get;set;}
    public Company Company { get; set; }
    public Team Team { get; set; }
}
public interface IUserService
{
  Task<bool> ChangeUserPwd(User user);
}
public class UserService
{
  public Task<bool> ChangeUserPwd(User user)
  {
      Console.WriteLine("用户{0}目前在{1}公司",user.UserId,user.Commpany);
      return Task.FromResult(true);
  }
}

上面代码IUserService 中的 ChangeUserPwd 需要一个User参数
User 类里面有两个导航属性(Company、Team)
在它的实现类中的ChangeUserPwd方法中,使用到了CompanyUserId字段。此处我建议将User类提取出来一个 UserDto。如下:

public class UserDto
{
     public string UserId { get; set; }
     public Company Company { get; set; }
}

这样做有一下两个好处:

  1. 告诉了调用端,UserDto里面的全部参数都是我需要的(例如: 避免在ChangeUserPwd的实现中,使用了可能调用者端未 Include 的属性 Team
  2. 未查询多余的字段Name,算是做了一个小小的节省

📌 传递错误信息 Result<泛型>的设计

nullable enable
public enum ResponseStatus
{
    BadRequest
}
public enum Status : int
{
  Failure = - 1,
  Ok,
  Redirect
}
public struct Result<T>
{
    public T? Data { get; set; }
    public int Status { get; set; }
    public ErrorResult? Error { get; set; }
}
public class ErrorResult
{
    public ResponseStatus StatusCode { get; set; } = ResponseStatus.BadRequest;
    public string? Message { get; init; }    
    public string? StackTrace { get; init; }    
}

我使用了struct设计Result,这将不会引起GC。同时它也适用于web返回响应和业务层代码返回处理结果。
仔细观察上面代码,Result中的Status字段是int字段,他可以描述多种操作成功后的响应。Error的设计更方便了打印日志等...

为方便构造Result 和 ErrorResult,可以使用继承或扩展方法。这部分请自行发挥

写在最后

日常开发中我们一定要约定好书写Model的规范,避免东一个西一个Model的存在(项目、或项目文件夹中)扰乱视线。同时良好的模型映射与Model 设计也可以小幅提升web服务器的性能...