Ultima attività 1 week ago

anduin's Avatar anduin ha revisionato questo gist 1 week ago. Vai alla revisione

1 file changed, 11 insertions, 15 deletions

fix_entity.md

@@ -2,6 +2,8 @@ Aiursoft 有一套自己的 Entity Framework Core 实体建模规范。
2 2
3 3 这套规范非常严格,内容如下:
4 4
5 + # Aiursoft Entity Framework Core 实体建模规范
6 +
5 7 ## 核心哲学:独裁模式 (Dictator Mode)
6 8
7 9 1. **DbSet 独裁**:数据的增删改(CUD)必须通过 `DbContext.DbSet<T>` 进行。
@@ -48,12 +50,8 @@ Aiursoft 有一套自己的 Entity Framework Core 实体建模规范。
48 50
49 51 * *理由*:在 `new Entity()` 时我们通常只赋值 ID 而不赋值对象,声明为可空是为了满足 C\# 编译器的初始化检查,避免虚假的警告。
50 52
51 - **2.4 禁止 Attribute 冲突**
52 - 禁止在可空类型(`?`)上使用 `[NotNull]`。
53 -
54 - * 如果业务逻辑上该字段允许为空,使用 `string?`。
55 - * 如果业务逻辑上该字段不允许为空,使用 `string` 或 `required string`。
56 - * *导航属性特例*:如 2.3 所述,导航属性必须是 `Type?`,且不得标记 `[NotNull]`。
53 + **2.4 导航属性必须 NotNull**
54 + 所有**导航引用属性**必须被 `[NotNull]` 修饰。
57 55
58 56 **2.5 序列化屏蔽**
59 57 所有**导航引用属性**必须被 `[Newtonsoft.Json.JsonIgnore]` 修饰(防止死循环)。
@@ -69,14 +67,12 @@ Aiursoft 有一套自己的 Entity Framework Core 实体建模规范。
69 67 > ```csharp
70 68 > // 外键 ID (必填)
71 69 > public required Guid UserId { get; set; }
72 - > ```
73 -
74 - > // 导航引用 (必须可空,禁止 virtual,禁止 NotNull)
70 + >
71 + > // 导航引用 (必须可空,必须 NotNull,禁止 virtual)
75 72 > [JsonIgnore]
76 73 > [ForeignKey(nameof(UserId))]
77 - > public User? User { get; init; }
78 - >
79 - > ```
74 + > [NotNull]
75 + > public User? User { get; set; }
80 76 > ```
81 77
82 78 -----
@@ -161,12 +157,12 @@ public class TemplateEntity
161 157 public required Guid ParentId { get; set; }
162 158
163 159 // [规则 2.3, 2.4, 2.5, 2.6]
164 - // 导航引用:Type?, JsonIgnore, ForeignKey
160 + // 导航引用:Type?, JsonIgnore, ForeignKey, NotNull
165 161 // 严禁 virtual (禁用延迟加载)
166 - // 严禁 [NotNull] (避免编译器歧义)
167 162 [JsonIgnore]
168 163 [ForeignKey(nameof(ParentId))]
169 - public ParentEntity? Parent { get; init; }
164 + [NotNull]
165 + public ParentEntity? Parent { get; set; }
170 166
171 167 // [规则 3.1, 3.2, 3.3]
172 168 // 集合:IEnumerable (独裁模式), InverseProperty, new List()

anduin's Avatar anduin ha revisionato questo gist 2 weeks ago. Vai alla revisione

1 file changed, 17 insertions

strict.md(file creato)

@@ -0,0 +1,17 @@
1 + # 严格化
2 +
3 + 我受够了你的代码了!你为了快速实现功能,选择了非常糙快猛的方法。
4 +
5 + 你的各种Helper,像极了初级程序员不会OO,把一样的代码腾了个函数。很多对象本身的行为你不会抽象!
6 +
7 + 很多复杂的验证逻辑你不会封装,封装出来的函数名字极其困惑我都不知道俩有什么区别。
8 +
9 + 而且你不喜欢抛错。我猜你可能是为了拟真真实的生产环境容忍噪音,但是你suppress错误我是完全不理解。现在在开发阶段,什么都应该严格着来。
10 +
11 + 你要赶紧反思,不要用现在这种遇到错误就suppress、遇到feature就糊一段的方式写代码;
12 +
13 + 而是仔细思考尽量让每一层都透明、都函数式、都面向对象,都封装好。
14 +
15 + 别人直接调用你的类,就能完成它的设计功能,而内部处理好安全、验证。
16 +
17 + 你做不到吗?反思,检讨、仔细设计,然后重构!代码要像基类一样暴露出来的api非常简明清晰!

