Files
LehrerApp/LehrerApp.Data/LiteDbContext.cs
T
2026-08-12 17:45:02 +02:00

178 lines
8.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using LiteDB;
using LehrerApp.Core.Models;
namespace LehrerApp.Data;
/// <summary>
/// Zentrale LiteDB-Verbindung. Singleton eine Datei = ein Nutzer.
/// </summary>
public class LiteDbContext : IDisposable
{
private readonly LiteDatabase _db;
public LiteDbContext(string databasePath)
{
_db = new LiteDatabase(new ConnectionString(databasePath)
{
Connection = ConnectionType.Shared,
});
MigrateExistingData();
EnsureIndexes();
}
public ILiteCollection<Student> Students => _db.GetCollection<Student>("students");
public ILiteCollection<LearningGroup> Groups => _db.GetCollection<LearningGroup>("groups");
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");
public ILiteCollection<GradingKeyTemplate> GradingKeyTemplates => _db.GetCollection<GradingKeyTemplate>("grading_key_templates");
public ILiteCollection<Unit> Units => _db.GetCollection<Unit>("units");
public ILiteCollection<Lesson> Lessons => _db.GetCollection<Lesson>("lessons");
public ILiteCollection<Documentation> Documentation => _db.GetCollection<Documentation>("documentation");
public ILiteCollection<WorkTask> Tasks => _db.GetCollection<WorkTask>("tasks");
public ILiteCollection<TimeEntry> TimeEntries => _db.GetCollection<TimeEntry>("time_entries");
public ILiteCollection<ParticipationSession> ParticipationSessions => _db.GetCollection<ParticipationSession>("participation_sessions");
public ILiteCollection<ParticipationEntry> ParticipationEntries => _db.GetCollection<ParticipationEntry>("participation");
public ILiteCollection<ParticipationAspect> ParticipationAspects => _db.GetCollection<ParticipationAspect>("participation_aspects");
public ILiteCollection<Subject> Subjects => _db.GetCollection<Subject>("subjects");
public ILiteCollection<CompetencyDomain> CompetencyDomains => _db.GetCollection<CompetencyDomain>("competency_domains");
public void Checkpoint() => _db.Checkpoint();
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();
foreach (var group in missingArchiveState)
{
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()
{
Students.EnsureIndex(x => x.LastName);
Students.EnsureIndex(x => x.IsActive);
Groups.EnsureIndex(x => x.SchoolYear);
Groups.EnsureIndex(x => x.IsActive);
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);
Units.EnsureIndex(x => x.GroupId);
Lessons.EnsureIndex(x => x.UnitId);
Lessons.EnsureIndex(x => x.GroupId);
Lessons.EnsureIndex(x => x.Date);
Documentation.EnsureIndex(x => x.StudentId);
Tasks.EnsureIndex(x => x.Status);
TimeEntries.EnsureIndex(x => x.Date);
ParticipationSessions.EnsureIndex(x => x.GroupId);
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);
}
public void Dispose() => _db.Dispose();
}