这是我参与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方法中,使用到了Company 和 UserId字段。此处我建议将User类提取出来一个 UserDto。如下:
public class UserDto
{
public string UserId { get; set; }
public Company Company { get; set; }
}
这样做有一下两个好处:
- 告诉了调用端,
UserDto里面的全部参数都是我需要的(例如: 避免在ChangeUserPwd的实现中,使用了可能调用者端未 Include 的属性 Team) - 未查询多余的字段
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服务器的性能...