anduin's Avatar anduin ha revisionato questo gist 2 weeks ago. Vai alla revisione

1 file changed, 1 insertion

fix_entity.md

@@ -7,6 +7,7 @@ Aiursoft 有一套自己的 Entity Framework Core 实体建模规范。
7 7 1. **DbSet 独裁**:数据的增删改(CUD)必须通过 `DbContext.DbSet<T>` 进行。
8 8 2. **显式加载**:禁止任何形式的隐式行为(如延迟加载),数据必须显式 `Include`。
9 9 3. **编译期契约**:利用 C\# 类型系统(Type System)在编译期暴露潜在错误,而不是推迟到运行期。
10 + 4. **表格可见**: 所有实体类必须清晰地表达其数据列和关系,避免隐式约定。所有实体类必须出现在 `DbContext` 中的 `public DbSet<Repo> Repos => Set<Repo>();`,表名必须是实体类名的复数形式(如 `User` 对应 `Users` 表)。
10 11
11 12 -----
12 13

anduin's Avatar anduin ha revisionato questo gist 2 weeks ago. Vai alla revisione

1 file changed, 218 insertions

fix_entity.md(file creato)

@@ -0,0 +1,218 @@
1 + Aiursoft 有一套自己的 Entity Framework Core 实体建模规范。
2 +
3 + 这套规范非常严格,内容如下:
4 +
5 + ## 核心哲学:独裁模式 (Dictator Mode)
6 +
7 + 1. **DbSet 独裁**:数据的增删改(CUD)必须通过 `DbContext.DbSet<T>` 进行。
8 + 2. **显式加载**:禁止任何形式的隐式行为(如延迟加载),数据必须显式 `Include`。
9 + 3. **编译期契约**:利用 C\# 类型系统(Type System)在编译期暴露潜在错误,而不是推迟到运行期。
10 +
11 + -----
12 +
13 + ## 1\. 主键规范 (Primary Keys)
14 +
15 + **1.1 类型限制**
16 + 所有实体类的主键必须是 `int` 或 `Guid` 类型。
17 +
18 + * **例外**:直接继承自 `Microsoft.AspNetCore.Identity.IdentityUser` 的实体除外(默认使用 `string`,允许保持原样)。
19 +
20 + **1.2 强制特性**
21 + 所有实体类的主键必须被 `[Key]` Attribute 修饰。
22 +
23 + **1.3 不可变性**
24 + 所有实体类的主键必须是 `{ get; init; }`(创建后不可变)。
25 +
26 + > **示例:**
27 + >
28 + > ```csharp
29 + > [Key]
30 + > public Guid Id { get; init; }
31 + > ```
32 +
33 + -----
34 +
35 + ## 2\. 外键与导航规范 (Foreign Keys & Navigation)
36 +
37 + **2.1 成对定义**
38 + 所有外键关系必须显式定义两个属性:**外键ID** 和 **导航引用**。
39 +
40 + **2.2 外键ID 必填性**
41 +
42 + * **必填关系**:外键ID 必须是 `Guid` (或 `int`),且属性必须是 `required`。
43 + * **可选关系**:外键ID 必须是 `Guid?` (或 `int?`),且属性必须是 `required` (强制显式赋值 `null`)。
44 +
45 + **2.3 导航引用可空性**
46 + 所有**导航引用属性**必须声明为**可空类型**(如 `User?`)。
47 +
48 + * *理由*:在 `new Entity()` 时我们通常只赋值 ID 而不赋值对象,声明为可空是为了满足 C\# 编译器的初始化检查,避免虚假的警告。
49 +
50 + **2.4 禁止 Attribute 冲突**
51 + 禁止在可空类型(`?`)上使用 `[NotNull]`。
52 +
53 + * 如果业务逻辑上该字段允许为空,使用 `string?`。
54 + * 如果业务逻辑上该字段不允许为空,使用 `string` 或 `required string`。
55 + * *导航属性特例*:如 2.3 所述,导航属性必须是 `Type?`,且不得标记 `[NotNull]`。
56 +
57 + **2.5 序列化屏蔽**
58 + 所有**导航引用属性**必须被 `[Newtonsoft.Json.JsonIgnore]` 修饰(防止死循环)。
59 +
60 + **2.6 禁止 Virtual (禁止延迟加载)**
61 + 所有导航属性**严禁**使用 `virtual` 关键字。
62 +
63 + * *后果*:彻底禁用 Lazy Loading Proxy。
64 + * *操作要求*:查询关联数据时,必须显式调用 `.Include()` 和 `.ThenInclude()`。
65 +
66 + > **示例:**
67 + >
68 + > ```csharp
69 + > // 外键 ID (必填)
70 + > public required Guid UserId { get; set; }
71 + > ```
72 +
73 + > // 导航引用 (必须可空,禁止 virtual,禁止 NotNull)
74 + > [JsonIgnore]
75 + > [ForeignKey(nameof(UserId))]
76 + > public User? User { get; init; }
77 + >
78 + > ```
79 + > ```
80 +
81 + -----
82 +
83 + ## 3\. 集合与独裁规范 (Collections & Dictatorship)
84 +
85 + **3.1 类型限制**
86 + 所有反向导航集合必须声明为 `IEnumerable<T>` 类型。
87 +
88 + * *目的*:在编译层面阉割集合的 `.Add()` 和 `.Remove()` 方法。
89 +
90 + **3.2 初始化**
91 + 所有集合属性必须初始化为 `new List<T>()` 以防止空引用异常。
92 +
93 + **3.3 显式关联**
94 + 所有集合属性必须被 `[InverseProperty]` 修饰。
95 +
96 + **3.4 独裁修改原则**
97 + 禁止将 `IEnumerable` 强转为 `List` 来操作数据。
98 +
99 + * **增加子项**:必须创建新对象并 `_dbContext.Add(newItem)`。
100 + * **删除子项**:必须查询出对象并 `_dbContext.Remove(item)`。
101 +
102 + > **示例:**
103 + >
104 + > ```csharp
105 + > [InverseProperty(nameof(ExamPaperSubmission.User))]
106 + > public IEnumerable<ExamPaperSubmission> Submissions { get; init; } = new List<ExamPaperSubmission>();
107 + > ```
108 +
109 + -----
110 +
111 + ## 4\. 数据列规范 (Data Columns)
112 +
113 + **4.1 长度约束**
114 + 所有 `string` 或 `byte[]` 类型的列必须被 `[MaxLength]` 约束。
115 +
116 + **4.2 空值语义**
117 +
118 + * **不可空列**:使用非空类型(如 `string`),严禁使用 `[NotNull]` 修饰可空类型。
119 + * **可空列**:使用可空类型(如 `string?`),且必须在 XML 注释中说明**为空代表什么业务状态**。
120 +
121 + **4.3 不可变属性**
122 + 所有业务上不可编辑的属性(如 `CreationTime`),必须是 `{ get; init; }`。
123 +
124 + -----
125 +
126 + ## 5\. 完整标准模板 (V3.0)
127 +
128 + ```csharp
129 + using System.ComponentModel.DataAnnotations;
130 + using System.ComponentModel.DataAnnotations.Schema;
131 + using System.Diagnostics.CodeAnalysis; // 尽量少用,除非用于辅助方法
132 + using Newtonsoft.Json;
133 +
134 + namespace Aiursoft.Exam.Entities;
135 +
136 + public class TemplateEntity
137 + {
138 + // [规则 1.1, 1.2, 1.3] 主键:Guid/int, Key, init
139 + [Key]
140 + public Guid Id { get; init; }
141 +
142 + // [规则 4.1, 4.2] 必填列:required string, MaxLength, 禁止 [NotNull]
143 + [MaxLength(100)]
144 + public required string Name { get; set; }
145 +
146 + // [规则 4.2, 4.3] 可空列:string?, 注释说明含义, init (如果是创建时不变量)
147 + /// <summary>
148 + /// 描述信息。
149 + /// 若为空,表示用户在创建时未填写备注。
150 + /// </summary>
151 + [MaxLength(1000)]
152 + public string? Description { get; init; }
153 +
154 + // [规则 4.3] 系统字段
155 + public DateTime CreationTime { get; init; } = DateTime.UtcNow;
156 +
157 + // ================= 关联关系 =================
158 +
159 + // [规则 2.2] 外键ID:required (即使是 Guid? 也要 required 以强制显式赋值)
160 + public required Guid ParentId { get; set; }
161 +
162 + // [规则 2.3, 2.4, 2.5, 2.6]
163 + // 导航引用:Type?, JsonIgnore, ForeignKey
164 + // 严禁 virtual (禁用延迟加载)
165 + // 严禁 [NotNull] (避免编译器歧义)
166 + [JsonIgnore]
167 + [ForeignKey(nameof(ParentId))]
168 + public ParentEntity? Parent { get; init; }
169 +
170 + // [规则 3.1, 3.2, 3.3]
171 + // 集合:IEnumerable (独裁模式), InverseProperty, new List()
172 + // 严禁 virtual
173 + [InverseProperty(nameof(ChildEntity.Parent))]
174 + public IEnumerable<ChildEntity> Children { get; init; } = new List<ChildEntity>();
175 + }
176 + ```
177 +
178 + 下面,你正在修正项目的代码,确保其符合严格的实体建模规范。
179 +
180 + 你需要小心谨慎的梳理实体,考虑已经存住于数据库中的数据,修改时要避免break已有的业务逻辑或API。仔细检查一些东西是不是by design或必须打破军规的。
181 +
182 + 如果你发现明显的疏漏、错误,你可以去修正实体类。务必小心!实体类是整个项目的最底层,直接修正可能牵一发动全身,小心行事!
183 +
184 + 如果你完成了变更,你必须对每种数据库类型都创建 migration。例如:
185 +
186 + ```bash
187 + cd ./src/MyOrg.MarkToHtml.Sqlite/
188 + dotnet ef migrations add AddMarkdownDocumentsTable --context "SqliteContext" -s ../MyOrg.MarkToHtml/MyOrg.MarkToHtml.csproj
189 + cd ..
190 + ```
191 +
192 + 为 MySQl 创建迁移的时候,必须先改好 appsettings.json:
193 +
194 + ```json
195 + {
196 + "ConnectionStrings": {
197 + "AllowCache": "True",
198 + "DbType": "MySql",
199 + "DefaultConnection": "Server=localhost;Database=template;Uid=template;Pwd=template_password;"
200 + },
201 + // ... other settings ...
202 + }
203 + ```
204 +
205 + 启动容器:
206 +
207 + ```bash
208 + sudo docker run -d --name db -e MYSQL_RANDOM_ROOT_PASSWORD=true -e MYSQL_DATABASE=template -e MYSQL_USER=template -e MYSQL_PASSWORD=template_password -p 3306:3306 mysql
209 + ```
210 +
211 + 然后才能
212 +
213 + ```bash
214 + cd ./src/MyOrg.MarkToHtml.MySql/
215 + dotnet ef migrations add AddMarkdownDocumentsTable --context "MySqlContext" -s ../MyOrg.MarkToHtml/MyOrg.MarkToHtml.csproj
216 + ```
217 +
218 + 小心操作!现在开始扮演你的实体判官,去修正当前项目吧!

