aufräumen

This commit is contained in:
2026-08-12 17:45:02 +02:00
parent 0823a023c9
commit a1e722ace1
27 changed files with 542 additions and 197 deletions
+91 -4
View File
@@ -22,7 +22,7 @@ public class LiteDbContext : IDisposable
public ILiteCollection<Student> Students => _db.GetCollection<Student>("students");
public ILiteCollection<LearningGroup> Groups => _db.GetCollection<LearningGroup>("groups");
public ILiteCollection<Enrollment> Enrollments => _db.GetCollection<Enrollment>("enrollments");
public ILiteCollection<GroupMembership> Memberships => _db.GetCollection<GroupMembership>("group_memberships");
public ILiteCollection<Exam> Exams => _db.GetCollection<Exam>("exams");
public ILiteCollection<ExamResult> ExamResults => _db.GetCollection<ExamResult>("exam_results");
public ILiteCollection<Grade> Grades => _db.GetCollection<Grade>("grades");
@@ -42,7 +42,11 @@ public class LiteDbContext : IDisposable
private void MigrateExistingData()
{
MigrateMemberships();
RemoveRedundantLegacyFields();
var groups = _db.GetCollection<BsonDocument>("groups");
var subjects = _db.GetCollection<Subject>("subjects");
var missingArchiveState = groups.FindAll()
.Where(g => !g.ContainsKey(nameof(LearningGroup.IsActive)))
.ToList();
@@ -51,6 +55,83 @@ public class LiteDbContext : IDisposable
group[nameof(LearningGroup.IsActive)] = true;
groups.Update(group);
}
// Subject war anfangs nur als Text in der Gruppe gespeichert. Alte
// Werte werden einmalig an die Fachstammdaten angebunden und danach
// entfernt, damit es nur noch eine führende Fachbezeichnung gibt.
foreach (var group in groups.FindAll().ToList())
{
var changed = false;
var legacyName = group.TryGetValue("Subject", out var value) && value.IsString
? value.AsString.Trim()
: "";
var hasSubjectId = group.TryGetValue(nameof(LearningGroup.SubjectId), out var id)
&& id.IsGuid && subjects.FindById(id.AsGuid) is not null;
if (!hasSubjectId && legacyName.Length > 0)
{
var subject = subjects.FindAll().FirstOrDefault(s =>
string.Equals(s.Name.Trim(), legacyName, StringComparison.OrdinalIgnoreCase));
if (subject is null)
{
subject = new Subject { Name = legacyName };
subjects.Insert(subject);
}
group[nameof(LearningGroup.SubjectId)] = subject.Id;
changed = true;
}
else if (!hasSubjectId && group.ContainsKey(nameof(LearningGroup.SubjectId)))
{
group[nameof(LearningGroup.SubjectId)] = BsonValue.Null;
changed = true;
}
changed |= group.Remove("Subject");
if (changed) groups.Update(group);
}
}
private void MigrateMemberships()
{
const string oldName = "enrollments";
const string newName = "group_memberships";
var names = _db.GetCollectionNames().ToHashSet(StringComparer.OrdinalIgnoreCase);
if (names.Contains(oldName) && !names.Contains(newName))
_db.RenameCollection(oldName, newName);
var memberships = _db.GetCollection<BsonDocument>(newName);
foreach (var membership in memberships.FindAll().ToList())
{
var changed = false;
if (!membership.ContainsKey(nameof(GroupMembership.AddedOn)) &&
membership.TryGetValue("EnrolledAt", out var enrolledAt))
{
membership[nameof(GroupMembership.AddedOn)] = enrolledAt;
changed = true;
}
changed |= membership.Remove("EnrolledAt");
changed |= membership.Remove("SchoolYear");
if (changed) memberships.Update(membership);
}
}
private void RemoveRedundantLegacyFields()
{
RemoveFields("exams", "Subject");
RemoveFields("units", "Subject", "SchoolYear");
RemoveFields("grades", "SchoolYear");
RemoveFields("participation", "GroupId", "Date");
}
private void RemoveFields(string collectionName, params string[] fieldNames)
{
var collection = _db.GetCollection<BsonDocument>(collectionName);
foreach (var document in collection.FindAll().ToList())
{
var changed = false;
foreach (var fieldName in fieldNames) changed |= document.Remove(fieldName);
if (changed) collection.Update(document);
}
}
private void EnsureIndexes()
@@ -59,13 +140,16 @@ public class LiteDbContext : IDisposable
Students.EnsureIndex(x => x.IsActive);
Groups.EnsureIndex(x => x.SchoolYear);
Groups.EnsureIndex(x => x.IsActive);
Enrollments.EnsureIndex(x => x.StudentId);
Enrollments.EnsureIndex(x => x.GroupId);
Enrollments.EnsureIndex(x => x.SchoolYear);
Memberships.EnsureIndex(x => x.StudentId);
Memberships.EnsureIndex(x => x.GroupId);
Memberships.EnsureIndex("ux_student_group",
BsonExpression.Create("STRING($.StudentId) + ':' + STRING($.GroupId)"), unique: true);
Exams.EnsureIndex(x => x.GroupId);
Exams.EnsureIndex(x => x.Status);
ExamResults.EnsureIndex(x => x.ExamId);
ExamResults.EnsureIndex(x => x.StudentId);
ExamResults.EnsureIndex("ux_exam_student",
BsonExpression.Create("STRING($.ExamId) + ':' + STRING($.StudentId)"), unique: true);
Grades.EnsureIndex(x => x.StudentId);
Grades.EnsureIndex(x => x.GroupId);
GradingKeyTemplates.EnsureIndex(x => x.GradingSystem);
@@ -80,8 +164,11 @@ public class LiteDbContext : IDisposable
ParticipationSessions.EnsureIndex(x => x.Date);
ParticipationEntries.EnsureIndex(x => x.SessionId);
ParticipationEntries.EnsureIndex(x => x.StudentId);
ParticipationEntries.EnsureIndex("ux_session_student",
BsonExpression.Create("STRING($.SessionId) + ':' + STRING($.StudentId)"), unique: true);
ParticipationAspects.EnsureIndex(x => x.GroupId);
Subjects.EnsureIndex(x => x.Name);
Subjects.EnsureIndex("ux_subject_name", BsonExpression.Create("LOWER(TRIM($.Name))"), unique: true);
CompetencyDomains.EnsureIndex(x => x.SubjectId);
CompetencyDomains.EnsureIndex(x => x.GradeLevel);
}
+46 -19
View File
@@ -9,10 +9,10 @@ public class StudentRepository(LiteDbContext db) : IStudentRepository
public List<Student> GetAll(bool includeInactive = false) =>
(includeInactive ? db.Students.FindAll() : db.Students.Find(s => s.IsActive))
.OrderBy(s => s.LastName).ToList();
public List<Student> GetByGroup(Guid groupId, string schoolYear)
public List<Student> GetByGroup(Guid groupId)
{
var ids = db.Enrollments
.Find(e => e.GroupId == groupId && e.SchoolYear == schoolYear)
var ids = db.Memberships
.Find(e => e.GroupId == groupId)
.Select(e => e.StudentId).ToHashSet();
return db.Students.Find(s => ids.Contains(s.Id)).OrderBy(s => s.LastName).ToList();
}
@@ -31,11 +31,17 @@ public class GroupRepository(LiteDbContext db) : IGroupRepository
? db.Groups.Find(g => g.SchoolYear == schoolYear)
: db.Groups.Find(g => g.SchoolYear == schoolYear && g.IsActive))
.OrderBy(g => g.Name).ToList();
public void Save(LearningGroup g) { g.UpdatedAt = DateTime.UtcNow; db.Groups.Upsert(g); }
public void Save(LearningGroup g)
{
if (g.SubjectId is Guid subjectId && db.Subjects.FindById(subjectId) is null)
throw new InvalidOperationException("Das gewählte Fach existiert nicht mehr.");
g.UpdatedAt = DateTime.UtcNow;
db.Groups.Upsert(g);
}
public void Delete(Guid id)
{
foreach (var enrollment in db.Enrollments.Find(e => e.GroupId == id).ToList())
db.Enrollments.Delete(enrollment.Id);
foreach (var membership in db.Memberships.Find(e => e.GroupId == id).ToList())
db.Memberships.Delete(membership.Id);
foreach (var exam in db.Exams.Find(e => e.GroupId == id).ToList())
{
@@ -71,16 +77,22 @@ public class GroupRepository(LiteDbContext db) : IGroupRepository
}
}
public class EnrollmentRepository(LiteDbContext db) : IEnrollmentRepository
public class GroupMembershipRepository(LiteDbContext db) : IGroupMembershipRepository
{
public List<Enrollment> GetByStudent(Guid id) =>
db.Enrollments.Find(e => e.StudentId == id).ToList();
public List<Enrollment> GetByGroup(Guid id) =>
db.Enrollments.Find(e => e.GroupId == id).ToList();
public List<Enrollment> GetByGroupAndYear(Guid groupId, string sy) =>
db.Enrollments.Find(e => e.GroupId == groupId && e.SchoolYear == sy).ToList();
public void Save(Enrollment e) => db.Enrollments.Upsert(e);
public void Delete(Guid id) => db.Enrollments.Delete(id);
public List<GroupMembership> GetByStudent(Guid id) =>
db.Memberships.Find(e => e.StudentId == id).ToList();
public List<GroupMembership> GetByGroup(Guid id) =>
db.Memberships.Find(e => e.GroupId == id).ToList();
public GroupMembership? GetByStudentAndGroup(Guid studentId, Guid groupId) =>
db.Memberships.FindOne(e => e.StudentId == studentId && e.GroupId == groupId);
public void Save(GroupMembership membership)
{
var existing = GetByStudentAndGroup(membership.StudentId, membership.GroupId);
if (existing is not null && existing.Id != membership.Id)
throw new InvalidOperationException("Der Schüler ist dieser Lerngruppe bereits zugeordnet.");
db.Memberships.Upsert(membership);
}
public void Delete(Guid id) => db.Memberships.Delete(id);
}
public class ExamRepository(LiteDbContext db) : IExamRepository
@@ -240,10 +252,25 @@ public class SubjectRepository(LiteDbContext db) : ISubjectRepository
{
public List<Subject> GetAll() => db.Subjects.FindAll().OrderBy(s => s.Name).ToList();
public Subject? GetById(Guid id) => db.Subjects.FindById(id);
public Subject? GetByName(string name) =>
db.Subjects.FindOne(s => s.Name == name);
public void Save(Subject s) { s.UpdatedAt = DateTime.UtcNow; db.Subjects.Upsert(s); }
public void Delete(Guid id) => db.Subjects.Delete(id);
public Subject? GetByName(string name) => db.Subjects.FindAll().FirstOrDefault(s =>
string.Equals(s.Name.Trim(), name.Trim(), StringComparison.OrdinalIgnoreCase));
public void Save(Subject s)
{
s.Name = s.Name.Trim();
s.ShortName = s.ShortName.Trim();
if (s.Name.Length == 0) throw new ArgumentException("Der Fachname darf nicht leer sein.");
var duplicate = GetByName(s.Name);
if (duplicate is not null && duplicate.Id != s.Id)
throw new InvalidOperationException("Ein Fach mit diesem Namen existiert bereits.");
s.UpdatedAt = DateTime.UtcNow;
db.Subjects.Upsert(s);
}
public void Delete(Guid id)
{
if (db.Groups.Exists(g => g.SubjectId == id) || db.CompetencyDomains.Exists(d => d.SubjectId == id))
throw new InvalidOperationException("Das Fach wird noch von einer Lerngruppe oder einem Kompetenzkatalog verwendet.");
db.Subjects.Delete(id);
}
}
public class CompetencyDomainRepository(LiteDbContext db) : ICompetencyDomainRepository