source: iknow-api/Data/AppDbContext.cs

Last change on this file was c42181c, checked in by Stefan-Saveski <stefansaveski19@…>, 7 days ago

Refactor payment model to remove redundant user_id, addressing BCNF violation in Phase 5 normalization

  • Property mode set to 100644
File size: 16.7 KB
RevLine 
[2859225]1using Microsoft.EntityFrameworkCore;
[e49c3ed]2using iknow_api.Models;
[2859225]3
[e49c3ed]4namespace iknow_api.Data
[2859225]5{
[ea40556]6 /// <summary>
7 /// The schema is owned by sql/ddl.sql, not by EF migrations. Every entity
8 /// below is mapped explicitly onto the table and column names in that
9 /// script; nothing here may rely on EF naming conventions.
10 /// </summary>
[9a57e89]11 public class AppDbContext : DbContext
12 {
[ea40556]13 /// <summary>
14 /// The schema created by sql/schema_creation.sql. Every table and enum
15 /// type is qualified with it, so nothing depends on search_path.
16 /// </summary>
17 public const string ProjectSchema = "project";
18
[9a57e89]19 public AppDbContext(DbContextOptions<AppDbContext> options)
20 : base(options) { }
[2859225]21
[9a57e89]22 public DbSet<User> User { get; set; }
[cab02ad]23 public DbSet<ContactInfo> ContactInfo { get; set; }
24 public DbSet<HighSchool> HighSchool { get; set; }
[3347c1b]25 public DbSet<RefreshToken> RefreshToken { get; set; }
[a92a2a4]26 public DbSet<Subject> Subjects { get; set; }
27 public DbSet<DependencySubject> DependencySubjects { get; set; }
[c2f79d7]28 public DbSet<Major> Majors { get; set; }
29 public DbSet<PassedSubject> PassedSubjects { get; set; }
[691f152]30 public DbSet<EnrolledSemesters> EnrolledSemesters { get; set; }
[70a9f6d]31 public DbSet<ActiveSemesters> ActiveSemesters { get; set; }
32 public DbSet<SemesterSubject> SemesterSubjects { get; set; }
33 public DbSet<MajorSubjects> MajorSubjects { get; set; }
34 public DbSet<UserDocuments> UserDocuments { get; set; }
35 public DbSet<Documents> Documents { get; set; }
36 public DbSet<Payment> Payments { get; set; }
[aee2b88]37 public DbSet<ProfessorSubjects> ProfessorSubjects { get; set; }
[a92a2a4]38
39 protected override void OnModelCreating(ModelBuilder modelBuilder)
40 {
41 base.OnModelCreating(modelBuilder);
42
[ea40556]43 // The enum types themselves are declared in ddl.sql; this tells EF
44 // they exist so it does not try to treat the columns as text.
45 modelBuilder.HasPostgresEnum<UserRole>(ProjectSchema, "user_role");
46 modelBuilder.HasPostgresEnum<HighSchoolType>(ProjectSchema, "hs_type");
47 modelBuilder.HasPostgresEnum<Quota>(ProjectSchema, "quota_type");
48 modelBuilder.HasPostgresEnum<sType>(ProjectSchema, "semester_type");
49 modelBuilder.HasPostgresEnum<Grade>(ProjectSchema, "grade_type");
[c2f79d7]50
[ea40556]51 // -------------------------
52 // users
53 // -------------------------
54 modelBuilder.Entity<User>(e =>
55 {
56 e.ToTable("users", ProjectSchema);
57 e.HasKey(u => u.Id);
58 e.Property(u => u.Id).HasColumnName("id");
59 e.Property(u => u.Name).HasColumnName("name");
60 e.Property(u => u.EMBG).HasColumnName("embg");
61 e.Property(u => u.Surname).HasColumnName("surname");
62 e.Property(u => u.Index).HasColumnName("index");
63 e.Property(u => u.Bday).HasColumnName("bday").HasColumnType("timestamp without time zone");
64 e.Property(u => u.Email).HasColumnName("email");
65 e.Property(u => u.PasswordHash).HasColumnName("password");
66 e.Property(u => u.Role).HasColumnName("role");
67 e.Property(u => u.Quota).HasColumnName("quota");
68 e.Property(u => u.EnrollmentYear).HasColumnName("enrollment_year");
69 e.Property(u => u.CreatedAt).HasColumnName("created_at").HasColumnType("timestamp without time zone");
70 e.HasIndex(u => u.Email).IsUnique();
71 });
[c2f79d7]72
[a92a2a4]73 // -------------------------
[ea40556]74 // high_school
[a92a2a4]75 // -------------------------
[ea40556]76 modelBuilder.Entity<HighSchool>(e =>
77 {
78 e.ToTable("high_school", ProjectSchema);
79 e.HasKey(h => h.Id);
80 e.Property(h => h.Id).HasColumnName("id");
81 e.Property(h => h.UserId).HasColumnName("user_id");
82 e.Property(h => h.GPA).HasColumnName("gpa");
83 e.Property(h => h.HighSchoolType).HasColumnName("type");
84 e.HasOne(h => h.User)
85 .WithOne(u => u.HighSchool)
86 .HasForeignKey<HighSchool>(h => h.UserId)
87 .OnDelete(DeleteBehavior.Restrict);
88 });
[a92a2a4]89
[ea40556]90 // -------------------------
91 // contact
92 // -------------------------
93 modelBuilder.Entity<ContactInfo>(e =>
94 {
95 e.ToTable("contact", ProjectSchema);
96 e.HasKey(c => c.Id);
97 e.Property(c => c.Id).HasColumnName("id");
98 e.Property(c => c.UserId).HasColumnName("user_id");
99 e.Property(c => c.City).HasColumnName("city");
100 e.Property(c => c.Municipality).HasColumnName("municipality");
101 e.Property(c => c.Address).HasColumnName("address");
102 e.Property(c => c.PhoneNumber).HasColumnName("number");
103 e.Property(c => c.MicrosoftEmail).HasColumnName("microsoft_email");
104 e.HasOne(c => c.User)
105 .WithOne(u => u.ContactInfo)
106 .HasForeignKey<ContactInfo>(c => c.UserId)
107 .OnDelete(DeleteBehavior.Restrict);
108 });
[a92a2a4]109
[ea40556]110 // -------------------------
111 // major
112 // -------------------------
113 modelBuilder.Entity<Major>(e =>
114 {
115 e.ToTable("major", ProjectSchema);
116 e.HasKey(m => m.Id);
117 e.Property(m => m.Id).HasColumnName("id");
118 e.Property(m => m.Name).HasColumnName("name");
119 });
[a92a2a4]120
121 // -------------------------
[ea40556]122 // active_semesters
[a92a2a4]123 // -------------------------
[ea40556]124 modelBuilder.Entity<ActiveSemesters>(e =>
125 {
126 e.ToTable("active_semesters", ProjectSchema);
127 e.HasKey(a => a.Id);
128 e.Property(a => a.Id).HasColumnName("id");
129 e.Property(a => a.Year).HasColumnName("year");
130 e.Property(a => a.Type).HasColumnName("type");
131 });
132
133 // -------------------------
134 // subjects
135 // -------------------------
136 modelBuilder.Entity<Subject>(e =>
137 {
138 e.ToTable("subjects", ProjectSchema);
139 e.HasKey(s => s.Id);
140 e.Property(s => s.Id).HasColumnName("id");
141 e.Property(s => s.Name).HasColumnName("name");
142 e.Property(s => s.Code).HasColumnName("code");
143 e.Property(s => s.AwardedCredits).HasColumnName("awarded_credits");
144 e.Property(s => s.DependencyCredit).HasColumnName("dependency_credit");
145 });
146
147 // -------------------------
148 // enrolled_semesters
149 // -------------------------
150 modelBuilder.Entity<EnrolledSemesters>(e =>
151 {
152 e.ToTable("enrolled_semesters", ProjectSchema);
153 e.HasKey(es => es.Id);
154 e.Property(es => es.Id).HasColumnName("id");
155 e.Property(es => es.UserId).HasColumnName("user_id");
156 e.Property(es => es.QuotaType).HasColumnName("quota");
157 e.Property(es => es.MajorId).HasColumnName("major_id");
158 e.Property(es => es.Note).HasColumnName("note");
159 e.Property(es => es.StudentComment).HasColumnName("student_comment");
160 e.Property(es => es.CratedAt).HasColumnName("created_at").HasColumnType("timestamp without time zone");
161 e.Property(es => es.LastChange).HasColumnName("last_change").HasColumnType("timestamp without time zone");
162 e.Property(es => es.Verified).HasColumnName("completed").HasColumnType("timestamp without time zone");
163 e.Property(es => es.SemesterId).HasColumnName("semester_id");
164
165 e.HasOne(es => es.User)
166 .WithMany(u => u.Enrolments)
167 .HasForeignKey(es => es.UserId)
168 .OnDelete(DeleteBehavior.Restrict);
[a92a2a4]169
[ea40556]170 // Note: this deliberately keys off SemesterId. An earlier version
171 // pointed the foreign key at es.Id, which silently tied every
172 // enrolment to the semester that happened to share its id.
173 e.HasOne(es => es.Semester)
174 .WithMany(a => a.EnrolledSemesters)
175 .HasForeignKey(es => es.SemesterId)
176 .OnDelete(DeleteBehavior.Restrict);
[a92a2a4]177
[ea40556]178 e.HasOne(es => es.Major)
179 .WithMany()
180 .HasForeignKey(es => es.MajorId)
181 .OnDelete(DeleteBehavior.Restrict);
182 });
[a92a2a4]183
184 // -------------------------
[ea40556]185 // semesters_subjects
[a92a2a4]186 // -------------------------
[ea40556]187 modelBuilder.Entity<SemesterSubject>(e =>
188 {
189 e.ToTable("semesters_subjects", ProjectSchema);
190 e.HasKey(ss => ss.Id);
191 e.Property(ss => ss.Id).HasColumnName("id");
192 e.Property(ss => ss.EnrolledSemesterId).HasColumnName("enrolled_semesters_id");
193 e.Property(ss => ss.SubjectId).HasColumnName("subjects_id");
194 e.Property(ss => ss.ProfessorId).HasColumnName("professor_id");
195 e.Property(ss => ss.Signature).HasColumnName("signature");
[a92a2a4]196
[ea40556]197 e.HasOne(ss => ss.Professor)
198 .WithMany(u => u.TeachingSubjects)
199 .HasForeignKey(ss => ss.ProfessorId)
200 .OnDelete(DeleteBehavior.Restrict);
[a92a2a4]201
[ea40556]202 e.HasOne(ss => ss.Subject)
203 .WithMany(s => s.SemesterSubjects)
204 .HasForeignKey(ss => ss.SubjectId)
205 .OnDelete(DeleteBehavior.Restrict);
206
207 e.HasOne(ss => ss.EnrolledSemester)
208 .WithMany(es => es.SemesterSubjects)
209 .HasForeignKey(ss => ss.EnrolledSemesterId)
210 .OnDelete(DeleteBehavior.Restrict);
211 });
212
213 // -------------------------
214 // passed_subjects
215 // -------------------------
216 modelBuilder.Entity<PassedSubject>(e =>
217 {
218 e.ToTable("passed_subjects", ProjectSchema);
219 e.HasKey(ps => ps.Id);
220 e.Property(ps => ps.Id).HasColumnName("id");
221 e.Property(ps => ps.SemesterSubjectId).HasColumnName("enrolled_id");
222 e.Property(ps => ps.Grade).HasColumnName("grade");
223 e.Property(ps => ps.DatePassed).HasColumnName("date_passed").HasColumnType("timestamp without time zone");
224
225 e.HasOne(ps => ps.SemesterSubject)
226 .WithOne(ss => ss.PassedSubject)
227 .HasForeignKey<PassedSubject>(ps => ps.SemesterSubjectId)
228 .OnDelete(DeleteBehavior.Cascade);
229 });
230
231 // -------------------------
232 // documents
233 // -------------------------
234 modelBuilder.Entity<Documents>(e =>
235 {
236 e.ToTable("documents", ProjectSchema);
237 e.HasKey(d => d.Id);
238 e.Property(d => d.Id).HasColumnName("id");
239 e.Property(d => d.Name).HasColumnName("type");
240 e.Property(d => d.Body).HasColumnName("body");
241 e.Property(d => d.Cost).HasColumnName("cost");
242 });
[a92a2a4]243
244 // -------------------------
[ea40556]245 // user_documents
[a92a2a4]246 // -------------------------
[ea40556]247 modelBuilder.Entity<UserDocuments>(e =>
248 {
249 e.ToTable("user_documents", ProjectSchema);
250 e.HasKey(ud => new { ud.UserId, ud.DocumentId });
251 e.Property(ud => ud.UserId).HasColumnName("user_id");
252 e.Property(ud => ud.DocumentId).HasColumnName("document_id");
[a92a2a4]253
[ea40556]254 e.HasOne(ud => ud.User)
255 .WithMany(u => u.Documents)
256 .HasForeignKey(ud => ud.UserId)
257 .OnDelete(DeleteBehavior.Cascade);
[a92a2a4]258
[ea40556]259 e.HasOne(ud => ud.Documents)
260 .WithMany(d => d.User)
261 .HasForeignKey(ud => ud.DocumentId)
262 .OnDelete(DeleteBehavior.Cascade);
263 });
[a92a2a4]264
265 // -------------------------
[ea40556]266 // token
[a92a2a4]267 // -------------------------
[ea40556]268 modelBuilder.Entity<RefreshToken>(e =>
269 {
270 e.ToTable("token", ProjectSchema);
271 e.HasKey(t => t.Id);
272 e.Property(t => t.Id).HasColumnName("id");
273 e.Property(t => t.UserId).HasColumnName("user_id");
274 e.Property(t => t.Token).HasColumnName("token");
275 e.Property(t => t.ExpiresAt).HasColumnName("expires_at").HasColumnType("timestamp without time zone");
276 e.Property(t => t.IsValid).HasColumnName("is_valid");
[a92a2a4]277
[ea40556]278 e.HasOne(t => t.User)
279 .WithMany()
280 .HasForeignKey(t => t.UserId)
281 .OnDelete(DeleteBehavior.Cascade);
282 });
[a92a2a4]283
[ea40556]284 // -------------------------
285 // payment
286 // -------------------------
287 modelBuilder.Entity<Payment>(e =>
288 {
289 e.ToTable("payment", ProjectSchema);
290 e.HasKey(p => p.Id);
291 e.Property(p => p.Id).HasColumnName("id");
292 e.Property(p => p.EnrollmentInfoId).HasColumnName("enrollment_id");
293 e.Property(p => p.Amount).HasColumnName("amount");
[a92a2a4]294
[c42181c]295 // No user_id: the student comes from the enrolment. Removing it
296 // was the BCNF fix from Phase 5 (enrolled_id -> user_id).
[ea40556]297 e.HasOne(p => p.EnrolledSemesters)
298 .WithMany(es => es.Users)
299 .HasForeignKey(p => p.EnrollmentInfoId)
300 .OnDelete(DeleteBehavior.Cascade);
301 });
[c2f79d7]302
303 // -------------------------
[ea40556]304 // major_subjects
[c2f79d7]305 // -------------------------
[ea40556]306 modelBuilder.Entity<MajorSubjects>(e =>
307 {
308 e.ToTable("major_subjects", ProjectSchema);
309 e.HasKey(ms => new { ms.MajorId, ms.SubjectId });
310 e.Property(ms => ms.MajorId).HasColumnName("major_id");
311 e.Property(ms => ms.SubjectId).HasColumnName("subject_id");
312 e.Property(ms => ms.MandatorySemester).HasColumnName("mandatory_semester");
313
314 e.HasOne(ms => ms.Majors)
315 .WithMany(m => m.Subjects)
316 .HasForeignKey(ms => ms.MajorId)
317 .OnDelete(DeleteBehavior.Cascade);
[c2f79d7]318
[ea40556]319 e.HasOne(ms => ms.Subjects)
320 .WithMany(s => s.Majors)
321 .HasForeignKey(ms => ms.SubjectId)
322 .OnDelete(DeleteBehavior.Cascade);
323 });
[70a9f6d]324
325 // -------------------------
[ea40556]326 // dependency_subject
[70a9f6d]327 // -------------------------
[ea40556]328 modelBuilder.Entity<DependencySubject>(e =>
329 {
330 e.ToTable("dependency_subject", ProjectSchema);
331 e.HasKey(ds => new { ds.SubjectId, ds.DependencyId });
332 e.Property(ds => ds.SubjectId).HasColumnName("subject_id");
333 e.Property(ds => ds.DependencyId).HasColumnName("dependency_id");
334
335 e.HasOne(ds => ds.Subject)
336 .WithMany(s => s.Dependencies)
337 .HasForeignKey(ds => ds.SubjectId)
338 .OnDelete(DeleteBehavior.Restrict);
339
340 e.HasOne(ds => ds.Dependency)
341 .WithMany(s => s.Dependents)
342 .HasForeignKey(ds => ds.DependencyId)
343 .OnDelete(DeleteBehavior.Restrict);
344 });
[aee2b88]345
346 // -------------------------
[ea40556]347 // professour_subjects (spelling matches the table in ddl.sql)
[aee2b88]348 // -------------------------
[ea40556]349 modelBuilder.Entity<ProfessorSubjects>(e =>
350 {
351 e.ToTable("professour_subjects", ProjectSchema);
352 e.HasKey(ps => new { ps.ProfessorId, ps.SemesterId, ps.SubjectId });
353 e.Property(ps => ps.ProfessorId).HasColumnName("prof_id");
354 e.Property(ps => ps.SemesterId).HasColumnName("active_semester_id");
355 e.Property(ps => ps.SubjectId).HasColumnName("subject_id");
[aee2b88]356
[ea40556]357 e.HasOne(ps => ps.Professor)
358 .WithMany()
359 .HasForeignKey(ps => ps.ProfessorId)
360 .OnDelete(DeleteBehavior.Cascade);
[aee2b88]361
[ea40556]362 e.HasOne(ps => ps.Semester)
363 .WithMany()
364 .HasForeignKey(ps => ps.SemesterId)
365 .OnDelete(DeleteBehavior.Cascade);
[aee2b88]366
[ea40556]367 e.HasOne(ps => ps.Subject)
368 .WithMany()
369 .HasForeignKey(ps => ps.SubjectId)
370 .OnDelete(DeleteBehavior.Cascade);
371 });
[a92a2a4]372 }
[9a57e89]373 }
[ea40556]374}
Note: See TracBrowser for help on using the repository browser.