anduin's Avatar anduin ha revisionato questo gist 2 weeks ago. Vai alla revisione

2 files changed, 2 insertions, 2 deletions

Fix_lint.md

@@ -1,4 +1,4 @@
1 - # 修Lint
1 + # 修Lint炸
2 2
3 3 这是一个 .NET 仓库,它使用了 jb 来进行lint。当然,你可以直接运行其根目录下的 lint.sh 文件,以得到lint的结论。它可能无法通过lint。你会从STDOUT里阅读出无法通过的理由。
4 4

fix_tests.md

@@ -1,4 +1,4 @@
1 - # 加测试
1 + # 修测试炸
2 2
3 3 这个仓库是一个 .NET 仓库。显然你不难理解它的项目结构,你可以先简单阅读一下它的代码。
4 4

anduin's Avatar anduin ha revisionato questo gist 2 weeks ago. Vai alla revisione

1 file changed, 17 insertions

fix_tests.md(file creato)

@@ -0,0 +1,17 @@
1 + # 加测试
2 +
3 + 这个仓库是一个 .NET 仓库。显然你不难理解它的项目结构,你可以先简单阅读一下它的代码。
4 +
5 + 它已经有完善的入口点、测试项目等。但是,目前社区普遍批评这个仓库的代码测试通过率很低,经常测试失败。
6 +
7 + 现在你需要扮演一位高级 .NET 专家,试图判断代码测试失败时,是测试用例的问题,还是环境的问题,还是真的发现了业务逻辑错误。
8 +
9 + 注意,你没必要mock很多东西,例如数据库、文件系统等。它们在测试环境完全准备的也非常妥当了。你的测试大可以直接作为集成测试,也就是直接启动Web项目,调用它的API,检查返回效果。
10 +
11 + 被测试的项目中的用例,例如对外部仓库的访问,你也大可以直接让它真的去访问即可。除非是非常昂贵的请求,我们需要Mock。
12 +
13 + Mock的方法就是在TestStartup里,对一个Service进行继承,override掉老方法,然后再把新服务替代老服务注册回去。
14 +
15 + 这样整个测试结构非常清晰。现在,你来试图开始运行 dotnet test 得到测试结果,然后分析测试内容,讨论为什么失败,判断是真的bug还是用例问题,尝试缓解。
16 +
17 + 在写完测试以后,你可以使用 dotnet test 命令来执行你的测试。如果执行失败,除非你非常确定是真的源码业务逻辑有错误,否则不要修改源码。

anduin's Avatar anduin ha revisionato questo gist 2 weeks ago. Vai alla revisione

1 file changed, 3 insertions, 1 deletion

Add_tests.md

@@ -14,4 +14,6 @@ Mock的方法就是在TestStartup里,对一个Service进行继承,override
14 14
15 15 这样整个测试结构非常清晰。现在,你来试图开始增加几个新的测试类,覆盖一些核心功能吧!
16 16
17 - 如果老的代码实在有函数很难覆盖,考虑增加 [ExcludeFromCodeCoverage] 吧!
17 + 如果老的代码实在有函数很难覆盖,考虑增加 [ExcludeFromCodeCoverage] 吧!
18 +
19 + 在写完测试以后,你可以使用 dotnet test 命令来执行你的测试。如果执行失败,除非你非常确定是真的源码业务逻辑有错误,否则不要修改源码。

anduin's Avatar anduin ha revisionato questo gist 2 weeks ago. Vai alla revisione

2 files changed, 4 insertions

Add_tests.md

@@ -1,3 +1,5 @@
1 + # 加测试
2 +
1 3 这个仓库是一个 .NET 仓库。显然你不难理解它的项目结构,你可以先简单阅读一下它的代码。
2 4
3 5 它已经有完善的入口点、测试项目等。但是,目前社区普遍批评这个仓库的代码覆盖率很低。尤其是很多核心业务并没有被测试覆盖。

Fix_lint.md

@@ -1,3 +1,5 @@
1 + # 修Lint
2 +
1 3 这是一个 .NET 仓库,它使用了 jb 来进行lint。当然,你可以直接运行其根目录下的 lint.sh 文件,以得到lint的结论。它可能无法通过lint。你会从STDOUT里阅读出无法通过的理由。
2 4
3 5 你的工作就是:首先分析他为什么无法通过lint,然后试图尽可能不break业务逻辑的修正lint的脚本指出的仓库的问题,也就是去修改 C#,然后重复运行 lint.sh 直到全部通过。

anduin's Avatar anduin ha revisionato questo gist 2 weeks ago. Vai alla revisione

1 file changed, 15 insertions

Add_tests.md(file creato)

@@ -0,0 +1,15 @@
1 + 这个仓库是一个 .NET 仓库。显然你不难理解它的项目结构,你可以先简单阅读一下它的代码。
2 +
3 + 它已经有完善的入口点、测试项目等。但是,目前社区普遍批评这个仓库的代码覆盖率很低。尤其是很多核心业务并没有被测试覆盖。
4 +
5 + 现在你需要扮演一位高级 .NET 专家,试图提高这个仓库的代码覆盖率。你可以直接去测试项目里增加新的 UT 。
6 +
7 + 注意,你没必要mock很多东西,例如数据库、文件系统等。它们在测试环境完全准备的也非常妥当了。你的测试大可以直接作为集成测试,也就是直接启动Web项目,调用它的API,检查返回效果。
8 +
9 + 被测试的项目中的用例,例如对外部仓库的访问,你也大可以直接让它真的去访问即可。除非是非常昂贵的请求,我们需要Mock。
10 +
11 + Mock的方法就是在TestStartup里,对一个Service进行继承,override掉老方法,然后再把新服务替代老服务注册回去。
12 +
13 + 这样整个测试结构非常清晰。现在,你来试图开始增加几个新的测试类,覆盖一些核心功能吧!
14 +
15 + 如果老的代码实在有函数很难覆盖,考虑增加 [ExcludeFromCodeCoverage] 吧!

anduin's Avatar anduin ha revisionato questo gist 2 weeks ago. Vai alla revisione

1 file changed, 1 insertion, 1 deletion

Fix_lint.md

@@ -2,7 +2,7 @@
2 2
3 3 你的工作就是:首先分析他为什么无法通过lint,然后试图尽可能不break业务逻辑的修正lint的脚本指出的仓库的问题,也就是去修改 C#,然后重复运行 lint.sh 直到全部通过。
4 4
5 - 你不得修改 lint.sh 文件也不必要分析它!因为这个文件是用来lint的。如果它发现了错误,一定是仓库的错误,不是 linter 的!直接执行即可。
5 + 你不得修改 lint.sh 文件也不必要分析它!因为这个文件是用来lint的。如果它发现了错误,一定是仓库的错误,不是 linter 的!直接执行即可得到仓库本身的lint错误信息。
6 6
7 7 当然,最好你也跑一次 dotnet build 和 dotnet test,确保可以编译、UT全过。
8 8
Più nuovi Più vecchi