struct&&assiQues

This commit is contained in:
SpecialX
2025-06-20 15:37:39 +08:00
parent f37262d72e
commit d20c051c51
68 changed files with 1927 additions and 2869 deletions

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.Contracts
{
public enum Layout : byte
{
horizontal = 0,
vertical = 1,
Auto = 2
}
public enum Publisher : byte
{
Unknown = 0,
,
,
}
public enum Grade : byte
{
Unknown = 0,
= 1,
= 2,
= 3,
= 4,
= 5,
= 6
}
public enum DifficultyLevel : byte
{
easy,
medium,
hard
}
public enum QuestionType : byte
{
Unknown = 0,
Spelling, // 拼写
Pronunciation, // 给带点字选择正确读音
WordFormation, // 组词
FillInTheBlanks, // 选词填空 / 补充词语
SentenceDictation, // 默写句子
SentenceRewriting, // 仿句 / 改写句子
ReadingComprehension, // 阅读理解
Composition // 作文
}
public enum SubjectAreaEnum : byte
{
Unknown = 0,
Mathematics, // 数学
Physics, // 物理
Chemistry, // 化学
Biology, // 生物
History, // 历史
Geography, // 地理
Literature, // 语文/文学
English, // 英语
ComputerScience, // 计算机科学
}
public enum QuestionGroupState : byte
{
Standalone,
Group,
Subquestion
}
}

View File

@@ -24,32 +24,36 @@ namespace Entities.Contracts
public string Description { get; set; }
[Column("subject_area")]
public string SubjectArea { get; set; }
public SubjectAreaEnum SubjectArea { get; set; }
[Required]
[Column("due_date")]
public DateTime DueDate { get; set; }
[Column("total_points")]
public float? TotalPoints { get; set; }
public byte TotalQuestions { get; set; }
[Column("score")]
public float Score { get; set; }
[Column("created_by")]
[ForeignKey("Creator")]
public Guid CreatedBy { get; set; }
public Guid CreatorId { get; set; }
[Column("created_at")]
public DateTime CreatedAt { get; set; }
[Column("updated_at")]
public DateTime UpdatedAt { get; set; }
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
[Column("deleted")]
public bool IsDeleted { get; set; }
public bool IsDeleted { get; set; } = false;
// Navigation Properties
[ForeignKey(nameof(CreatorId))]
public User Creator { get; set; }
public ICollection<AssignmentClass> AssignmentClasses { get; set; }
public ICollection<AssignmentGroup> AssignmentGroups { get; set; }
public AssignmentStruct ExamStruct { get; set; }
public ICollection<AssignmentAttachment> AssignmentAttachments { get; set; }
public ICollection<Submission> Submissions { get; set; }
@@ -58,7 +62,6 @@ namespace Entities.Contracts
Id = Guid.NewGuid();
Submissions = new HashSet<Submission>();
AssignmentGroups = new HashSet<AssignmentGroup>();
AssignmentClasses = new HashSet<AssignmentClass>();
AssignmentAttachments = new HashSet<AssignmentAttachment>();
}

View File

@@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Entities.DTO;
namespace Entities.Contracts
{
@@ -17,41 +18,43 @@ namespace Entities.Contracts
public Guid Id { get; set; }
[Column("question_id")]
public Guid? QuestionId { get; set; } // 设为可空
// 当 IsGroup 为 true 时,此为 QuestionGroup 的外键
[Column("question_group_id")] // 新增一个外键列
public Guid? QuestionGroupId { get; set; } // 设为可空
public Guid QuestionId { get; set; }
[Required]
[Column("group_id")]
[ForeignKey("AssignmentGroup")]
public Guid AssignmentGroupId { get; set; }
public Guid AssignmentStructId { get; set; }
[Required]
[Column("question_number")]
public byte QuestionNumber { get; set; }
public byte Index { get; set; }
[Column("parent_question_group_id")]
public Guid? ParentAssignmentQuestionId { get; set; }
[Column("group_state")]
public QuestionGroupState GroupState { get; set; } = QuestionGroupState.Standalone;
[Column("created_at")]
public DateTime CreatedAt { get; set; }
[Column("score")]
public float? Score { get; set; }
[Required]
[Column("bgroup")]
public bool IsGroup { get; set; }
[Column("deleted")]
public bool IsDeleted { get; set; }
public Question Question { get; set; }
public QuestionGroup QuestionGroup { get; set; }
public AssignmentStruct AssignmentStruct { get; set; }
public ICollection<SubmissionDetail> SubmissionDetails { get; set; }
public AssignmentGroup AssignmentGroup { get; set; }
[ForeignKey(nameof(ParentAssignmentQuestionId))]
public AssignmentQuestion? ParentAssignmentQuestion { get; set; }
public ICollection<AssignmentQuestion> ChildrenAssignmentQuestion { get; set; } = new List<AssignmentQuestion>();
public AssignmentQuestion()
{

View File

@@ -5,11 +5,12 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace Entities.Contracts
{
[Table("assignment_group")]
public class AssignmentGroup
public class AssignmentStruct
{
[Key]
[Column("id")]
@@ -26,35 +27,34 @@ namespace Entities.Contracts
[Column("descript")]
[MaxLength(65535)]
public string Descript { get; set; }
public string Description { get; set; }
[Column("layout")]
public Layout Layout { get; set; }
[Column("total_points")]
public float? TotalPoints { get; set; }
public float? Score { get; set; }
[Column("number")]
public byte Number { get; set; }
[Column("index")]
public byte Index { get; set; }
[Column("parent_group")]
public Guid? ParentGroup { get; set; }
public Guid? ParentStructId { get; set; }
[Column("deleted")]
public bool IsDeleted { get; set; }
[Column("valid_question_group")]
public bool ValidQuestionGroup { get; set; }
public bool IsDeleted { get; set; } = false;
// Navigation Properties
public Assignment? Assignment { get; set; }
public AssignmentGroup? ParentAssignmentGroup { get; set;}
public ICollection<AssignmentGroup> ChildAssignmentGroups { get; set; }
public AssignmentStruct? ParentStruct { get; set;}
public ICollection<AssignmentStruct> ChildrenGroups { get; set; }
public ICollection<AssignmentQuestion> AssignmentQuestions { get; set; }
public AssignmentGroup()
public AssignmentStruct()
{
Id = Guid.NewGuid();
ChildAssignmentGroups = new HashSet<AssignmentGroup>();
ChildrenGroups = new HashSet<AssignmentStruct>();
AssignmentQuestions = new HashSet<AssignmentQuestion>();
}
}

View File

@@ -22,6 +22,6 @@ namespace Entities.Contracts
public User Teacher { get; set; }
[Column("subject_taught")]
public string SubjectTaught { get; set; }
public SubjectAreaEnum SubjectTaught { get; set; }
}
}

View File

@@ -16,36 +16,43 @@ namespace Entities.Contracts
public Guid Id { get; set; }
[Required]
[Column("question_text")]
[Column("title")]
[MaxLength(65535)]
public string QuestionText { get; set; }
public string Title { get; set; }
[Column("answer")]
[MaxLength(65535)]
public string? Answer { get; set; }
[Column("description")]
public Guid? DescriptionId { get; set; }
[Required]
[Column("question_type")]
[Column("type")]
[MaxLength(20)]
public QuestionType QuestionType { get; set; }
[Column("correct_answer")]
[MaxLength(65535)]
public string CorrectAnswer { get; set; }
[Column("question_group_id")]
public Guid? QuestionGroupId { get; set; }
public QuestionType Type { get; set; } = QuestionType.Unknown;
[Column("difficulty_level")]
[MaxLength(10)]
public DifficultyLevel DifficultyLevel { get; set; }
public DifficultyLevel DifficultyLevel { get; set; } = DifficultyLevel.easy;
[Column("subject_area")]
public SubjectAreaEnum SubjectArea { get; set; }
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
[Column("options")]
public string? Options { get; set; }
[Column("key_point")]
public Guid? KeyPointId { get; set; }
[Column("lesson")]
public Guid? LessonId { get; set; }
[Required]
[Column("created_by")]
[ForeignKey("Creator")]
public Guid CreatedBy { get; set; }
public Guid CreatorId { get; set; }
[Column("created_at")]
public DateTime CreatedAt { get; set; }
@@ -56,54 +63,30 @@ namespace Entities.Contracts
[Column("deleted")]
public bool IsDeleted { get; set; }
[Column("valid_question")]
public bool ValidQuestion { get; set; }
// Navigation Properties
[ForeignKey(nameof(CreatorId))]
public User Creator { get; set; }
public QuestionGroup QuestionGroup { get; set; }
public ICollection<AssignmentQuestion> AssignmentQuestions { get; set; }
[ForeignKey(nameof(DescriptionId))]
public QuestionContext Description { get; set; }
public Question? ParentQuestion { get; set; }
public ICollection<Question>? ChildrenQuestion { get; set; }
[ForeignKey(nameof(KeyPointId))]
public KeyPoint? KeyPoint { get; set; }
[ForeignKey(nameof(LessonId))]
public Lesson? Lesson { get; set; }
public ICollection<AssignmentQuestion>? AssignmentQuestions { get; set; }
public Question()
{
Id = Guid.NewGuid();
AssignmentQuestions = new HashSet<AssignmentQuestion>();
ChildrenQuestion = new HashSet<Question>();
}
}
public enum DifficultyLevel
{
easy,
medium,
hard
}
public enum QuestionType
{
Unknown, // 可以有一个未知类型或作为默认
Spelling, // 拼写
Pronunciation, // 给带点字选择正确读音
WordFormation, // 组词
FillInTheBlanks, // 选词填空 / 补充词语
SentenceDictation, // 默写句子
SentenceRewriting, // 仿句 / 改写句子
ReadingComprehension, // 阅读理解
Composition // 作文
// ... 添加您其他题目类型
}
public enum SubjectAreaEnum // 建议命名为 SubjectAreaEnum 以避免与属性名冲突
{
Unknown, // 未知或默认
Mathematics, // 数学
Physics, // 物理
Chemistry, // 化学
Biology, // 生物
History, // 历史
Geography, // 地理
Literature, // 语文/文学
English, // 英语
ComputerScience, // 计算机科学
// ... 你可以根据需要添加更多科目
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.Contracts
{
public class QuestionContext
{
public Guid Id { get; set; }
public string Description { get; set; } = string.Empty;
[InverseProperty(nameof(Question.Description))]
public ICollection<Question> Questions { get; set; } = new List<Question>();
public QuestionContext()
{
Questions = new HashSet<Question>();
}
}
}

View File

@@ -1,79 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.Contracts
{
[Table("question_groups")]
public class QuestionGroup
{
[Key]
[Column("id")]
public Guid Id { get; set; }
[Column("title")]
[MaxLength(255)]
public string Title { get; set; }
[Required]
[Column("description")]
[MaxLength(65535)]
public string Description { get; set; }
[Column("type")]
[MaxLength(50)]
public string Type { get; set; }
[Column("difficulty_level")]
[MaxLength(10)]
public DifficultyLevel DifficultyLevel { get; set; }
[Column("subject_area")]
public SubjectAreaEnum SubjectArea { get; set; }
[Column("total_questions")]
public int TotalQuestions { get; set; } = 0;
[Column("parent_question_group")]
public Guid? ParentQG { get; set; }
[Required]
[Column("created_by")]
[ForeignKey("Creator")]
public Guid CreatedBy { get; set; }
[Column("created_at")]
public DateTime CreatedAt { get; set; }
[Column("updated_at")]
public DateTime UpdatedAt { get; set; }
[Column("deleted")]
public bool IsDeleted { get; set; }
[Column("valid_group")]
public bool ValidGroup { get; set; }
public User Creator { get; set; }
public QuestionGroup ParentQuestionGroup { get; set; }
public ICollection<QuestionGroup> ChildQuestionGroups { get; set; }
public ICollection<AssignmentQuestion> AssignmentQuestions { get; set; }
public ICollection<Question> Questions { get; set; }
public QuestionGroup()
{
Id = Guid.NewGuid();
Questions = new HashSet<Question>();
CreatedAt = DateTime.UtcNow;
UpdatedAt = DateTime.UtcNow;
IsDeleted = false;
ValidGroup = true;
}
}
}

View File

@@ -41,7 +41,7 @@ namespace Entities.Contracts
[Column("graded_by")]
[ForeignKey("Grader")]
public Guid? GradedBy { get; set; }
public Guid? GraderId { get; set; }
[Column("graded_at")]
public DateTime? GradedAt { get; set; }
@@ -74,6 +74,5 @@ namespace Entities.Contracts
Resubmission, // 待重新提交 (如果允许)
Late, // 迟交
Draft, // 草稿
// ... 添加你需要的其他状态
}
}

View File

@@ -23,7 +23,6 @@ namespace Entities.Contracts
[Required]
[Column("student_id")]
[ForeignKey("User")]
public Guid StudentId { get; set; }
[Required]
@@ -52,8 +51,11 @@ namespace Entities.Contracts
[Column("deleted")]
public bool IsDeleted { get; set; }
[ForeignKey(nameof(StudentId))]
public User Student { get; set; }
public Submission Submission { get; set; }
public User User { get; set; }
public AssignmentQuestion AssignmentQuestion { get; set; }
public SubmissionDetail()

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.Contracts
{
[Table("key_point")]
public class KeyPoint
{
[Key]
public Guid Id { get; set; }
[StringLength(255)]
public string Key { get; set; } = string.Empty;
[Required]
public Guid LessonID { get; set; }
[ForeignKey(nameof(LessonID))]
public Lesson Lesson { get; set; }
public ICollection<Question> Questions { get; set; }
public KeyPoint()
{
Id = Guid.NewGuid();
Questions = new HashSet<Question>();
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.Contracts
{
[Table("lesson")]
public class Lesson
{
[Key]
public Guid Id { get; set; }
[StringLength(255)]
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
[Required]
public Guid TextbookID { get; set; }
[ForeignKey(nameof(TextbookID))]
public Textbook Textbook { get; set; }
[InverseProperty(nameof(KeyPoint.Lesson))]
public ICollection<KeyPoint>? KeyPoints { get; set; }
[InverseProperty(nameof(Question.Lesson))]
public ICollection<Question>? Questions { get; set; }
[InverseProperty(nameof(LessonQuestion.Lesson))]
public ICollection<LessonQuestion>? LessonQuestions { get; set; }
public Lesson()
{
Id = Guid.NewGuid();
KeyPoints = new HashSet<KeyPoint>();
Questions = new HashSet<Question>();
LessonQuestions = new HashSet<LessonQuestion>();
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.Contracts
{
[Table("lesson_question")]
public class LessonQuestion
{
[Key]
public Guid Id { get; set; }
[MaxLength(65535)]
public string Question { get; set; }
[Required]
public Guid LessonID { get; set; }
[ForeignKey(nameof(LessonID))]
public Lesson Lesson { get; set; }
public LessonQuestion()
{
Id = Guid.NewGuid();
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.Contracts
{
[Table("textbook")]
public class Textbook
{
[Key]
public Guid Id { get; set; }
public Grade Grade { get; set; } = Grade.Unknown;
public string Title { get; set; } = string.Empty;
public Publisher Publisher { get; set; } = Publisher.;
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
[InverseProperty(nameof(Lesson.Textbook))]
public ICollection<Lesson> Lessons { get; set; }
public Textbook()
{
Id = Guid.NewGuid();
Lessons = new HashSet<Lesson>();
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.DTO
{
}

View File

@@ -0,0 +1,25 @@
using Entities.Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.DTO
{
public class AssignmentQuestionDto
{
public float Score { get; set; } = 0;
public byte Index { get; set; } = 0;
public QuestionGroupState GroupState { get; set; } = QuestionGroupState.Standalone;
public AssignmentQuestionDto? ParentAssignmentQuestion { get; set; }
public ICollection<AssignmentQuestionDto> ChildrenAssignmentQuestion { get; set; } = new List<AssignmentQuestionDto>();
public QuestionDto Question { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Entities.Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -7,6 +8,59 @@ using System.Xml.Serialization;
namespace Entities.DTO
{
public class AssignmentStructDto
{
public Guid Id { get; set; } = Guid.Empty;
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public float Score { get; set; } = 0;
public byte Index { get; set; } = 0;
public Layout Layout { get; set; } = Layout.horizontal;
public ICollection<AssignmentQuestionDto> AssignmentQuestions { get; set; } = new List<AssignmentQuestionDto>();
public AssignmentStructDto? ParentStruct { get; set; }
public ICollection<AssignmentStructDto> ChildrenGroups { get; set; } = new List<AssignmentStructDto>();
}
public class AssignmentDto
{
public Guid Id { get; set; } = Guid.Empty;
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public byte TotalQuestions { get; set; }
public float Score { get; set; } = 0;
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime DueDate { get; set; }
public Guid CreatorId { get; set; }
public AssignmentStructDto ExamStruct { get; set; } = new AssignmentStructDto();
}
public class AssignmentClassDto
{
public AssignmentDto Assignment { get; set; }
public Class ClassId { get; set; }
public DateTime AssignedAt { get; set; }
}
public class QuestionContextDto
{
public Guid Id { get; set; } = Guid.Empty;
public string Description { get; set; } = string.Empty;
}
public class ExamDto
{
public Guid? AssignmentId { get; set; }
@@ -14,7 +68,7 @@ namespace Entities.DTO
public string AssignmentTitle { get; set; } = string.Empty;
public string Description { get; set; }
public string SubjectArea { get; set; }
public QuestionGroupDto QuestionGroups { get; set; } = new QuestionGroupDto();
public QuestionGroupDto ExamStruct { get; set; } = new QuestionGroupDto();
}
public class QuestionGroupDto
@@ -29,7 +83,6 @@ namespace Entities.DTO
public List<SubQuestionDto> SubQuestions { get; set; } = new List<SubQuestionDto>();
public List<QuestionGroupDto> SubQuestionGroups { get; set; } = new List<QuestionGroupDto>();
// 标记是否是一个具有上下文的单独问题
public bool ValidQuestionGroup { get; set; } = false;
}
@@ -49,7 +102,6 @@ namespace Entities.DTO
public string? QuestionType { get; set; }
public string? DifficultyLevel { get; set; }
// 标记是否是一个独立的问题
public bool ValidQuestion { get; set; } = false;
}
@@ -64,13 +116,13 @@ namespace Entities.DTO
{
public static void Convert(this ExamDto examDto)
{
var qg = examDto.QuestionGroups;
var qg = examDto.ExamStruct;
}
public static void Convert(this QuestionGroupDto examDto)
{
if(examDto.ValidQuestionGroup)
if (examDto.ValidQuestionGroup)
{
}

View File

@@ -0,0 +1,41 @@
using Entities.Contracts;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entities.DTO
{
public class QuestionDto
{
public Guid Id { get; set; } = Guid.Empty;
public string Title { get; set; } = string.Empty;
public QuestionContextDto? Description { get; set; }
public QuestionType Type { get; set; } = QuestionType.Unknown;
public string? Answer { get; set; } = string.Empty;
public string? Options { get; set; }
public DifficultyLevel DifficultyLevel { get; set; } = DifficultyLevel.easy;
public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
public Guid CreatorId { get; set; }
public Guid? KeyPointId { get; set; }
public Guid? LessonId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; } = DateTime.Now;
}
}

View File

@@ -1,65 +1,171 @@
using Entities.DTO;
using System.Text.Json.Serialization;
using System.Text.Json;
using Entities.Contracts;
using Microsoft.Extensions.Options;
namespace TechHelper.Client.Exam
{
public class ParentStructInfo
{
public string Number { get; set; }
public SubjectAreaEnum SubjectArea { get; set; }
public byte Index { get; set; }
}
public static class ExamPaperExtensions
{
public static ExamDto ConvertToExamDTO(this ExamPaper examPaper)
public static AssignmentDto ConvertToExamDTO(this ExamPaper examPaper)
{
ExamDto dto = new ExamDto();
AssignmentDto dto = new AssignmentDto();
dto.AssignmentTitle = examPaper.AssignmentTitle;
dto.Title = examPaper.AssignmentTitle;
dto.Description = examPaper.Description;
dto.SubjectArea = examPaper.SubjectArea;
dto.QuestionGroups.Title = examPaper.AssignmentTitle;
dto.QuestionGroups.Descript = examPaper.Description;
var SubjectArea = SubjectAreaEnum.Literature;
Enum.TryParse<SubjectAreaEnum>(examPaper.SubjectArea, out SubjectArea);
dto.SubjectArea = SubjectArea;
AssignmentStructDto examStruct = new AssignmentStructDto();
foreach (var qg in examPaper.QuestionGroups)
{
var qgd = new QuestionGroupDto();
ParseMajorQuestionGroup(qg, qgd, false);
dto.QuestionGroups.SubQuestionGroups.Add(qgd);
examStruct.ChildrenGroups.Add(ParseMajorQuestionGroup(qg));
examStruct.ChildrenGroups.Last().Index = (byte)(examStruct.ChildrenGroups.Count());
}
dto.ExamStruct = examStruct;
foreach (var question in examPaper.TopLevelQuestions)
return dto;
}
private static AssignmentStructDto ParseMajorQuestionGroup(MajorQuestionGroup sqg)
{
if (question.SubQuestions != null && question.SubQuestions.Any())
var examStruct = new AssignmentStructDto();
if (sqg.SubQuestionGroups != null)
{
var qgDto = new QuestionGroupDto
examStruct.Title = sqg.Title;
examStruct.Score = sqg.Score;
examStruct.ChildrenGroups = new List<AssignmentStructDto>();
sqg.SubQuestionGroups?.ForEach(ssqg =>
{
Title = question.Stem,
Score = (int)question.Score,
Descript = "",
};
qgDto.ValidQuestionGroup = !string.IsNullOrEmpty(qgDto.Descript);
ParseQuestionWithSubQuestions(question, qgDto, qgDto.ValidQuestionGroup);
dto.QuestionGroups.SubQuestionGroups.Add(qgDto);
if (string.IsNullOrEmpty(ssqg.Descript))
{
examStruct.ChildrenGroups.Add(ParseMajorQuestionGroup(ssqg));
examStruct.ChildrenGroups.Last().Index = (byte)(examStruct.ChildrenGroups.Count());
}
else
{
var qgDto = new QuestionGroupDto
examStruct.AssignmentQuestions.Add(ParseGroupToAssignmentQuestion(ssqg, false));
examStruct.AssignmentQuestions.Last().Index = (byte)(examStruct.AssignmentQuestions.Count());
}
});
}
if (sqg.SubQuestions != null)
{
Title = question.Stem,
Score = (int)question.Score,
Descript = "",
};
qgDto.ValidQuestionGroup = !string.IsNullOrEmpty(qgDto.Descript);
sqg.SubQuestions?.ForEach(sq =>
{
if(sq.SubQuestions.Any())
{
var subQuestionDto = new SubQuestionDto();
ParseSingleQuestion(question, subQuestionDto, !qgDto.ValidQuestionGroup);
qgDto.SubQuestions.Add(subQuestionDto);
dto.QuestionGroups.SubQuestionGroups.Add(qgDto);
}
examStruct.AssignmentQuestions.Add(ParseAssignmentQuestion(sq));
examStruct.AssignmentQuestions.Last().Index = (byte)(examStruct.AssignmentQuestions.Count());
});
}
return dto;
return examStruct;
}
public static List<string> ParseOptionsFromText(this string optionsText)
{
return optionsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)
.Where(line => !string.IsNullOrWhiteSpace(line)).ToList();
}
private static QuestionDto ParseGroupToQuestion(MajorQuestionGroup qg, bool subQ = true)
{
var dq = new QuestionDto();
dq.Title = qg.Title + Environment.NewLine + qg.Descript;
if (subQ) dq.GroupState = QuestionGroupState.Subquestion;
else dq.GroupState = QuestionGroupState.Group;
qg.SubQuestions?.ForEach(ssq =>
{
dq.ChildrenQuestion.Add(ParseQuestion(ssq));
});
qg.SubQuestionGroups?.ForEach(sqg =>
{
dq.ChildrenQuestion.Add(ParseGroupToQuestion(sqg));
});
return dq;
}
private static AssignmentQuestionDto ParseGroupToAssignmentQuestion(MajorQuestionGroup qg, bool subQ = true)
{
var aq = new AssignmentQuestionDto();
aq.Score = qg.Score;
qg.SubQuestions?.ForEach(ssq =>
{
aq.Question.ChildrenQuestion.Add(ParseQuestion(ssq));
aq.Question.ChildrenQuestion.Last().Index = (byte)aq.Question.ChildrenQuestion.Count;
});
qg.SubQuestionGroups?.ForEach(sqg =>
{
aq.Question.ChildrenQuestion.Add(ParseGroupToQuestion(sqg));
aq.Question.ChildrenQuestion.Last().Index = (byte)aq.Question.ChildrenQuestion.Count;
});
return aq;
}
private static AssignmentQuestionDto ParseAssignmentQuestion(PaperQuestion sq)
{
var aq = new AssignmentQuestionDto();
aq.Score = sq.Score;
aq.Question = ParseQuestion(sq);
sq.SubQuestions?.ForEach(ssq =>
{
aq.Question.ChildrenQuestion.Add(ParseQuestion(ssq));
aq.Question.ChildrenQuestion.Last().Index = (byte)aq.Question.ChildrenQuestion.Count;
});
return aq;
}
private static QuestionDto ParseQuestion(PaperQuestion sq)
{
var dq = new QuestionDto();
dq.Title = sq.Stem;
dq.Options = string.Join(Environment.NewLine, sq.Options.Select(opt => $"{opt.Label} {opt.Text}"));
dq.Score = sq.Score;
sq.SubQuestions?.ForEach(ssq =>
{
dq.ChildrenQuestion.Add(ParseQuestion(ssq));
dq.ChildrenQuestion.Last().Index = (byte)dq.ChildrenQuestion.Count;
});
return dq;
}
private static void ParseMajorQuestionGroup(MajorQuestionGroup qg, QuestionGroupDto qgd, bool isParentGroupValidChain)
@@ -86,12 +192,10 @@ namespace TechHelper.Client.Exam
});
}
// 处理 MajorQuestionGroup 下的 SubQuestions
if (qg.SubQuestions != null)
{
qg.SubQuestions.ForEach(sq =>
{
// 如果 MajorQuestionGroup 下的 Question 包含子问题,则转为 QuestionGroupDto
if (sq.SubQuestions != null && sq.SubQuestions.Any())
{
var subQgd = new QuestionGroupDto
@@ -101,7 +205,6 @@ namespace TechHelper.Client.Exam
Score = (int)sq.Score,
Descript = "" // 默认为空
};
// 判断当前组是否有效:如果有描述,并且其父级链中没有任何一个组是有效组,则当前组有效
subQgd.ValidQuestionGroup = !string.IsNullOrEmpty(subQgd.Descript) && !nextIsParentGroupValidChain;
ParseQuestionWithSubQuestions(sq, subQgd, subQgd.ValidQuestionGroup || nextIsParentGroupValidChain);
@@ -121,7 +224,7 @@ namespace TechHelper.Client.Exam
// 解析包含子问题的 Question将其转换为 QuestionGroupDto
// isParentGroupValidChain 参数表示从顶层到当前组的任一父组是否已经是“有效组”
private static void ParseQuestionWithSubQuestions(Question question, QuestionGroupDto qgd, bool isParentGroupValidChain)
private static void ParseQuestionWithSubQuestions(PaperQuestion question, QuestionGroupDto qgd, bool isParentGroupValidChain)
{
qgd.Title = question.Stem;
qgd.Score = (int)question.Score;
@@ -165,7 +268,7 @@ namespace TechHelper.Client.Exam
}
// 解析单个 Question (没有子问题) 为 SubQuestionDto
private static void ParseSingleQuestion(Question question, SubQuestionDto subQd, bool validQ)
private static void ParseSingleQuestion(PaperQuestion question, SubQuestionDto subQd, bool validQ)
{
subQd.Stem = question.Stem;
subQd.Score = (int)question.Score;
@@ -187,7 +290,7 @@ namespace TechHelper.Client.Exam
public static void SeqIndex(this ExamDto dto)
{
dto.QuestionGroups.SeqQGroupIndex();
dto.ExamStruct.SeqQGroupIndex();
}

View File

@@ -51,7 +51,7 @@ namespace TechHelper.Client.Exam
public string Description { get; set; } = "未识别试卷描述";
public string SubjectArea { get; set; } = "试卷类别";
public List<MajorQuestionGroup> QuestionGroups { get; set; } = new List<MajorQuestionGroup>();
public List<Question> TopLevelQuestions { get; set; } = new List<Question>();
public List<PaperQuestion> TopLevelQuestions { get; set; } = new List<PaperQuestion>();
public List<ParseError> Errors { get; set; } = new List<ParseError>();
}
@@ -61,17 +61,18 @@ namespace TechHelper.Client.Exam
public string Descript { get; set; } = string.Empty;
public float Score { get; set; }
public List<MajorQuestionGroup> SubQuestionGroups { get; set; } = new List<MajorQuestionGroup>();
public List<Question> SubQuestions { get; set; } = new List<Question>();
public List<PaperQuestion> SubQuestions { get; set; } = new List<PaperQuestion>();
public int Priority { get; set; }
public bool bGroup { get; set; } = true;
}
public class Question
public class PaperQuestion
{
public string Number { get; set; } = string.Empty;
public string Stem { get; set; } = string.Empty;
public float Score { get; set; }
public List<Option> Options { get; set; } = new List<Option>();
public List<Question> SubQuestions { get; set; } = new List<Question>();
public List<PaperQuestion> SubQuestions { get; set; } = new List<PaperQuestion>();
public string SampleAnswer { get; set; } = string.Empty;
public string QuestionType { get; set; } = string.Empty;
public int Priority { get; set; }
@@ -120,17 +121,17 @@ namespace TechHelper.Client.Exam
public ExamParserConfig()
{
MajorQuestionGroupPatterns.Add(new RegexPatternConfig(@"^([一二三四五六七八九十]+)[、\.]\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 1));
MajorQuestionGroupPatterns.Add(new RegexPatternConfig(@"^\(([一二三四五六七八九十]{1,2}|十[一二三四五六七八九])\)\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 2));
QuestionPatterns.Add(new RegexPatternConfig(@"^\(([一二三四五六七八九十]{1,2}|十[一二三四五六七八九])\)\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 1));
// 模式 1: "1. 这是一个题目 (5分)" 或 "1. 这是一个题目"
QuestionPatterns.Add(new RegexPatternConfig(@"^(\d+)\.\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 1));
QuestionPatterns.Add(new RegexPatternConfig(@"^(\d+)\.\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 2));
// 模式 2: "(1) 这是一个子题目 (3分)" 或 "(1) 这是一个子题目"
QuestionPatterns.Add(new RegexPatternConfig(@"^\((\d+)\)\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 2));
QuestionPatterns.Add(new RegexPatternConfig(@"^\((\d+)\)\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 3));
// 模式 3: "① 这是一个更深层次的子题目 (2分)" 或 "① 这是一个更深层次的子题目"
QuestionPatterns.Add(new RegexPatternConfig(@"^[①②③④⑤⑥⑦⑧⑨⑩]+\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 3));
QuestionPatterns.Add(new RegexPatternConfig(@"^[①②③④⑤⑥⑦⑧⑨⑩]+\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 4));
OptionPatterns.Add(new RegexPatternConfig(@"([A-Z]\.)\s*(.*?)(?=[A-Z]\.|$)", 1)); // 大写字母选项
@@ -251,6 +252,23 @@ namespace TechHelper.Client.Exam
_config = config ?? throw new ArgumentNullException(nameof(config), "ExamParserConfig cannot be null.");
}
///
/// 一.基础
/// 1.听写
/// 2.阅读
/// 二.提升
/// 1.阅读
/// (1).选择
/// (2).填空
/// 三.写
/// (一)课文
///
///
///
/// <summary>
/// Builds the ExamPaper structure from raw text and potential matches.
/// Collects and returns parsing errors encountered during the process.
@@ -260,7 +278,7 @@ namespace TechHelper.Client.Exam
/// <returns>An ExamPaper object containing the parsed structure and a list of errors.</returns>
/// <exception cref="ArgumentException">Thrown if fullExamText is null or empty.</exception>
/// <exception cref="ArgumentNullException">Thrown if allPotentialMatches is null.</exception>
public ExamPaper BuildExamPaper(string fullExamText, List<PotentialMatch> allPotentialMatches)
public ExamPaper BuildExam(string fullExamText, List<PotentialMatch> allPotentialMatches)
{
// 核心输入验证仍然是必要的,因为这些错误是无法恢复的
if (string.IsNullOrWhiteSpace(fullExamText))
@@ -289,8 +307,8 @@ namespace TechHelper.Client.Exam
var majorQGStack = new Stack<MajorQuestionGroup>();
MajorQuestionGroup currentMajorQG = null;
var questionStack = new Stack<Question>();
Question currentQuestion = null;
var questionStack = new Stack<PaperQuestion>();
PaperQuestion currentQuestion = null;
int currentContentStart = 0;
@@ -388,6 +406,7 @@ namespace TechHelper.Client.Exam
Title = pm.RegexMatch.Groups[2].Value.Trim(), // 标题是 Group 2
Score = score,
Priority = pm.PatternConfig.Priority,
bGroup = true
};
if (majorQGStack.Any())
@@ -446,7 +465,7 @@ namespace TechHelper.Client.Exam
}
}
Question newQuestion = new Question
PaperQuestion newQuestion = new PaperQuestion
{
Number = pm.RegexMatch.Groups[1].Value.Trim(),
Stem = pm.RegexMatch.Groups[2].Value.Trim(),
@@ -618,7 +637,7 @@ namespace TechHelper.Client.Exam
/// Processes the content of a Question, mainly for parsing Options and identifying unstructured text.
/// Logs errors to the provided error list instead of throwing.
/// </summary>
private void ProcessQuestionContent(Question question, string contentText, List<PotentialMatch> potentialMatchesInScope, List<ParseError> errors)
private void ProcessQuestionContent(PaperQuestion question, string contentText, List<PotentialMatch> potentialMatchesInScope, List<ParseError> errors)
{
// 参数验证,这些是内部方法的契约,如果违反则直接抛出,因为这意味着调用者有错
if (question == null) throw new ArgumentNullException(nameof(question), "Question cannot be null in ProcessQuestionContent.");
@@ -674,8 +693,10 @@ namespace TechHelper.Client.Exam
question.Options.Add(newOption);
lastOptionEndIndex = pm.EndIndex;
}
// TODO: If there are SubQuestion types, they can be processed similarly here.
// 你可以在此处添加对子问题的处理逻辑,同样需要小心处理其内容和嵌套。
else
{
question.Stem += contentText;
}
}
catch (Exception innerEx)
{
@@ -734,7 +755,7 @@ namespace TechHelper.Client.Exam
// 2. 构建:根据扫描结果和原始文本,线性遍历并构建层级结构
// BuildExamPaper 现在会返回一个包含错误列表的 ExamPaper 对象
// 外部不再需要捕获内部解析异常,只需检查 ExamPaper.Errors 列表
return _builder.BuildExamPaper(examPaperText, allPotentialMatches);
return _builder.BuildExam(examPaperText, allPotentialMatches);
}
}
}

View File

@@ -52,7 +52,7 @@ namespace TechHelper.Client.Exam
Title = dto.AssignmentTitle
};
GetSeqRecursive(dto.QuestionGroups, null, examStruct.Questions);
GetSeqRecursive(dto.ExamStruct, null, examStruct.Questions);
return examStruct;
}

View File

@@ -1,5 +1,6 @@
@using Entities.DTO
@using TechHelper.Client.Exam
@using TechHelper.Client.Services
@page "/exam/check/{ExamID}"

View File

@@ -1,4 +1,5 @@
@page "/exam/create"
@using TechHelper.Client.Services
@using Blazored.TextEditor
@using Entities.DTO
@using TechHelper.Client.Exam
@@ -82,7 +83,7 @@
}
private BlazoredTextEditor _textEditor = new BlazoredTextEditor();
private ExamPaper _parsedExam = new ExamPaper();
private ExamDto ExamContent = new ExamDto();
private AssignmentDto ExamContent = new AssignmentDto();
private ExamParserConfig _examParserConfig { get; set; } = new ExamParserConfig();
@@ -101,7 +102,7 @@
Snackbar.Add("试卷解析成功。", Severity.Success);
Snackbar.Add($"{_parsedExam.Errors}。", Severity.Success);
ExamContent = _parsedExam.ConvertToExamDTO();
ExamContent.SeqIndex();
// ExamContent.SeqIndex();
}
catch (Exception ex)
{
@@ -125,7 +126,6 @@
public async Task Publish()
{
ExamContent.CreaterEmail = authenticationStateTask.Result.User.Identity.Name;
var apiRespon = await examService.SaveParsedExam(ExamContent);
Snackbar.Add(apiRespon.Message);
}

View File

@@ -1,5 +1,7 @@
@page "/exam/edit/{ExamId}"
@using Entities.DTO
@using TechHelper.Client.Services
@using Entities.DTO
@using TechHelper.Client.Exam
<ExamView ParsedExam="@ExamDto"/>
@@ -17,7 +19,7 @@
[Inject]
private ISnackbar Snackbar { get; set; }
private ExamDto ExamDto { get; set; }
private AssignmentDto ExamDto { get; set; }
protected override async Task OnInitializedAsync()
{
@@ -27,8 +29,8 @@
Console.WriteLine($"ExamId 字符串成功解析为 Guid: {parsedExamId}");
try
{
var result = await ExamService.GetExam(parsedExamId);
if (result.Status) ExamDto = result.Result as ExamDto ?? new ExamDto();
// var result = await ExamService.GetExam(parsedExamId);
// if (result.Status) ExamDto = result.Result as ExamDto ?? new ExamDto();
}
catch (Exception ex)
{

View File

@@ -4,41 +4,29 @@
<MudPaper Elevation=@Elevation Class=@Class>
@foreach (var majorQG in MajorQGList)
{
<MudStack Row="true">
<MudText Typo="Typo.h6">@majorQG.Title</MudText>
@if (majorQG.Score > 0)
<MudText Typo="Typo.h6">@ExamStruct.Title</MudText>
@if (ExamStruct.Score > 0)
{
<MudText Typo="Typo.body2"><b>总分:</b> @majorQG.Score 分</MudText>
<MudText Typo="Typo.body2"><b>总分:</b> @ExamStruct.Score 分</MudText>
}
</MudStack>
@if (!string.IsNullOrWhiteSpace(majorQG.Descript))
@foreach (var childStruct in ExamStruct.ChildrenGroups)
{
<MudText Typo="Typo.body2">@((MarkupString)majorQG.Descript.Replace("\n", "<br />"))</MudText>
<ExamGroupView ExamStruct="childStruct"/>
}
@if (majorQG.SubQuestions.Any())
@foreach (var question in ExamStruct.AssignmentQuestions)
{
@foreach (var question in majorQG.SubQuestions)
{
<QuestionCard Question="question" Elevation=@Elevation Class="my-2 pa-1" />
}
}
@if (majorQG.SubQuestionGroups.Any())
{
<ExamGroupView MajorQGList="majorQG.SubQuestionGroups" Elevation="1" />
}
<QuestionCard Question="question.Question" Elevation=@Elevation Class="my-2 pa-1" />
}
</MudPaper>
@code {
[Parameter]
public List<QuestionGroupDto> MajorQGList { get; set; } = new List<QuestionGroupDto>();
public AssignmentStructDto ExamStruct { get; set; } = new AssignmentStructDto();
[Parameter]
public string Class { get; set; } = "my-2 pa-1";

View File

@@ -4,6 +4,8 @@
@page "/exam/manage"
@using Entities.DTO
@using TechHelper.Client.Services
@attribute [Authorize]

View File

@@ -5,10 +5,10 @@
{
<MudPaper Height="@Height" Class="@Class" Style="@Style" Width="@Width">
<MudText Class="d-flex justify-content-center" Typo="Typo.h6"> @ParsedExam.AssignmentTitle </MudText>
<MudText Class="d-flex justify-content-center" Typo="Typo.h6"> @ParsedExam.Title </MudText>
<MudText Typo="Typo.body1"> @ParsedExam.Description </MudText>
<ExamGroupView MajorQGList="@ParsedExam.QuestionGroups.SubQuestionGroups" Elevation="1" Class="ma-0 pa-2" />
<ExamGroupView ExamStruct="@ParsedExam.ExamStruct" Elevation="1" Class="ma-0 pa-2" />
</MudPaper>
}
@@ -24,7 +24,7 @@ else
@code {
[Parameter]
public ExamDto ParsedExam { get; set; } = new ExamDto();
public AssignmentDto ParsedExam { get; set; } = new AssignmentDto();
[Parameter]
public string Height { get; set; } = "100%";
[Parameter]

View File

@@ -4,29 +4,35 @@
<MudPaper Class=@Class Elevation=@Elevation Outlined="false">
<MudText Typo="Typo.subtitle1">
<b>@Question.Index</b> @((MarkupString)Question.Stem.Replace("\n", "<br />"))
<b>@Question.Index</b> @((MarkupString)Question.Title.Replace("\n", "<br />"))
@if (Question.Score > 0)
{
<MudText Typo="Typo.body2" Class="d-inline ml-2">(@Question.Score 分)</MudText>
}
</MudText>
@if (Question.Options.Any())
@if (Question.Options != null)
{
<div class="mt-2">
@foreach (var option in Question.Options)
@foreach (var option in Question.Options.ParseOptionsFromText())
{
var tempOption = option;
<p>@((MarkupString)(tempOption.Value.Replace("\n", "<br />")))</p>
<p>@((MarkupString)(tempOption.Replace("\n", "<br />")))</p>
}
</div>
}
@foreach(var item in Question.ChildrenQuestion)
{
<QuestionCard Question="item"/>
}
</MudPaper>
@code {
[Parameter]
public SubQuestionDto Question { get; set; } = new SubQuestionDto();
public QuestionDto Question { get; set; } = new QuestionDto();
[Parameter]
public string Class { get; set; }

View File

@@ -13,7 +13,6 @@ using TechHelper.Client.Services;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using TechHelper.Client.AI;
using TechHelper.Client.Exam;
using Microsoft.AspNetCore.Components;

View File

@@ -3,9 +3,9 @@ using TechHelper.Client.AI;
using TechHelper.Services;
using Entities.DTO;
using System.Net.Http.Json; // 用于 PostAsJsonAsync
using Newtonsoft.Json; // 用于 JSON 反序列化
using Newtonsoft.Json;
namespace TechHelper.Client.Exam
namespace TechHelper.Client.Services
{
public class ExamService : IExamService
{
@@ -144,10 +144,10 @@ namespace TechHelper.Client.Exam
}
}
public async Task<ApiResponse> SaveParsedExam(ExamDto examDto)
public async Task<ApiResponse> SaveParsedExam(AssignmentDto assiDto)
{
// 直接使用注入的 _client 实例
var response = await _client.PostAsJsonAsync("exam/add", examDto);
var response = await _client.PostAsJsonAsync("exam/add", assiDto);
if (response.IsSuccessStatusCode) // 检查是否是成功的状态码,例如 200 OK, 201 Created 等
{

View File

@@ -1,13 +1,13 @@
using Entities.DTO;
using TechHelper.Services;
namespace TechHelper.Client.Exam
namespace TechHelper.Client.Services
{
public interface IExamService
{
public Task<ApiResponse> FormatExam(string examContent);
public Task<ApiResponse> DividExam(string examContent);
public Task<ApiResponse> SaveParsedExam(ExamDto examDto);
public Task<ApiResponse> SaveParsedExam(AssignmentDto assiDto);
public Task<ApiResponse> ParseSingleQuestionGroup(string examContent);
public ApiResponse ConvertToXML<T>(string xmlContent);

View File

@@ -22,6 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Blazor.LocalStorage.WebAssembly" Version="8.0.0" />
<PackageReference Include="Blazored.TextEditor" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.12" />

View File

@@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Entities.Contracts;
using TechHelper.Server.Context.Configuration;
namespace TechHelper.Context
{
@@ -36,7 +35,6 @@ namespace TechHelper.Context
builder.ApplyConfiguration(new ClassTeacherConfiguration());
builder.ApplyConfiguration(new QuestionConfiguration());
builder.ApplyConfiguration(new SubmissionConfiguration());
builder.ApplyConfiguration(new QuestionGroupConfiguration());
builder.ApplyConfiguration(new SubmissionDetailConfiguration());
}
}

View File

@@ -36,60 +36,41 @@ namespace TechHelper.Context
CreateMap<SubQuestionDto, Question>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.QuestionText, opt => opt.MapFrom(src => src.Stem))
.ForMember(dest => dest.CorrectAnswer, opt => opt.MapFrom(src => src.SampleAnswer))
.ForMember(dest => dest.QuestionType, opt => opt.MapFrom(src => EnumMappingHelpers.ParseEnumSafe(src.QuestionType, QuestionType.Unknown)))
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Stem))
.ForMember(dest => dest.Answer, opt => opt.MapFrom(src => src.SampleAnswer))
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => EnumMappingHelpers.ParseEnumSafe(src.QuestionType, QuestionType.Unknown)))
.ForMember(dest => dest.DifficultyLevel, opt => opt.MapFrom(src => EnumMappingHelpers.ParseEnumSafe(src.DifficultyLevel, DifficultyLevel.easy)))
.ForMember(dest => dest.SubjectArea, opt => opt.MapFrom(src => EnumMappingHelpers.ParseEnumSafe(src.DifficultyLevel, SubjectAreaEnum.Unknown)))
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
.ForMember(dest => dest.CreatorId, opt => opt.Ignore())
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedAt, opt => opt.Ignore())
.ForMember(dest => dest.IsDeleted, opt => opt.Ignore());
// 2. Question -> SubQuestionDto (查看时)
CreateMap<Question, SubQuestionDto>()
.ForMember(dest => dest.Stem, opt => opt.MapFrom(src => src.QuestionText))
.ForMember(dest => dest.Score, opt => opt.Ignore()) // Question 实体没有 Score 字段,需要从 AssignmentQuestion 获取
.ForMember(dest => dest.SampleAnswer, opt => opt.MapFrom(src => src.CorrectAnswer))
.ForMember(dest => dest.QuestionType, opt => opt.MapFrom(src => src.QuestionType.ToString()))
.ForMember(dest => dest.DifficultyLevel, opt => opt.MapFrom(src => src.DifficultyLevel.ToString()))
.ForMember(dest => dest.Options, opt => opt.Ignore()); // Options 需要单独处理
CreateMap<Assignment, ExamDto>()
.ForMember(dest => dest.AssignmentTitle, opt => opt.MapFrom(src => src.Title))
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description))
.ForMember(dest => dest.AssignmentId, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.QuestionGroups, opt => opt.MapFrom(src =>
src.AssignmentGroups.FirstOrDefault(ag => ag.ParentGroup == null)))
.ForMember(dest => dest.SubjectArea, opt => opt.MapFrom(src => src.SubjectArea.ToString()));
// =============================================================
// ENTITY -> DTO Mappings (用于读取/查询)
// =============================================================
CreateMap<Assignment, AssignmentDto>()
.ForMember(dest => dest.ExamStruct, opt => opt.MapFrom(src => src.ExamStruct));
CreateMap<AssignmentGroup, QuestionGroupDto>()
.ForMember(dest => dest.SubQuestionGroups, opt => opt.MapFrom(src => src.ChildAssignmentGroups))
.ForMember(dest => dest.SubQuestions, opt => opt.MapFrom(src => src.AssignmentQuestions));
CreateMap<AssignmentStruct, AssignmentStructDto>(); // 直接映射,因为成员现在对等了
CreateMap<AssignmentQuestion, SubQuestionDto>()
.ForMember(dest => dest.Stem, opt => opt.MapFrom(src => src.Question.QuestionText))
.ForMember(dest => dest.SampleAnswer, opt => opt.MapFrom(src => src.Question.CorrectAnswer))
.ForMember(dest => dest.QuestionType, opt => opt.MapFrom(src => src.Question.QuestionType.ToString()))
.ForMember(dest => dest.DifficultyLevel, opt => opt.MapFrom(src => src.Question.DifficultyLevel.ToString()));
// 新增!从实体到新的 DTO 的映射
CreateMap<AssignmentQuestion, AssignmentQuestionDto>();
CreateMap<Question, QuestionDto>();
// =================================================================
// DTO -> ENTITY Mappings (用于创建/更新) - 现在变得极其简单!
// =================================================================
CreateMap<AssignmentDto, Assignment>();
CreateMap<AssignmentStructDto, AssignmentStruct>();
CreateMap<QuestionGroupDto, AssignmentGroup>()
.ForMember(dest => dest.ChildAssignmentGroups, opt => opt.MapFrom(src => src.SubQuestionGroups))
.ForMember(dest => dest.AssignmentQuestions, opt => opt.MapFrom(src => src.SubQuestions));
// 新增!从新的 DTO 到实体的映射
CreateMap<AssignmentQuestionDto, AssignmentQuestion>();
CreateMap<SubQuestionDto, AssignmentQuestion>()
.ForMember(dest => dest.Question, opt => opt.MapFrom(src => src)); // 映射到嵌套的 Question 对象
CreateMap<QuestionDto, Question>();
CreateMap<QuestionGroupDto, QuestionGroup>()
.ForMember(dest => dest.ChildQuestionGroups, opt => opt.MapFrom(src => src.SubQuestionGroups))
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Descript));
CreateMap<Assignment, ExamDto>();
}
}

View File

@@ -29,10 +29,10 @@ namespace TechHelper.Context.Configuration
.IsRequired()
.HasColumnName("due_date");
builder.Property(a => a.TotalPoints)
builder.Property(a => a.TotalQuestions)
.HasColumnName("total_points");
builder.Property(a => a.CreatedBy)
builder.Property(a => a.CreatorId)
.HasColumnName("created_by");
builder.Property(a => a.CreatedAt)
@@ -54,7 +54,7 @@ namespace TechHelper.Context.Configuration
// 如果 User 有一个名为 AssignmentsCreated 的导航属性,应写为 .WithMany(u => u.AssignmentsCreated)
builder.HasOne(a => a.Creator)
.WithMany() // User 实体没有指向 Assignment 的导航属性 (或我们不知道)
.HasForeignKey(a => a.CreatedBy)
.HasForeignKey(a => a.CreatorId)
.IsRequired(); // CreatedBy 是必填的,对应 [Required] 在外键属性上
// 关系: Assignment (一) 到 AssignmentClass (多)

View File

@@ -4,9 +4,9 @@ using Microsoft.EntityFrameworkCore;
namespace TechHelper.Context.Configuration
{
public class AssignmentGroupConfiguration : IEntityTypeConfiguration<AssignmentGroup>
public class AssignmentGroupConfiguration : IEntityTypeConfiguration<AssignmentStruct>
{
public void Configure(EntityTypeBuilder<AssignmentGroup> builder)
public void Configure(EntityTypeBuilder<AssignmentStruct> builder)
{
// 1. 设置表名
// 将此实体映射到数据库中名为 "assignment_detail" 的表。
@@ -34,23 +34,23 @@ namespace TechHelper.Context.Configuration
.HasMaxLength(65535); // 对应 MaxLength(65535)
// 配置 Descript 属性对应的数据库列名为 "descript",并设置最大长度。
builder.Property(ag => ag.Descript)
builder.Property(ag => ag.Description)
.HasColumnName("descript")
.HasMaxLength(65535); // 对应 MaxLength(65535)
// 配置 TotalPoints 属性对应的数据库列名为 "total_points"。
// TotalPoints 是 decimal? 类型,默认就是可选的,无需 IsRequired(false)。
builder.Property(ag => ag.TotalPoints)
builder.Property(ag => ag.Score)
.HasColumnName("total_points");
// 配置 Number 属性对应的数据库列名为 "number"。
builder.Property(ag => ag.Number)
builder.Property(ag => ag.Index)
.HasColumnName("number")
.IsRequired(); // byte 默认非空,显式 IsRequired 增加可读性。
// 配置 ParentGroup 属性对应的数据库列名为 "sub_group"。
// ParentGroup 是 Guid? 类型,默认就是可选的,无需 IsRequired(false)。
builder.Property(ag => ag.ParentGroup)
builder.Property(ag => ag.ParentStructId)
.HasColumnName("parent_group")
.IsRequired(false);
@@ -64,16 +64,16 @@ namespace TechHelper.Context.Configuration
// 配置 AssignmentGroup 到 Assignment 的多对一关系。
// 一个 AssignmentGroup 记录属于一个 Assignment。
builder.HasOne(ag => ag.Assignment) // 当前 AssignmentGroup 有一个 Assignment
.WithMany(a => a.AssignmentGroups) // 该 Assignment 可以有多个 AssignmentGroup 记录
.HasForeignKey(ag => ag.AssignmentId) // 通过 AssignmentId 建立外键
.OnDelete(DeleteBehavior.Cascade); // 当关联的 Assignment 被删除时,其所有相关的 AssignmentGroup 记录也级联删除。
.WithOne(a => a.ExamStruct) // 该 Assignment 可以有多个 AssignmentGroup 记录
.HasForeignKey<AssignmentStruct>(ag => ag.AssignmentId); // 通过 AssignmentId 建立外键
// 配置 AssignmentGroup 到 AssignmentGroup 的自引用关系(父子关系)。
// 一个 AssignmentGroup 可以有一个父 AssignmentGroup (SubAssignmentGroup)。
// 假设父 AssignmentGroup 实体中有一个名为 ChildAssignmentGroups 的集合属性来表示它所包含的所有子组。
builder.HasOne(ag => ag.ParentAssignmentGroup) // 当前 AssignmentGroup 有一个父 AssignmentGroup
.WithMany(parentAg => parentAg.ChildAssignmentGroups) // 该父 AssignmentGroup 可以有多个子 AssignmentGroup
.HasForeignKey(ag => ag.ParentGroup) // 通过 SubGroup 建立外键
builder.HasOne(ag => ag.ParentStruct) // 当前 AssignmentGroup 有一个父 AssignmentGroup
.WithMany(parentAg => parentAg.ChildrenGroups) // 该父 AssignmentGroup 可以有多个子 AssignmentGroup
.HasForeignKey(ag => ag.ParentStructId) // 通过 SubGroup 建立外键
.IsRequired(false) // SubGroup 是可空的 (Guid?),所以这个关系是可选的。
.OnDelete(DeleteBehavior.SetNull); // 当父 AssignmentGroup 被删除时,其子 AssignmentGroup 的 SubGroup 外键将被设置为 NULL。
// 如果你希望父组被删除时子组不能脱离父组(即不允许父组被删除),

View File

@@ -25,12 +25,8 @@ namespace TechHelper.Context.Configuration
.HasColumnName("question_id");
builder.Property(aq => aq.QuestionGroupId)
.HasColumnName("question_group_id");
// 配置 QuestionNumber 列
builder.Property(aq => aq.QuestionNumber)
builder.Property(aq => aq.Index)
.HasColumnName("question_number")
.IsRequired(); // uint 类型默认非空
@@ -44,15 +40,10 @@ namespace TechHelper.Context.Configuration
// 配置 AssignmentGroupId 列
// 该列在数据库中名为 "detail_id"
builder.Property(aq => aq.AssignmentGroupId)
builder.Property(aq => aq.AssignmentStructId)
.HasColumnName("group_id")
.IsRequired();
builder.Property(aq => aq.IsGroup)
.HasColumnName("is_group") // 修正为一致的列名
.IsRequired(); // IsGroup 应该是必需的
// 配置 IsDeleted 列
builder.Property(aq => aq.IsDeleted)
.HasColumnName("deleted")
.HasDefaultValue(false); // 适用于软删除策略
@@ -69,12 +60,6 @@ namespace TechHelper.Context.Configuration
.HasForeignKey(aq => aq.QuestionId) // 外键是 AssignmentQuestion.QuestionId
.OnDelete(DeleteBehavior.Cascade); // 当 Question 被删除时,相关的 AssignmentQuestion 也级联删除。
builder.HasOne(aq => aq.QuestionGroup)
.WithMany(qg => qg.AssignmentQuestions)
.HasForeignKey(aq => aq.QuestionGroupId)
.OnDelete(DeleteBehavior.SetNull);
// ---
// 配置 AssignmentQuestion 到 AssignmentGroup 的关系 (多对一)
// 一个 AssignmentQuestion 属于一个 AssignmentGroup。
@@ -82,9 +67,9 @@ namespace TechHelper.Context.Configuration
// 你的 `AssignmentQuestion` 类现在有了 `public AssignmentGroup AssignmentGroup { get; set; }`
// 这是一个非常好的改进,它与 `AssignmentGroupId` 外键完美匹配。
// 假设 `AssignmentGroup` 实体中有一个名为 `AssignmentQuestions` 的 `ICollection<AssignmentQuestion>` 集合属性。
builder.HasOne(aq => aq.AssignmentGroup) // 当前 AssignmentQuestion 有一个 AssignmentGroup
builder.HasOne(aq => aq.AssignmentStruct) // 当前 AssignmentQuestion 有一个 AssignmentGroup
.WithMany(ag => ag.AssignmentQuestions) // 那个 AssignmentGroup 可以有多个 AssignmentQuestion
.HasForeignKey(aq => aq.AssignmentGroupId) // 外键是 AssignmentQuestion.AssignmentGroupId (列名 detail_id)
.HasForeignKey(aq => aq.AssignmentStructId) // 外键是 AssignmentQuestion.AssignmentGroupId (列名 detail_id)
.OnDelete(DeleteBehavior.Cascade); // 当 AssignmentGroup 被删除时,相关的 AssignmentQuestion 也级联删除。
// ---

View File

@@ -0,0 +1,17 @@
using Entities.Contracts;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
namespace TechHelper.Context.Configuration
{
public class KeyPointConfiguration : IEntityTypeConfiguration<KeyPoint>
{
public void Configure(EntityTypeBuilder<KeyPoint> builder)
{
builder.HasOne(kp=>kp.Lesson)
.WithMany(l => l.KeyPoints)
.HasForeignKey(kp => kp.LessonID)
.OnDelete(DeleteBehavior.Cascade);
}
}
}

View File

@@ -14,7 +14,8 @@ namespace TechHelper.Context.Configuration
// 2. 设置主键
builder.HasKey(q => q.Id);
builder.HasIndex(q => q.QuestionText);
builder.HasIndex(q => q.Title)
.HasPrefixLength(20);
// 3. 配置列名、必需性、长度及其他属性
@@ -23,44 +24,37 @@ namespace TechHelper.Context.Configuration
.HasColumnName("id");
// 对于 Guid 类型的主键EF Core 默认由应用程序生成值,无需 ValueGeneratedOnAdd()
builder.Property(q => q.QuestionGroupId)
.HasColumnName("question_group_id")
.IsRequired(false); // 可为空,因为题目不一定属于某个题组
// QuestionText
builder.Property(q => q.QuestionText)
builder.Property(q => q.Title)
.HasColumnName("question_text")
.IsRequired()
.HasMaxLength(65535); // 对应 MaxLength(65535)
// QuestionType (枚举作为字符串存储)
builder.Property(q => q.QuestionType)
builder.Property(q => q.Type)
.HasColumnName("question_type")
.IsRequired()
.HasConversion<string>() // <-- 重要:将枚举存储为字符串
.HasMaxLength(20); // <-- 应用最大长度
.HasMaxLength(20);
// CorrectAnswer
builder.Property(q => q.CorrectAnswer)
builder.Property(q => q.Answer)
.HasColumnName("correct_answer")
.HasMaxLength(65535); // 对应 MaxLength(65535)
.HasMaxLength(65535);
// DifficultyLevel (枚举作为字符串存储)
builder.Property(q => q.DifficultyLevel)
.HasColumnName("difficulty_level")
.HasConversion<string>() // <-- 重要:将枚举存储为字符串
.HasMaxLength(10); // <-- 应用最大长度
// DifficultyLevel 属性没有 [Required],所以默认是可选的
.HasMaxLength(10);
// SubjectArea
builder.Property(q => q.SubjectArea)
.HasColumnName("subject_area")
.HasConversion<string>() // <--- 重要:将枚举存储为字符串
.HasMaxLength(100); // <--- 应用最大长度 (从原 StringLength 继承)
.HasMaxLength(100);
// CreatedBy
builder.Property(q => q.CreatedBy)
builder.Property(q => q.CreatorId)
.HasColumnName("created_by")
.IsRequired();
@@ -91,7 +85,7 @@ namespace TechHelper.Context.Configuration
// 假设 `User` 实体中有一个名为 `CreatedQuestions` 的 `ICollection<Question>` 集合属性。
builder.HasOne(q => q.Creator) // 当前 Question 有一个 Creator
.WithMany(u => u.CreatedQuestions) // 那个 Creator 可以创建多个 Question
.HasForeignKey(q => q.CreatedBy) // 外键是 Question.CreatedBy
.HasForeignKey(q => q.CreatorId) // 外键是 Question.CreatedBy
.OnDelete(DeleteBehavior.Restrict); // 当 User (Creator) 被删除时,如果还有他/她创建的 Question则会阻止删除。
// 由于 CreatedBy 是 [Required]Prevent/Restrict 是一个安全的选择。
@@ -105,11 +99,24 @@ namespace TechHelper.Context.Configuration
.WithOne(aq => aq.Question); // 每一个 AssignmentQuestion 都有一个 Question
// .HasForeignKey(aq => aq.QuestionId); // 外键的配置应在 `AssignmentQuestionConfiguration` 中进行
builder.HasOne(q => q.QuestionGroup) // Question 实体中的 QuestionGroup 导航属性
.WithMany(qg => qg.Questions) // QuestionGroup 实体中的 Questions 集合
.HasForeignKey(q => q.QuestionGroupId) // Question 实体中的 QuestionGroupId 外键
.IsRequired(false) // QuestionGroupId 在 Question 实体中是可空的
.OnDelete(DeleteBehavior.SetNull); // 如果 QuestionGroup 被删除,关联的 Question 的外键设置为 NULL
builder.HasOne(q => q.KeyPoint)
.WithMany(kp => kp.Questions)
.HasForeignKey(q => q.KeyPointId)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne(q => q.Lesson)
.WithMany(kp => kp.Questions)
.IsRequired(false)
.HasForeignKey(q => q.LessonId)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne(q => q.ParentQuestion)
.WithMany(pq => pq.ChildrenQuestion)
.IsRequired(false)
.HasForeignKey(q => q.ParentQuestionId)
.OnDelete(DeleteBehavior.SetNull);
}
}
}

View File

@@ -1,111 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
using Entities.Contracts;
namespace TechHelper.Server.Context.Configuration
{
public class QuestionGroupConfiguration : IEntityTypeConfiguration<QuestionGroup>
{
public void Configure(EntityTypeBuilder<QuestionGroup> builder)
{
// 1. 设置表名
builder.ToTable("question_groups");
// 2. 设置主键
builder.HasKey(qg => qg.Id);
// 3. 配置列属性
// Title 标题
builder.Property(qg => qg.Title)
.HasColumnName("title")
.HasMaxLength(255)
.IsRequired(false); // 允许为空
// Description 描述内容 (Required)
builder.Property(qg => qg.Description)
.HasColumnName("description")
.IsRequired()
.HasColumnType("longtext"); // 对应 MySQL 的 TEXT 或 LONGTEXT
// Type 类型 (例如: "ReadingComprehension", "DiagramAnalysis")
builder.Property(qg => qg.Type)
.HasColumnName("type")
.HasMaxLength(50)
.IsRequired(false); // 允许为空
// DifficultyLevel 难度级别 (枚举映射为字符串)
builder.Property(qg => qg.DifficultyLevel)
.HasColumnName("difficulty_level")
.HasConversion<string>() // 将枚举转换为字符串存储
.HasMaxLength(10);
// SubjectArea 科目领域 (枚举映射为字符串)
builder.Property(qg => qg.SubjectArea)
.HasColumnName("subject_area")
.HasConversion<string>(); // 将枚举转换为字符串存储
// TotalQuestions 包含题目总数
builder.Property(qg => qg.TotalQuestions)
.HasColumnName("total_questions")
.IsRequired();
// ParentQG 父题组 ID (外键,自引用关系)
builder.Property(qg => qg.ParentQG)
.HasColumnName("parent_question_group") // 使用你定义的列名
.IsRequired(false); // 可为空,因为根题组没有父级
// CreatedBy 创建者 ID (外键)
builder.Property(qg => qg.CreatedBy)
.HasColumnName("created_by")
.IsRequired();
// CreatedAt 创建时间
builder.Property(qg => qg.CreatedAt)
.HasColumnName("created_at")
.IsRequired();
// UpdatedAt 更新时间
builder.Property(qg => qg.UpdatedAt)
.HasColumnName("updated_at")
.IsRequired();
// IsDeleted 是否删除 (软删除)
builder.Property(qg => qg.IsDeleted)
.HasColumnName("deleted")
.IsRequired();
// ValidGroup 是否有效
builder.Property(qg => qg.ValidGroup)
.HasColumnName("valid_group")
.IsRequired();
// 4. 配置关系
// 与 User 的关系 (创建者)
builder.HasOne(qg => qg.Creator)
.WithMany()
.HasForeignKey(qg => qg.CreatedBy)
.OnDelete(DeleteBehavior.Restrict); // 阻止删除关联的 User
// 与 Question 的关系 (一对多)
// 一个 QuestionGroup 可以包含多个 Question
builder.HasMany(qg => qg.Questions)
.WithOne(q => q.QuestionGroup) // Question 实体中的 QuestionGroup 导航属性
.HasForeignKey(q => q.QuestionGroupId) // Question 实体中的 QuestionGroupId 外键
.IsRequired(false) // QuestionGroupId 在 Question 实体中是可空的
.OnDelete(DeleteBehavior.SetNull); // 如果 QuestionGroup 被删除,关联的 Question 的外键设置为 NULL
// 与自身的自引用关系 (父子题组)
// 一个 QuestionGroup 可以有多个 ChildQuestionGroups
builder.HasMany(qg => qg.ChildQuestionGroups)
.WithOne(childQG => childQG.ParentQuestionGroup) // 子 QuestionGroup 实体中的 ParentQuestionGroup 导航属性
.HasForeignKey(childQG => childQG.ParentQG) // 子 QuestionGroup 实体中的 ParentQG 外键
.IsRequired(false) // ParentQG 是可空的,因为根题组没有父级
.OnDelete(DeleteBehavior.Restrict); // 或者 SetNull, Cascade。Restrict 更安全,避免意外删除整个分支。
// 如果选择 SetNull删除父组时子组的 ParentQG 会变为 NULL它们就成了新的根组。
// 如果选择 Cascade删除父组会递归删除所有子组。根据业务逻辑选择。
// 这里我选择了 Restrict 作为默认安全选项。
}
}
}

View File

@@ -30,18 +30,13 @@ namespace TechHelper.Context.Configuration
.HasColumnName("student_id")
.IsRequired();
// AttemptNumber
// 注意:如果 AttemptNumber 应该是一个递增的数字Guid 可能不是最合适的类型。
// 但根据你的定义,这里按 Guid 类型配置。
builder.Property(s => s.AttemptNumber)
.HasColumnName("attempt_number")
.IsRequired();
// SubmissionTime
builder.Property(s => s.SubmissionTime)
.HasColumnName("submission_time"); // 没有 [Required] 属性,所以可以是可空的
// OverallGrade
builder.Property(s => s.OverallGrade)
.HasColumnName("overall_grade")
.HasPrecision(5, 2); // 应用精度设置
@@ -51,59 +46,45 @@ namespace TechHelper.Context.Configuration
.HasColumnName("overall_feedback");
// GradedBy (现为 Guid? 类型)
builder.Property(s => s.GradedBy)
.HasColumnName("graded_by"); // 作为可空外键,不需要 IsRequired()
builder.Property(s => s.GraderId)
.HasColumnName("graded_by");
// GradedAt
builder.Property(s => s.GradedAt)
.HasColumnName("graded_at");
// IsDeleted
builder.Property(s => s.IsDeleted)
.HasColumnName("deleted")
.HasDefaultValue(false);
// Status (枚举作为字符串存储)
builder.Property(s => s.Status)
.HasColumnName("status")
.IsRequired()
.HasConversion<string>() // <--- 重要:将枚举存储为字符串
.HasMaxLength(15); // <--- 应用最大长度
.HasMaxLength(15);
// 4. 配置导航属性和外键关系
// ---
// 配置 Submission 到 Assignment 的关系 (多对一)
// 一个 Submission 属于一个 Assignment。
builder.HasOne(s => s.Assignment) // 当前 Submission 有一个 Assignment
.WithMany(a => a.Submissions) // 那个 Assignment 可以有多个 Submission
.HasForeignKey(s => s.AssignmentId) // 外键是 Submission.AssignmentId
.OnDelete(DeleteBehavior.Cascade); // 当 Assignment 被删除时,相关的 Submission 也级联删除。
// ---
// 配置 Submission 到 User (Student) 的关系 (多对一)
// 一个 Submission 由一个 User (Student) 提交。
builder.HasOne(s => s.Student) // 当前 Submission 有一个 Student (User)
.WithMany(u => u.SubmissionsAsStudent) // 那个 User (Student) 可以有多个 Submission
.HasForeignKey(s => s.StudentId) // 外键是 Submission.StudentId
.OnDelete(DeleteBehavior.Restrict); // 当 User (Student) 被删除时,如果还有其提交的 Submission则会阻止删除。
// ---
// 配置 Submission 到 User (Grader) 的关系 (多对一)
// 一个 Submission 可以由一个 User (Grader) 批改 (可选)。
builder.HasOne(s => s.Grader) // 当前 Submission 有一个 Grader (User),可以是空的
.WithMany(u => u.GradedSubmissions) // 那个 User (Grader) 可以批改多个 Submission
.HasForeignKey(s => s.GradedBy) // 外键是 Submission.GradedBy
.HasForeignKey(s => s.GraderId) // 外键是 Submission.GradedBy
.OnDelete(DeleteBehavior.SetNull); // 当 User (Grader) 被删除时,如果 GradedBy 是可空的,则将其设置为 NULL。
// 如果 GradedBy 是不可空的,需要改为 Restrict 或 Cascade。
// ---
// 配置 Submission 到 SubmissionDetail 的关系 (一对多)
// 一个 Submission 可以有多个 SubmissionDetail。
// 这个关系的外键配置通常在 "多" 的一方 (`SubmissionDetail` 实体) 进行。
builder.HasMany(s => s.SubmissionDetails) // 当前 Submission 有多个 SubmissionDetail
.WithOne(sd => sd.Submission); // 每一个 SubmissionDetail 都有一个 Submission
// .HasForeignKey(sd => sd.SubmissionId); // 外键的配置应在 `SubmissionDetailConfiguration` 中进行
}
}
}

View File

@@ -8,94 +8,65 @@ namespace TechHelper.Context.Configuration
{
public void Configure(EntityTypeBuilder<SubmissionDetail> builder)
{
// 1. 设置表名
// 将此实体映射到数据库中名为 "submission_details" 的表。
builder.ToTable("submission_details");
// 2. 设置主键
// 将 Id 属性设置为主键。
builder.HasKey(sd => sd.Id);
// 3. 配置列名、必需性、精度及其他属性
// Id 属性对应的数据库列名为 "id"。
builder.Property(sd => sd.Id)
.HasColumnName("id");
// SubmissionId 属性对应的数据库列名为 "submission_id",并设置为必需字段。
builder.Property(sd => sd.SubmissionId)
.HasColumnName("submission_id")
.IsRequired();
// StudentId 属性对应的数据库列名为 "student_id",并设置为必需字段。
// 此外键指向 User 实体,代表提交该详情的学生。
builder.Property(sd => sd.StudentId)
.HasColumnName("student_id")
.IsRequired();
// AssignmentQuestionId 属性对应的数据库列名为 "assignment_question_id",并设置为必需字段。
builder.Property(sd => sd.AssignmentQuestionId)
.HasColumnName("assignment_question_id")
.IsRequired();
// StudentAnswer 属性对应的数据库列名为 "student_answer"。
builder.Property(sd => sd.StudentAnswer)
.HasColumnName("student_answer"); // string 默认可空
// IsCorrect 属性对应的数据库列名为 "is_correct"。
builder.Property(sd => sd.IsCorrect)
.HasColumnName("is_correct"); // bool? 默认可空
// PointsAwarded 属性对应的数据库列名为 "points_awarded",并设置精度。
builder.Property(sd => sd.PointsAwarded)
.HasColumnName("points_awarded")
.HasPrecision(5, 2); // 应用 [Precision(5, 2)] 设置
// TeacherFeedback 属性对应的数据库列名为 "teacher_feedback"。
builder.Property(sd => sd.TeacherFeedback)
.HasColumnName("teacher_feedback"); // string 默认可空
// CreatedAt 属性对应的数据库列名为 "created_at",设置为必需字段,并在添加时自动生成值。
builder.Property(sd => sd.CreatedAt)
.HasColumnName("created_at")
.IsRequired()
.ValueGeneratedOnAdd(); // 在实体首次保存时自动设置值
// UpdatedAt 属性对应的数据库列名为 "updated_at",设置为必需字段,并在添加或更新时自动生成值,同时作为并发令牌。
builder.Property(sd => sd.UpdatedAt)
.HasColumnName("updated_at")
.IsRequired()
.ValueGeneratedOnAddOrUpdate() // 在实体添加或更新时自动设置值
.IsConcurrencyToken(); // 用作乐观并发控制,防止同时修改同一记录
// IsDeleted 属性对应的数据库列名为 "deleted",并设置默认值为 false。
builder.Property(sd => sd.IsDeleted)
.HasColumnName("deleted")
.HasDefaultValue(false); // 常用作软删除标记
.HasDefaultValue(false);
// 4. 配置导航属性和外键关系
// ---
// 配置 SubmissionDetail 到 Submission 的关系 (多对一)
// 一个 SubmissionDetail 记录属于一个 Submission。
builder.HasOne(sd => sd.Submission) // 当前 SubmissionDetail 有一个 Submission
.WithMany(s => s.SubmissionDetails) // 那个 Submission 可以有多个 SubmissionDetail 记录
.HasForeignKey(sd => sd.SubmissionId) // 外键是 SubmissionDetail.SubmissionId
.OnDelete(DeleteBehavior.Cascade); // 当 Submission 被删除时,相关的 SubmissionDetail 记录也级联删除。
// ---
// 配置 SubmissionDetail 到 User (作为 Student) 的关系 (多对一)
// 一个 SubmissionDetail 记录与一个 User (提交该详情的学生) 相关联。
// 假设 `User` 实体中有一个名为 `SubmissionDetailsAsStudent` 的 `ICollection<SubmissionDetail>` 集合属性。
builder.HasOne(sd => sd.User) // 当前 SubmissionDetail 有一个 User (作为学生)
builder.HasOne(sd => sd.Student) // 当前 SubmissionDetail 有一个 User (作为学生)
.WithMany(u => u.SubmissionDetails)
.HasForeignKey(sd => sd.StudentId) // 外键是 SubmissionDetail.StudentId
.OnDelete(DeleteBehavior.Restrict); // 当 User (学生) 被删除时,如果他/她还有提交详情,则会阻止删除。
// 这是一个更安全的选择,以防止意外数据丢失。
// ---
// 配置 SubmissionDetail 到 AssignmentQuestion 的关系 (多对一)
// 一个 SubmissionDetail 记录对应一个 AssignmentQuestion。
builder.HasOne(sd => sd.AssignmentQuestion) // 当前 SubmissionDetail 有一个 AssignmentQuestion
.WithMany(aq => aq.SubmissionDetails) // 那个 AssignmentQuestion 可以有多个 SubmissionDetail 记录
.HasForeignKey(sd => sd.AssignmentQuestionId) // 外键是 SubmissionDetail.AssignmentQuestionId

View File

@@ -8,14 +8,8 @@ namespace TechHelper.Context.Configuration
{
public void Configure(EntityTypeBuilder<User> builder)
{
// 映射到表名:如果 User 类上没有 [Table("users")],默认是 "AspNetUsers"。
// 显式指定可以确保你的数据库表名和你期望的一致。
builder.ToTable("AspNetUsers");
// IdentityUser 的 Id 属性和其他标准属性(如 UserName, Email 等)
// 大多由 IdentityDbContext 自动处理,通常不需要在这里显式配置主键或默认列。
// 配置自定义属性
builder.Property(u => u.RefreshToken)
.HasColumnName("refresh_token");
@@ -24,11 +18,8 @@ namespace TechHelper.Context.Configuration
builder.Property(u => u.IsDeleted)
.HasColumnName("deleted")
.HasDefaultValue(false); // 软删除标记,默认 false
.HasDefaultValue(false);
// 配置导航属性 (User 作为关系的“一”或“主”方)
// User 作为老师,与 ClassTeacher 的关系 (一对多)
builder.HasMany(u => u.TaughtClassesLink) // 一个 User (老师) 可以教授多个班级
.WithOne(ct => ct.Teacher) // 一个 ClassTeacher 记录对应一个 Teacher
.HasForeignKey(ct => ct.TeacherId) // 外键在 ClassTeacher.TeacherId
@@ -43,20 +34,17 @@ namespace TechHelper.Context.Configuration
// User 作为创建者,与 Question 的关系 (一对多)
builder.HasMany(u => u.CreatedQuestions) // 一个 User 可以创建多个题目
.WithOne(q => q.Creator) // 一个 Question 对应一个 Creator
.HasForeignKey(q => q.CreatedBy) // 外键在 Question.CreatedBy
.HasForeignKey(q => q.CreatorId) // 外键在 Question.CreatedBy
.OnDelete(DeleteBehavior.Restrict); // 限制删除:如果创建者有题目,则不允许删除
// User 作为创建者,与 Assignment 的关系 (一对多)
builder.HasMany(u => u.CreatedAssignments) // 一个 User 可以创建多个作业
.WithOne(a => a.Creator) // 一个 Assignment 对应一个 Creator
.HasForeignKey(a => a.CreatedBy) // 外键在 Assignment.CreatedBy
.HasForeignKey(a => a.CreatorId) // 外键在 Assignment.CreatedBy
.OnDelete(DeleteBehavior.Restrict); // 限制删除:如果创建者有作业,则不允许删除
// User 作为学生,与 SubmissionDetail 的关系 (一对多)
// 尽管 SubmissionDetail 也可以通过 Submission 间接关联到 User
// 但这里提供了直接访问的导航属性,以方便直接查询学生的所有作答详情。
builder.HasMany(u => u.SubmissionDetails) // 一个 User (学生) 可以有多个提交详情记录
.WithOne(sd => sd.User) // 一个 SubmissionDetail 对应一个 User (学生)
.WithOne(sd => sd.Student) // 一个 SubmissionDetail 对应一个 User (学生)
.HasForeignKey(sd => sd.StudentId) // 外键在 SubmissionDetail.StudentId
.OnDelete(DeleteBehavior.Restrict); // 限制删除:如果学生有提交详情,则不允许删除
@@ -69,7 +57,7 @@ namespace TechHelper.Context.Configuration
// User 作为批改者,与 Submission 的关系 (一对多)
builder.HasMany(u => u.GradedSubmissions) // 一个 User (批改者) 可以批改多个提交
.WithOne(s => s.Grader) // 一个 Submission 对应一个 Grader
.HasForeignKey(s => s.GradedBy) // 外键在 Submission.GradedBy
.HasForeignKey(s => s.GraderId) // 外键在 Submission.GradedBy
.OnDelete(DeleteBehavior.SetNull); // 因为 GradedBy 是可空的,所以批改者删除时,设为 NULL
}
}

View File

@@ -28,9 +28,13 @@ namespace TechHelper.Server.Controllers
[HttpPost("add")]
public async Task<IActionResult> AddExam(
[FromBody] ExamDto examDto)
[FromBody] AssignmentDto examDto)
{
var result = await _examService.AddAsync(examDto);
var user = await _userManager.FindByEmailAsync(User.Identity?.Name ?? "");
if(user == null) return BadRequest("无效的用户");
examDto.CreatorId = user.Id;
var result = await _examService.CreateExamAsync(examDto);
if (result.Status)
{
return Ok(result);
@@ -63,7 +67,7 @@ namespace TechHelper.Server.Controllers
var result = await _examService.GetAllExamPreview(userid.Id);
var result = await _examService.GetAllExamPreviewsAsync(userid.Id);
if (result.Status)
{

View File

@@ -1,93 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace TechHelper.Server.Migrations
{
/// <inheritdoc />
public partial class assignmentnot_required : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("02a808ba-bd16-4f90-bf2b-0bc42f767e00"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("9e526681-e57e-46b5-a01c-5731b27bfc4a"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("dfdfb884-4063-4161-84e0-9c225f4e883c"));
migrationBuilder.AlterColumn<Guid>(
name: "assignment",
table: "assignment_group",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci",
oldClrType: typeof(Guid),
oldType: "char(36)")
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("b2e087e6-ea32-46c4-aeb3-09b936cd0cf4"), null, "Teacher", "TEACHER" },
{ new Guid("ba33e047-8354-4f2c-b8b1-1f46441c28fc"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("d4b41bc3-612e-49dd-aeda-6a98ea0e4e68"), null, "Student", "STUDENT" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("b2e087e6-ea32-46c4-aeb3-09b936cd0cf4"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("ba33e047-8354-4f2c-b8b1-1f46441c28fc"));
migrationBuilder.DeleteData(
table: "AspNetRoles",
keyColumn: "Id",
keyValue: new Guid("d4b41bc3-612e-49dd-aeda-6a98ea0e4e68"));
migrationBuilder.AlterColumn<Guid>(
name: "assignment",
table: "assignment_group",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
collation: "ascii_general_ci",
oldClrType: typeof(Guid),
oldType: "char(36)",
oldNullable: true)
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("02a808ba-bd16-4f90-bf2b-0bc42f767e00"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("9e526681-e57e-46b5-a01c-5731b27bfc4a"), null, "Student", "STUDENT" },
{ new Guid("dfdfb884-4063-4161-84e0-9c225f4e883c"), null, "Teacher", "TEACHER" }
});
}
}
}

View File

@@ -12,7 +12,7 @@ using TechHelper.Context;
namespace TechHelper.Server.Migrations
{
[DbContext(typeof(ApplicationContext))]
[Migration("20250610025325_init")]
[Migration("20250619070929_init")]
partial class init
{
/// <inheritdoc />
@@ -36,7 +36,7 @@ namespace TechHelper.Server.Migrations
.HasColumnType("datetime(6)")
.HasColumnName("created_at");
b.Property<Guid>("CreatedBy")
b.Property<Guid>("CreatorId")
.HasColumnType("char(36)")
.HasColumnName("created_by");
@@ -53,9 +53,12 @@ namespace TechHelper.Server.Migrations
.HasColumnType("tinyint(1)")
.HasColumnName("deleted");
b.Property<string>("SubjectArea")
.IsRequired()
.HasColumnType("longtext")
b.Property<float>("Score")
.HasColumnType("float")
.HasColumnName("score");
b.Property<byte>("SubjectArea")
.HasColumnType("tinyint unsigned")
.HasColumnName("subject_area");
b.Property<string>("Title")
@@ -64,8 +67,8 @@ namespace TechHelper.Server.Migrations
.HasColumnType("varchar(255)")
.HasColumnName("title");
b.Property<float?>("TotalPoints")
.HasColumnType("float")
b.Property<byte>("TotalQuestions")
.HasColumnType("tinyint unsigned")
.HasColumnName("total_points");
b.Property<DateTime>("UpdatedAt")
@@ -77,7 +80,7 @@ namespace TechHelper.Server.Migrations
b.HasKey("Id");
b.HasIndex("CreatedBy");
b.HasIndex("CreatorId");
b.HasIndex("UserId");
@@ -151,61 +154,6 @@ namespace TechHelper.Server.Migrations
b.ToTable("assignment_class", (string)null);
});
modelBuilder.Entity("Entities.Contracts.AssignmentGroup", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)")
.HasColumnName("id");
b.Property<Guid?>("AssignmentId")
.IsRequired()
.HasColumnType("char(36)")
.HasColumnName("assignment");
b.Property<string>("Descript")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("descript");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<byte>("Number")
.HasColumnType("tinyint unsigned")
.HasColumnName("number");
b.Property<Guid?>("ParentGroup")
.HasColumnType("char(36)")
.HasColumnName("parent_group");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("title");
b.Property<float?>("TotalPoints")
.HasColumnType("float")
.HasColumnName("total_points");
b.Property<bool>("ValidQuestionGroup")
.HasColumnType("tinyint(1)")
.HasColumnName("valid_question_group");
b.HasKey("Id");
b.HasIndex("AssignmentId");
b.HasIndex("ParentGroup");
b.ToTable("assignment_group", (string)null);
});
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.Property<Guid>("Id")
@@ -221,6 +169,10 @@ namespace TechHelper.Server.Migrations
.HasColumnType("datetime(6)")
.HasColumnName("created_at");
b.Property<byte>("Index")
.HasColumnType("tinyint unsigned")
.HasColumnName("question_number");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
@@ -231,10 +183,6 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("question_id");
b.Property<byte>("QuestionNumber")
.HasColumnType("tinyint unsigned")
.HasColumnName("question_number");
b.Property<float?>("Score")
.HasColumnType("float")
.HasColumnName("score");
@@ -248,6 +196,61 @@ namespace TechHelper.Server.Migrations
b.ToTable("assignment_questions", (string)null);
});
modelBuilder.Entity("Entities.Contracts.AssignmentStruct", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)")
.HasColumnName("id");
b.Property<Guid?>("AssignmentId")
.HasColumnType("char(36)")
.HasColumnName("assignment");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("descript");
b.Property<byte>("Index")
.HasColumnType("tinyint unsigned")
.HasColumnName("number");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<byte>("Layout")
.HasColumnType("tinyint unsigned")
.HasColumnName("layout");
b.Property<Guid?>("ParentGroupId")
.HasColumnType("char(36)")
.HasColumnName("parent_group");
b.Property<float?>("Score")
.HasColumnType("float")
.HasColumnName("total_points");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("title");
b.HasKey("Id");
b.HasIndex("AssignmentId")
.IsUnique();
b.HasIndex("ParentGroupId");
b.ToTable("assignment_group", (string)null);
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
{
b.Property<Guid>("Id")
@@ -337,9 +340,8 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("teacher_id");
b.Property<string>("SubjectTaught")
.IsRequired()
.HasColumnType("longtext")
b.Property<byte>("SubjectTaught")
.HasColumnType("tinyint unsigned")
.HasColumnName("subject_taught");
b.HasKey("ClassId", "TeacherId");
@@ -349,6 +351,72 @@ namespace TechHelper.Server.Migrations
b.ToTable("class_teachers", (string)null);
});
modelBuilder.Entity("Entities.Contracts.KeyPoint", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("varchar(255)");
b.Property<Guid>("LessonID")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("LessonID");
b.ToTable("key_point");
});
modelBuilder.Entity("Entities.Contracts.Lesson", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("TextbookID")
.HasColumnType("char(36)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("TextbookID");
b.ToTable("lesson");
});
modelBuilder.Entity("Entities.Contracts.LessonQuestion", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("LessonID")
.HasColumnType("char(36)");
b.Property<string>("Question")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("LessonID");
b.ToTable("lesson_question");
});
modelBuilder.Entity("Entities.Contracts.Question", b =>
{
b.Property<Guid>("Id")
@@ -356,8 +424,7 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("id");
b.Property<string>("CorrectAnswer")
.IsRequired()
b.Property<string>("Answer")
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("correct_answer");
@@ -369,53 +436,75 @@ namespace TechHelper.Server.Migrations
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<DateTime>("CreatedAt"));
b.Property<Guid>("CreatedBy")
b.Property<Guid>("CreatorId")
.HasColumnType("char(36)")
.HasColumnName("created_by");
b.Property<string>("DifficultyLevel")
.IsRequired()
b.Property<byte>("DifficultyLevel")
.HasMaxLength(10)
.HasColumnType("varchar(10)")
.HasColumnType("tinyint unsigned")
.HasColumnName("difficulty_level");
b.Property<byte>("GroupState")
.HasColumnType("tinyint unsigned")
.HasColumnName("group_state");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<string>("QuestionText")
b.Property<Guid?>("KeyPointId")
.HasColumnType("char(36)")
.HasColumnName("key_point");
b.Property<Guid?>("LessonId")
.HasColumnType("char(36)")
.HasColumnName("lesson");
b.Property<string>("Options")
.HasColumnType("longtext")
.HasColumnName("options");
b.Property<Guid?>("ParentQuestionId")
.HasColumnType("char(36)")
.HasColumnName("parent_question_group_id");
b.Property<byte>("SubjectArea")
.HasMaxLength(100)
.HasColumnType("tinyint unsigned")
.HasColumnName("subject_area");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("question_text");
b.Property<string>("QuestionType")
.IsRequired()
b.Property<byte>("Type")
.HasMaxLength(20)
.HasColumnType("varchar(20)")
.HasColumnType("tinyint unsigned")
.HasColumnName("question_type");
b.Property<string>("SubjectArea")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("subject_area");
b.Property<DateTime>("UpdatedAt")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("datetime(6)")
.HasColumnName("updated_at");
b.Property<bool>("ValidQuestion")
.HasColumnType("tinyint(1)")
.HasColumnName("valid_question");
b.HasKey("Id");
b.HasIndex("CreatedBy");
b.HasIndex("CreatorId");
b.HasIndex("KeyPointId");
b.HasIndex("LessonId");
b.HasIndex("ParentQuestionId");
b.HasIndex("Title")
.HasAnnotation("MySql:IndexPrefixLength", new[] { 20 });
b.ToTable("questions", (string)null);
});
@@ -439,7 +528,7 @@ namespace TechHelper.Server.Migrations
.HasColumnType("datetime(6)")
.HasColumnName("graded_at");
b.Property<Guid?>("GradedBy")
b.Property<Guid?>("GraderId")
.HasColumnType("char(36)")
.HasColumnName("graded_by");
@@ -459,10 +548,9 @@ namespace TechHelper.Server.Migrations
.HasColumnType("float")
.HasColumnName("overall_grade");
b.Property<string>("Status")
.IsRequired()
b.Property<int>("Status")
.HasMaxLength(15)
.HasColumnType("varchar(15)")
.HasColumnType("int")
.HasColumnName("status");
b.Property<Guid>("StudentId")
@@ -477,7 +565,7 @@ namespace TechHelper.Server.Migrations
b.HasIndex("AssignmentId");
b.HasIndex("GradedBy");
b.HasIndex("GraderId");
b.HasIndex("StudentId");
@@ -552,6 +640,30 @@ namespace TechHelper.Server.Migrations
b.ToTable("submission_details", (string)null);
});
modelBuilder.Entity("Entities.Contracts.Textbook", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<byte>("Grade")
.HasColumnType("tinyint unsigned");
b.Property<byte>("Publisher")
.HasColumnType("tinyint unsigned");
b.Property<byte>("SubjectArea")
.HasColumnType("tinyint unsigned");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("textbook");
});
modelBuilder.Entity("Entities.Contracts.User", b =>
{
b.Property<Guid>("Id")
@@ -662,19 +774,19 @@ namespace TechHelper.Server.Migrations
b.HasData(
new
{
Id = new Guid("9e526681-e57e-46b5-a01c-5731b27bfc4a"),
Id = new Guid("895d8f32-714e-4a14-bd97-8fa262b83172"),
Name = "Student",
NormalizedName = "STUDENT"
},
new
{
Id = new Guid("dfdfb884-4063-4161-84e0-9c225f4e883c"),
Id = new Guid("d182c396-c656-42da-965a-d93c17a1f74f"),
Name = "Teacher",
NormalizedName = "TEACHER"
},
new
{
Id = new Guid("02a808ba-bd16-4f90-bf2b-0bc42f767e00"),
Id = new Guid("4e65fab9-3315-4474-b92c-bdab5a617e65"),
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
});
@@ -787,7 +899,7 @@ namespace TechHelper.Server.Migrations
{
b.HasOne("Entities.Contracts.User", "Creator")
.WithMany()
.HasForeignKey("CreatedBy")
.HasForeignKey("CreatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -828,27 +940,9 @@ namespace TechHelper.Server.Migrations
b.Navigation("Class");
});
modelBuilder.Entity("Entities.Contracts.AssignmentGroup", b =>
{
b.HasOne("Entities.Contracts.Assignment", "Assignment")
.WithMany("AssignmentGroups")
.HasForeignKey("AssignmentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Entities.Contracts.AssignmentGroup", "ParentAssignmentGroup")
.WithMany("ChildAssignmentGroups")
.HasForeignKey("ParentGroup")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Assignment");
b.Navigation("ParentAssignmentGroup");
});
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.HasOne("Entities.Contracts.AssignmentGroup", "AssignmentGroup")
b.HasOne("Entities.Contracts.AssignmentStruct", "AssignmentGroup")
.WithMany("AssignmentQuestions")
.HasForeignKey("AssignmentGroupId")
.OnDelete(DeleteBehavior.Cascade)
@@ -865,6 +959,22 @@ namespace TechHelper.Server.Migrations
b.Navigation("Question");
});
modelBuilder.Entity("Entities.Contracts.AssignmentStruct", b =>
{
b.HasOne("Entities.Contracts.Assignment", "Assignment")
.WithOne("ExamStruct")
.HasForeignKey("Entities.Contracts.AssignmentStruct", "AssignmentId");
b.HasOne("Entities.Contracts.AssignmentStruct", "ParentGroup")
.WithMany("ChildrenGroups")
.HasForeignKey("ParentGroupId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Assignment");
b.Navigation("ParentGroup");
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
{
b.HasOne("Entities.Contracts.User", "HeadTeacher")
@@ -914,15 +1024,69 @@ namespace TechHelper.Server.Migrations
b.Navigation("Teacher");
});
modelBuilder.Entity("Entities.Contracts.KeyPoint", b =>
{
b.HasOne("Entities.Contracts.Lesson", "Lesson")
.WithMany("KeyPoints")
.HasForeignKey("LessonID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Lesson");
});
modelBuilder.Entity("Entities.Contracts.Lesson", b =>
{
b.HasOne("Entities.Contracts.Textbook", "Textbook")
.WithMany("Lessons")
.HasForeignKey("TextbookID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Textbook");
});
modelBuilder.Entity("Entities.Contracts.LessonQuestion", b =>
{
b.HasOne("Entities.Contracts.Lesson", "Lesson")
.WithMany("LessonQuestions")
.HasForeignKey("LessonID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Lesson");
});
modelBuilder.Entity("Entities.Contracts.Question", b =>
{
b.HasOne("Entities.Contracts.User", "Creator")
.WithMany("CreatedQuestions")
.HasForeignKey("CreatedBy")
.HasForeignKey("CreatorId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Entities.Contracts.KeyPoint", "KeyPoint")
.WithMany("Questions")
.HasForeignKey("KeyPointId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.Lesson", "Lesson")
.WithMany("Questions")
.HasForeignKey("LessonId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.Question", "ParentQuestion")
.WithMany("ChildrenQuestion")
.HasForeignKey("ParentQuestionId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Creator");
b.Navigation("KeyPoint");
b.Navigation("Lesson");
b.Navigation("ParentQuestion");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>
@@ -935,7 +1099,7 @@ namespace TechHelper.Server.Migrations
b.HasOne("Entities.Contracts.User", "Grader")
.WithMany("GradedSubmissions")
.HasForeignKey("GradedBy")
.HasForeignKey("GraderId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.User", "Student")
@@ -959,7 +1123,7 @@ namespace TechHelper.Server.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Entities.Contracts.User", "User")
b.HasOne("Entities.Contracts.User", "Student")
.WithMany("SubmissionDetails")
.HasForeignKey("StudentId")
.OnDelete(DeleteBehavior.Restrict)
@@ -973,9 +1137,9 @@ namespace TechHelper.Server.Migrations
b.Navigation("AssignmentQuestion");
b.Navigation("Submission");
b.Navigation("Student");
b.Navigation("User");
b.Navigation("Submission");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
@@ -1035,23 +1199,24 @@ namespace TechHelper.Server.Migrations
b.Navigation("AssignmentClasses");
b.Navigation("AssignmentGroups");
b.Navigation("ExamStruct")
.IsRequired();
b.Navigation("Submissions");
});
modelBuilder.Entity("Entities.Contracts.AssignmentGroup", b =>
{
b.Navigation("AssignmentQuestions");
b.Navigation("ChildAssignmentGroups");
});
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.Navigation("SubmissionDetails");
});
modelBuilder.Entity("Entities.Contracts.AssignmentStruct", b =>
{
b.Navigation("AssignmentQuestions");
b.Navigation("ChildrenGroups");
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
{
b.Navigation("AssignmentClasses");
@@ -1061,9 +1226,25 @@ namespace TechHelper.Server.Migrations
b.Navigation("ClassTeachers");
});
modelBuilder.Entity("Entities.Contracts.KeyPoint", b =>
{
b.Navigation("Questions");
});
modelBuilder.Entity("Entities.Contracts.Lesson", b =>
{
b.Navigation("KeyPoints");
b.Navigation("LessonQuestions");
b.Navigation("Questions");
});
modelBuilder.Entity("Entities.Contracts.Question", b =>
{
b.Navigation("AssignmentQuestions");
b.Navigation("ChildrenQuestion");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>
@@ -1071,6 +1252,11 @@ namespace TechHelper.Server.Migrations
b.Navigation("SubmissionDetails");
});
modelBuilder.Entity("Entities.Contracts.Textbook", b =>
{
b.Navigation("Lessons");
});
modelBuilder.Entity("Entities.Contracts.User", b =>
{
b.Navigation("CreatedAssignments");

View File

@@ -77,6 +77,23 @@ namespace TechHelper.Server.Migrations
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "textbook",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Grade = table.Column<byte>(type: "tinyint unsigned", nullable: false),
Title = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Publisher = table.Column<byte>(type: "tinyint unsigned", nullable: false),
SubjectArea = table.Column<byte>(type: "tinyint unsigned", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_textbook", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
@@ -207,10 +224,10 @@ namespace TechHelper.Server.Migrations
.Annotation("MySql:CharSet", "utf8mb4"),
description = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
subject_area = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
subject_area = table.Column<byte>(type: "tinyint unsigned", nullable: false),
due_date = table.Column<DateTime>(type: "datetime(6)", nullable: false),
total_points = table.Column<float>(type: "float", nullable: true),
total_points = table.Column<byte>(type: "tinyint unsigned", nullable: false),
score = table.Column<float>(type: "float", nullable: false),
created_by = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false),
updated_at = table.Column<DateTime>(type: "datetime(6)", nullable: false),
@@ -263,36 +280,25 @@ namespace TechHelper.Server.Migrations
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "questions",
name: "lesson",
columns: table => new
{
id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
question_text = table.Column<string>(type: "longtext", maxLength: 65535, nullable: false)
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Title = table.Column<string>(type: "varchar(255)", maxLength: 255, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
question_type = table.Column<string>(type: "varchar(20)", maxLength: 20, nullable: false)
Description = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
correct_answer = table.Column<string>(type: "longtext", maxLength: 65535, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
difficulty_level = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
subject_area = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
created_by = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
updated_at = table.Column<DateTime>(type: "datetime(6)", rowVersion: true, nullable: false),
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false),
valid_question = table.Column<bool>(type: "tinyint(1)", nullable: false)
TextbookID = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_questions", x => x.id);
table.PrimaryKey("PK_lesson", x => x.Id);
table.ForeignKey(
name: "FK_questions_AspNetUsers_created_by",
column: x => x.created_by,
principalTable: "AspNetUsers",
name: "FK_lesson_textbook_TextbookID",
column: x => x.TextbookID,
principalTable: "textbook",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
@@ -326,16 +332,16 @@ namespace TechHelper.Server.Migrations
columns: table => new
{
id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
assignment = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
assignment = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
title = table.Column<string>(type: "longtext", maxLength: 65535, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
descript = table.Column<string>(type: "longtext", maxLength: 65535, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
layout = table.Column<byte>(type: "tinyint unsigned", nullable: false),
total_points = table.Column<float>(type: "float", nullable: true),
number = table.Column<byte>(type: "tinyint unsigned", nullable: false),
parent_group = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false),
valid_question_group = table.Column<bool>(type: "tinyint(1)", nullable: false)
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false)
},
constraints: table =>
{
@@ -350,8 +356,7 @@ namespace TechHelper.Server.Migrations
name: "FK_assignment_group_assignments_assignment",
column: x => x.assignment,
principalTable: "assignments",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
principalColumn: "id");
})
.Annotation("MySql:CharSet", "utf8mb4");
@@ -370,8 +375,7 @@ namespace TechHelper.Server.Migrations
graded_by = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
graded_at = table.Column<DateTime>(type: "datetime(6)", nullable: true),
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false),
status = table.Column<string>(type: "varchar(15)", maxLength: 15, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
status = table.Column<int>(type: "int", maxLength: 15, nullable: false)
},
constraints: table =>
{
@@ -457,8 +461,7 @@ namespace TechHelper.Server.Migrations
{
class_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
teacher_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
subject_taught = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
subject_taught = table.Column<byte>(type: "tinyint unsigned", nullable: false)
},
constraints: table =>
{
@@ -478,6 +481,102 @@ namespace TechHelper.Server.Migrations
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "key_point",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Key = table.Column<string>(type: "varchar(255)", maxLength: 255, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
LessonID = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_key_point", x => x.Id);
table.ForeignKey(
name: "FK_key_point_lesson_LessonID",
column: x => x.LessonID,
principalTable: "lesson",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "lesson_question",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Question = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
LessonID = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_lesson_question", x => x.Id);
table.ForeignKey(
name: "FK_lesson_question_lesson_LessonID",
column: x => x.LessonID,
principalTable: "lesson",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "questions",
columns: table => new
{
id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
question_text = table.Column<string>(type: "longtext", maxLength: 65535, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
correct_answer = table.Column<string>(type: "longtext", maxLength: 65535, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
question_type = table.Column<byte>(type: "tinyint unsigned", maxLength: 20, nullable: false),
difficulty_level = table.Column<byte>(type: "tinyint unsigned", maxLength: 10, nullable: false),
subject_area = table.Column<byte>(type: "tinyint unsigned", maxLength: 100, nullable: false),
group_state = table.Column<byte>(type: "tinyint unsigned", nullable: false),
options = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
key_point = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
lesson = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
parent_question_group_id = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
created_by = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
updated_at = table.Column<DateTime>(type: "datetime(6)", rowVersion: true, nullable: false),
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false)
},
constraints: table =>
{
table.PrimaryKey("PK_questions", x => x.id);
table.ForeignKey(
name: "FK_questions_AspNetUsers_created_by",
column: x => x.created_by,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_questions_key_point_key_point",
column: x => x.key_point,
principalTable: "key_point",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_questions_lesson_lesson",
column: x => x.lesson,
principalTable: "lesson",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_questions_questions_parent_question_group_id",
column: x => x.parent_question_group_id,
principalTable: "questions",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "assignment_questions",
columns: table => new
@@ -556,9 +655,9 @@ namespace TechHelper.Server.Migrations
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("02a808ba-bd16-4f90-bf2b-0bc42f767e00"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("9e526681-e57e-46b5-a01c-5731b27bfc4a"), null, "Student", "STUDENT" },
{ new Guid("dfdfb884-4063-4161-84e0-9c225f4e883c"), null, "Teacher", "TEACHER" }
{ new Guid("4e65fab9-3315-4474-b92c-bdab5a617e65"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("895d8f32-714e-4a14-bd97-8fa262b83172"), null, "Student", "STUDENT" },
{ new Guid("d182c396-c656-42da-965a-d93c17a1f74f"), null, "Teacher", "TEACHER" }
});
migrationBuilder.CreateIndex(
@@ -611,7 +710,8 @@ namespace TechHelper.Server.Migrations
migrationBuilder.CreateIndex(
name: "IX_assignment_group_assignment",
table: "assignment_group",
column: "assignment");
column: "assignment",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_assignment_group_parent_group",
@@ -653,11 +753,47 @@ namespace TechHelper.Server.Migrations
table: "classes",
column: "head_teacher_id");
migrationBuilder.CreateIndex(
name: "IX_key_point_LessonID",
table: "key_point",
column: "LessonID");
migrationBuilder.CreateIndex(
name: "IX_lesson_TextbookID",
table: "lesson",
column: "TextbookID");
migrationBuilder.CreateIndex(
name: "IX_lesson_question_LessonID",
table: "lesson_question",
column: "LessonID");
migrationBuilder.CreateIndex(
name: "IX_questions_created_by",
table: "questions",
column: "created_by");
migrationBuilder.CreateIndex(
name: "IX_questions_key_point",
table: "questions",
column: "key_point");
migrationBuilder.CreateIndex(
name: "IX_questions_lesson",
table: "questions",
column: "lesson");
migrationBuilder.CreateIndex(
name: "IX_questions_parent_question_group_id",
table: "questions",
column: "parent_question_group_id");
migrationBuilder.CreateIndex(
name: "IX_questions_question_text",
table: "questions",
column: "question_text")
.Annotation("MySql:IndexPrefixLength", new[] { 20 });
migrationBuilder.CreateIndex(
name: "IX_submission_details_assignment_question_id",
table: "submission_details",
@@ -719,6 +855,9 @@ namespace TechHelper.Server.Migrations
migrationBuilder.DropTable(
name: "class_teachers");
migrationBuilder.DropTable(
name: "lesson_question");
migrationBuilder.DropTable(
name: "submission_details");
@@ -743,8 +882,17 @@ namespace TechHelper.Server.Migrations
migrationBuilder.DropTable(
name: "assignments");
migrationBuilder.DropTable(
name: "key_point");
migrationBuilder.DropTable(
name: "AspNetUsers");
migrationBuilder.DropTable(
name: "lesson");
migrationBuilder.DropTable(
name: "textbook");
}
}
}

View File

@@ -33,7 +33,7 @@ namespace TechHelper.Server.Migrations
.HasColumnType("datetime(6)")
.HasColumnName("created_at");
b.Property<Guid>("CreatedBy")
b.Property<Guid>("CreatorId")
.HasColumnType("char(36)")
.HasColumnName("created_by");
@@ -50,9 +50,12 @@ namespace TechHelper.Server.Migrations
.HasColumnType("tinyint(1)")
.HasColumnName("deleted");
b.Property<string>("SubjectArea")
.IsRequired()
.HasColumnType("longtext")
b.Property<float>("Score")
.HasColumnType("float")
.HasColumnName("score");
b.Property<byte>("SubjectArea")
.HasColumnType("tinyint unsigned")
.HasColumnName("subject_area");
b.Property<string>("Title")
@@ -61,8 +64,8 @@ namespace TechHelper.Server.Migrations
.HasColumnType("varchar(255)")
.HasColumnName("title");
b.Property<float?>("TotalPoints")
.HasColumnType("float")
b.Property<byte>("TotalQuestions")
.HasColumnType("tinyint unsigned")
.HasColumnName("total_points");
b.Property<DateTime>("UpdatedAt")
@@ -74,7 +77,7 @@ namespace TechHelper.Server.Migrations
b.HasKey("Id");
b.HasIndex("CreatedBy");
b.HasIndex("CreatorId");
b.HasIndex("UserId");
@@ -148,60 +151,6 @@ namespace TechHelper.Server.Migrations
b.ToTable("assignment_class", (string)null);
});
modelBuilder.Entity("Entities.Contracts.AssignmentGroup", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)")
.HasColumnName("id");
b.Property<Guid?>("AssignmentId")
.HasColumnType("char(36)")
.HasColumnName("assignment");
b.Property<string>("Descript")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("descript");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<byte>("Number")
.HasColumnType("tinyint unsigned")
.HasColumnName("number");
b.Property<Guid?>("ParentGroup")
.HasColumnType("char(36)")
.HasColumnName("parent_group");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("title");
b.Property<float?>("TotalPoints")
.HasColumnType("float")
.HasColumnName("total_points");
b.Property<bool>("ValidQuestionGroup")
.HasColumnType("tinyint(1)")
.HasColumnName("valid_question_group");
b.HasKey("Id");
b.HasIndex("AssignmentId");
b.HasIndex("ParentGroup");
b.ToTable("assignment_group", (string)null);
});
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.Property<Guid>("Id")
@@ -217,6 +166,10 @@ namespace TechHelper.Server.Migrations
.HasColumnType("datetime(6)")
.HasColumnName("created_at");
b.Property<byte>("Index")
.HasColumnType("tinyint unsigned")
.HasColumnName("question_number");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
@@ -227,10 +180,6 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("question_id");
b.Property<byte>("QuestionNumber")
.HasColumnType("tinyint unsigned")
.HasColumnName("question_number");
b.Property<float?>("Score")
.HasColumnType("float")
.HasColumnName("score");
@@ -244,6 +193,61 @@ namespace TechHelper.Server.Migrations
b.ToTable("assignment_questions", (string)null);
});
modelBuilder.Entity("Entities.Contracts.AssignmentStruct", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)")
.HasColumnName("id");
b.Property<Guid?>("AssignmentId")
.HasColumnType("char(36)")
.HasColumnName("assignment");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("descript");
b.Property<byte>("Index")
.HasColumnType("tinyint unsigned")
.HasColumnName("number");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<byte>("Layout")
.HasColumnType("tinyint unsigned")
.HasColumnName("layout");
b.Property<Guid?>("ParentGroupId")
.HasColumnType("char(36)")
.HasColumnName("parent_group");
b.Property<float?>("Score")
.HasColumnType("float")
.HasColumnName("total_points");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("title");
b.HasKey("Id");
b.HasIndex("AssignmentId")
.IsUnique();
b.HasIndex("ParentGroupId");
b.ToTable("assignment_group", (string)null);
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
{
b.Property<Guid>("Id")
@@ -333,9 +337,8 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("teacher_id");
b.Property<string>("SubjectTaught")
.IsRequired()
.HasColumnType("longtext")
b.Property<byte>("SubjectTaught")
.HasColumnType("tinyint unsigned")
.HasColumnName("subject_taught");
b.HasKey("ClassId", "TeacherId");
@@ -345,6 +348,72 @@ namespace TechHelper.Server.Migrations
b.ToTable("class_teachers", (string)null);
});
modelBuilder.Entity("Entities.Contracts.KeyPoint", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("varchar(255)");
b.Property<Guid>("LessonID")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("LessonID");
b.ToTable("key_point");
});
modelBuilder.Entity("Entities.Contracts.Lesson", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("TextbookID")
.HasColumnType("char(36)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("TextbookID");
b.ToTable("lesson");
});
modelBuilder.Entity("Entities.Contracts.LessonQuestion", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("LessonID")
.HasColumnType("char(36)");
b.Property<string>("Question")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("LessonID");
b.ToTable("lesson_question");
});
modelBuilder.Entity("Entities.Contracts.Question", b =>
{
b.Property<Guid>("Id")
@@ -352,8 +421,7 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("id");
b.Property<string>("CorrectAnswer")
.IsRequired()
b.Property<string>("Answer")
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("correct_answer");
@@ -365,53 +433,75 @@ namespace TechHelper.Server.Migrations
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<DateTime>("CreatedAt"));
b.Property<Guid>("CreatedBy")
b.Property<Guid>("CreatorId")
.HasColumnType("char(36)")
.HasColumnName("created_by");
b.Property<string>("DifficultyLevel")
.IsRequired()
b.Property<byte>("DifficultyLevel")
.HasMaxLength(10)
.HasColumnType("varchar(10)")
.HasColumnType("tinyint unsigned")
.HasColumnName("difficulty_level");
b.Property<byte>("GroupState")
.HasColumnType("tinyint unsigned")
.HasColumnName("group_state");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<string>("QuestionText")
b.Property<Guid?>("KeyPointId")
.HasColumnType("char(36)")
.HasColumnName("key_point");
b.Property<Guid?>("LessonId")
.HasColumnType("char(36)")
.HasColumnName("lesson");
b.Property<string>("Options")
.HasColumnType("longtext")
.HasColumnName("options");
b.Property<Guid?>("ParentQuestionId")
.HasColumnType("char(36)")
.HasColumnName("parent_question_group_id");
b.Property<byte>("SubjectArea")
.HasMaxLength(100)
.HasColumnType("tinyint unsigned")
.HasColumnName("subject_area");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasColumnName("question_text");
b.Property<string>("QuestionType")
.IsRequired()
b.Property<byte>("Type")
.HasMaxLength(20)
.HasColumnType("varchar(20)")
.HasColumnType("tinyint unsigned")
.HasColumnName("question_type");
b.Property<string>("SubjectArea")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("subject_area");
b.Property<DateTime>("UpdatedAt")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("datetime(6)")
.HasColumnName("updated_at");
b.Property<bool>("ValidQuestion")
.HasColumnType("tinyint(1)")
.HasColumnName("valid_question");
b.HasKey("Id");
b.HasIndex("CreatedBy");
b.HasIndex("CreatorId");
b.HasIndex("KeyPointId");
b.HasIndex("LessonId");
b.HasIndex("ParentQuestionId");
b.HasIndex("Title")
.HasAnnotation("MySql:IndexPrefixLength", new[] { 20 });
b.ToTable("questions", (string)null);
});
@@ -435,7 +525,7 @@ namespace TechHelper.Server.Migrations
.HasColumnType("datetime(6)")
.HasColumnName("graded_at");
b.Property<Guid?>("GradedBy")
b.Property<Guid?>("GraderId")
.HasColumnType("char(36)")
.HasColumnName("graded_by");
@@ -455,10 +545,9 @@ namespace TechHelper.Server.Migrations
.HasColumnType("float")
.HasColumnName("overall_grade");
b.Property<string>("Status")
.IsRequired()
b.Property<int>("Status")
.HasMaxLength(15)
.HasColumnType("varchar(15)")
.HasColumnType("int")
.HasColumnName("status");
b.Property<Guid>("StudentId")
@@ -473,7 +562,7 @@ namespace TechHelper.Server.Migrations
b.HasIndex("AssignmentId");
b.HasIndex("GradedBy");
b.HasIndex("GraderId");
b.HasIndex("StudentId");
@@ -548,6 +637,30 @@ namespace TechHelper.Server.Migrations
b.ToTable("submission_details", (string)null);
});
modelBuilder.Entity("Entities.Contracts.Textbook", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<byte>("Grade")
.HasColumnType("tinyint unsigned");
b.Property<byte>("Publisher")
.HasColumnType("tinyint unsigned");
b.Property<byte>("SubjectArea")
.HasColumnType("tinyint unsigned");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("textbook");
});
modelBuilder.Entity("Entities.Contracts.User", b =>
{
b.Property<Guid>("Id")
@@ -658,19 +771,19 @@ namespace TechHelper.Server.Migrations
b.HasData(
new
{
Id = new Guid("d4b41bc3-612e-49dd-aeda-6a98ea0e4e68"),
Id = new Guid("895d8f32-714e-4a14-bd97-8fa262b83172"),
Name = "Student",
NormalizedName = "STUDENT"
},
new
{
Id = new Guid("b2e087e6-ea32-46c4-aeb3-09b936cd0cf4"),
Id = new Guid("d182c396-c656-42da-965a-d93c17a1f74f"),
Name = "Teacher",
NormalizedName = "TEACHER"
},
new
{
Id = new Guid("ba33e047-8354-4f2c-b8b1-1f46441c28fc"),
Id = new Guid("4e65fab9-3315-4474-b92c-bdab5a617e65"),
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
});
@@ -783,7 +896,7 @@ namespace TechHelper.Server.Migrations
{
b.HasOne("Entities.Contracts.User", "Creator")
.WithMany()
.HasForeignKey("CreatedBy")
.HasForeignKey("CreatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -824,26 +937,9 @@ namespace TechHelper.Server.Migrations
b.Navigation("Class");
});
modelBuilder.Entity("Entities.Contracts.AssignmentGroup", b =>
{
b.HasOne("Entities.Contracts.Assignment", "Assignment")
.WithMany("AssignmentGroups")
.HasForeignKey("AssignmentId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Entities.Contracts.AssignmentGroup", "ParentAssignmentGroup")
.WithMany("ChildAssignmentGroups")
.HasForeignKey("ParentGroup")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Assignment");
b.Navigation("ParentAssignmentGroup");
});
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.HasOne("Entities.Contracts.AssignmentGroup", "AssignmentGroup")
b.HasOne("Entities.Contracts.AssignmentStruct", "AssignmentGroup")
.WithMany("AssignmentQuestions")
.HasForeignKey("AssignmentGroupId")
.OnDelete(DeleteBehavior.Cascade)
@@ -860,6 +956,22 @@ namespace TechHelper.Server.Migrations
b.Navigation("Question");
});
modelBuilder.Entity("Entities.Contracts.AssignmentStruct", b =>
{
b.HasOne("Entities.Contracts.Assignment", "Assignment")
.WithOne("ExamStruct")
.HasForeignKey("Entities.Contracts.AssignmentStruct", "AssignmentId");
b.HasOne("Entities.Contracts.AssignmentStruct", "ParentGroup")
.WithMany("ChildrenGroups")
.HasForeignKey("ParentGroupId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Assignment");
b.Navigation("ParentGroup");
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
{
b.HasOne("Entities.Contracts.User", "HeadTeacher")
@@ -909,15 +1021,69 @@ namespace TechHelper.Server.Migrations
b.Navigation("Teacher");
});
modelBuilder.Entity("Entities.Contracts.KeyPoint", b =>
{
b.HasOne("Entities.Contracts.Lesson", "Lesson")
.WithMany("KeyPoints")
.HasForeignKey("LessonID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Lesson");
});
modelBuilder.Entity("Entities.Contracts.Lesson", b =>
{
b.HasOne("Entities.Contracts.Textbook", "Textbook")
.WithMany("Lessons")
.HasForeignKey("TextbookID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Textbook");
});
modelBuilder.Entity("Entities.Contracts.LessonQuestion", b =>
{
b.HasOne("Entities.Contracts.Lesson", "Lesson")
.WithMany("LessonQuestions")
.HasForeignKey("LessonID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Lesson");
});
modelBuilder.Entity("Entities.Contracts.Question", b =>
{
b.HasOne("Entities.Contracts.User", "Creator")
.WithMany("CreatedQuestions")
.HasForeignKey("CreatedBy")
.HasForeignKey("CreatorId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Entities.Contracts.KeyPoint", "KeyPoint")
.WithMany("Questions")
.HasForeignKey("KeyPointId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.Lesson", "Lesson")
.WithMany("Questions")
.HasForeignKey("LessonId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.Question", "ParentQuestion")
.WithMany("ChildrenQuestion")
.HasForeignKey("ParentQuestionId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Creator");
b.Navigation("KeyPoint");
b.Navigation("Lesson");
b.Navigation("ParentQuestion");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>
@@ -930,7 +1096,7 @@ namespace TechHelper.Server.Migrations
b.HasOne("Entities.Contracts.User", "Grader")
.WithMany("GradedSubmissions")
.HasForeignKey("GradedBy")
.HasForeignKey("GraderId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.User", "Student")
@@ -954,7 +1120,7 @@ namespace TechHelper.Server.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Entities.Contracts.User", "User")
b.HasOne("Entities.Contracts.User", "Student")
.WithMany("SubmissionDetails")
.HasForeignKey("StudentId")
.OnDelete(DeleteBehavior.Restrict)
@@ -968,9 +1134,9 @@ namespace TechHelper.Server.Migrations
b.Navigation("AssignmentQuestion");
b.Navigation("Submission");
b.Navigation("Student");
b.Navigation("User");
b.Navigation("Submission");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
@@ -1030,23 +1196,24 @@ namespace TechHelper.Server.Migrations
b.Navigation("AssignmentClasses");
b.Navigation("AssignmentGroups");
b.Navigation("ExamStruct")
.IsRequired();
b.Navigation("Submissions");
});
modelBuilder.Entity("Entities.Contracts.AssignmentGroup", b =>
{
b.Navigation("AssignmentQuestions");
b.Navigation("ChildAssignmentGroups");
});
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.Navigation("SubmissionDetails");
});
modelBuilder.Entity("Entities.Contracts.AssignmentStruct", b =>
{
b.Navigation("AssignmentQuestions");
b.Navigation("ChildrenGroups");
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
{
b.Navigation("AssignmentClasses");
@@ -1056,9 +1223,25 @@ namespace TechHelper.Server.Migrations
b.Navigation("ClassTeachers");
});
modelBuilder.Entity("Entities.Contracts.KeyPoint", b =>
{
b.Navigation("Questions");
});
modelBuilder.Entity("Entities.Contracts.Lesson", b =>
{
b.Navigation("KeyPoints");
b.Navigation("LessonQuestions");
b.Navigation("Questions");
});
modelBuilder.Entity("Entities.Contracts.Question", b =>
{
b.Navigation("AssignmentQuestions");
b.Navigation("ChildrenQuestion");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>
@@ -1066,6 +1249,11 @@ namespace TechHelper.Server.Migrations
b.Navigation("SubmissionDetails");
});
modelBuilder.Entity("Entities.Contracts.Textbook", b =>
{
b.Navigation("Lessons");
});
modelBuilder.Entity("Entities.Contracts.User", b =>
{
b.Navigation("CreatedAssignments");

View File

@@ -11,6 +11,7 @@ using System.Text;
using TechHelper.Features;
using TechHelper.Services;
using TechHelper.Server.Services;
using TechHelper.Server.Repositories;
var builder = WebApplication.CreateBuilder(args);
@@ -26,7 +27,7 @@ builder.Services.AddDbContext<ApplicationContext>(options =>
).AddUnitOfWork<ApplicationContext>()
.AddCustomRepository<Assignment, AssignmentRepository>()
.AddCustomRepository<AssignmentAttachment, AssignmentAttachmentRepository>()
.AddCustomRepository<AssignmentGroup, AssignmentGroupRepository>()
.AddCustomRepository<AssignmentStruct, AssignmentGroupRepository>()
.AddCustomRepository<AssignmentQuestion, AssignmentQuestionRepository>()
.AddCustomRepository<Class, ClassRepository>()
.AddCustomRepository<ClassStudent, ClassStudentRepository>()
@@ -85,6 +86,7 @@ builder.Services.AddScoped<IEmailSender, QEmailSender>();
builder.Services.AddTransient<IUserRegistrationService, UserRegistrationService>();
builder.Services.AddScoped<IClassService, ClassService>();
builder.Services.AddScoped<IExamService, ExamService>();
builder.Services.AddScoped<IExamRepository, ExamRepository>();
builder.Services.AddEndpointsApiExplorer();

View File

@@ -0,0 +1,92 @@
using Entities.Contracts;
using Entities.DTO;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Api;
using TechHelper.Repository;
namespace TechHelper.Server.Repositories
{
public class ExamRepository : IExamRepository
{
private readonly IUnitOfWork _unitOfWork;
private readonly IRepository<Assignment> _assignmentRepo;
private readonly IRepository<AssignmentStruct> _assignmentGroupRepo;
private readonly IRepository<Question> _questionRepo;
public ExamRepository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_assignmentRepo = _unitOfWork.GetRepository<Assignment>();
_assignmentGroupRepo = _unitOfWork.GetRepository<AssignmentStruct>();
}
public async Task<Assignment?> GetFullExamByIdAsync(Guid assignmentId)
{
return null;
}
private async Task LoadSubGroupsRecursive(AssignmentStruct group)
{
// EF Core 已经加载了下一层,我们需要确保更深层次的加载
var groupWithChildren = await _assignmentGroupRepo.GetFirstOrDefaultAsync(
predicate: g => g.Id == group.Id,
include: source => source
.Include(g => g.ChildrenGroups.Where(cg => !cg.IsDeleted))
.ThenInclude(cg => cg.AssignmentQuestions.Where(aq => !aq.IsDeleted))
.ThenInclude(aq => aq.Question)
.Include(g => g.AssignmentQuestions.Where(aq => !aq.IsDeleted))
.ThenInclude(aq => aq.Question)
);
group.ChildrenGroups = groupWithChildren.ChildrenGroups;
group.AssignmentQuestions = groupWithChildren.AssignmentQuestions;
if (group.ChildrenGroups != null)
{
foreach (var child in group.ChildrenGroups)
{
await LoadSubGroupsRecursive(child);
}
}
}
public async Task<IEnumerable<Assignment>> GetExamPreviewsByUserAsync(Guid userId)
{
return await _assignmentRepo.GetAllAsync(
predicate: a => a.CreatorId == userId && !a.IsDeleted);
}
public async Task AddAsync(Assignment assignment)
{
await _assignmentRepo.InsertAsync(assignment);
}
public async Task AddAsync(QuestionGroupDto qg)
{
if (qg.ValidQuestionGroup)
{
}
}
public async Task AddAsync(AssignmentStruct assignment)
{
}
public async Task AddAsync(AssignmentQuestion assignment)
{
}
public async Task AddAsync(Question assignment)
{
}
public async Task AddAsync(AssignmentClass assignment)
{
}
}
}

View File

@@ -1,6 +1,6 @@
using Entities.Contracts;
namespace TechHelper.Server.Repository
namespace TechHelper.Server.Repositories
{
public interface IExamRepository
{
@@ -23,5 +23,16 @@ namespace TechHelper.Server.Repository
/// </summary>
/// <param name="assignment">要添加的试卷实体。</param>
Task AddAsync(Assignment assignment);
Task AddAsync(AssignmentStruct assignment);
Task AddAsync(AssignmentQuestion assignment);
Task AddAsync(Question assignment);
Task AddAsync(AssignmentClass assignment);
}
}

View File

@@ -5,7 +5,7 @@ using TechHelper.Context;
namespace TechHelper.Repository
{
public class AssignmentGroupRepository : Repository<AssignmentGroup>, IRepository<AssignmentGroup>
public class AssignmentGroupRepository : Repository<AssignmentStruct>, IRepository<AssignmentStruct>
{
public AssignmentGroupRepository(ApplicationContext dbContext) : base(dbContext)
{

View File

@@ -10,5 +10,10 @@ namespace TechHelper.Repository
public AssignmentRepository(ApplicationContext dbContext) : base(dbContext)
{
}
public void THISTEST()
{
}
}
}

View File

@@ -1,96 +0,0 @@
using Entities.Contracts;
using Entities.DTO;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Api;
namespace TechHelper.Server.Repository
{
public class ExamRepository : IExamRepository
{
private readonly IUnitOfWork _unitOfWork;
private readonly IRepository<Assignment> _assignmentRepo;
private readonly IRepository<AssignmentGroup> _assignmentGroupRepo;
private readonly IRepository<QuestionGroup> _questionGroupRepo;
private readonly IRepository<Question> _questionRepo;
public ExamRepository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_assignmentRepo = _unitOfWork.GetRepository<Assignment>();
_assignmentGroupRepo = _unitOfWork.GetRepository<AssignmentGroup>();
}
public async Task<Assignment?> GetFullExamByIdAsync(Guid assignmentId)
{
var assignment = await _assignmentRepo.GetFirstOrDefaultAsync(
predicate: a => a.Id == assignmentId && !a.IsDeleted,
include: source => source
.Include
(a => a.AssignmentGroups.Where(ag => ag.ParentGroup == null && !ag.IsDeleted)) // 加载根题组
.ThenInclude(ag => ag.ChildAssignmentGroups.Where(cag => !cag.IsDeleted)) // 加载子题组
.ThenInclude(cag => cag.AssignmentQuestions.Where(aq => !aq.IsDeleted)) // 加载子题组的题目
.ThenInclude(aq => aq.Question)
.Include(a => a.AssignmentGroups.Where(ag => ag.ParentGroup == null && !ag.IsDeleted)) // 再次从根开始,加载题组下的题目
.ThenInclude(ag => ag.AssignmentQuestions.Where(aq => !aq.IsDeleted))
.ThenInclude(aq => aq.Question)
);
if (assignment?.AssignmentGroups != null)
{
foreach (var rootGroup in assignment.AssignmentGroups)
{
await LoadSubGroupsRecursive(rootGroup);
}
}
return assignment;
}
private async Task LoadSubGroupsRecursive(AssignmentGroup group)
{
// EF Core 已经加载了下一层,我们需要确保更深层次的加载
var groupWithChildren = await _assignmentGroupRepo.GetFirstOrDefaultAsync(
predicate: g => g.Id == group.Id,
include: source => source
.Include(g => g.ChildAssignmentGroups.Where(cg => !cg.IsDeleted))
.ThenInclude(cg => cg.AssignmentQuestions.Where(aq => !aq.IsDeleted))
.ThenInclude(aq => aq.Question)
.Include(g => g.AssignmentQuestions.Where(aq => !aq.IsDeleted))
.ThenInclude(aq => aq.Question)
);
group.ChildAssignmentGroups = groupWithChildren.ChildAssignmentGroups;
group.AssignmentQuestions = groupWithChildren.AssignmentQuestions;
if (group.ChildAssignmentGroups != null)
{
foreach (var child in group.ChildAssignmentGroups)
{
await LoadSubGroupsRecursive(child);
}
}
}
public async Task<IEnumerable<Assignment>> GetExamPreviewsByUserAsync(Guid userId)
{
return await _assignmentRepo.GetAllAsync(
predicate: a => a.CreatedBy == userId && !a.IsDeleted);
}
public async Task AddAsync(Assignment assignment)
{
await _assignmentRepo.InsertAsync(assignment);
}
public async Task AddAsync(QuestionGroupDto qg)
{
if(qg.ValidQuestionGroup)
{
}
}
}
}

View File

@@ -0,0 +1,17 @@
using Entities.Contracts;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Api;
using TechHelper.Context;
namespace TechHelper.Repository
{
public class UserRepository : Repository<User>, IRepository<User>
{
public UserRepository(ApplicationContext dbContext) : base(dbContext)
{
}
}
}

View File

@@ -1,47 +0,0 @@
namespace TechHelper.Services
{
public class ApiResponse
{
public string Message { get; set; }
public bool Status { get; set; }
public object? Result { get; set; }
private ApiResponse(bool status, string message, object? result)
{
Status = status;
Message = message;
Result = result;
}
public ApiResponse(string message, bool status = false)
: this(status, message, null) { }
public ApiResponse(bool status, object result)
: this(status, string.Empty, result) { }
/// <summary>
/// 创建一个表示成功响应的 ApiResponse 实例。
/// </summary>
/// <param name="message">成功消息。</param>
/// <param name="result">可选的返回数据。</param>
/// <returns>ApiResponse 实例。</returns>
public static ApiResponse Success(string message = "操作成功。", object? result = null)
{
return new ApiResponse(true, message, result);
}
/// <summary>
/// 创建一个表示失败响应的 ApiResponse 实例。
/// </summary>
/// <param name="message">错误消息。</param>
/// <param name="result">可选的错误详情或数据。</param>
/// <returns>ApiResponse 实例。</returns>
public static ApiResponse Error(string message = "操作失败。", object? result = null)
{
return new ApiResponse(false, message, result);
}
}
}

View File

@@ -197,7 +197,7 @@ namespace TechHelper.Services
{
ClassId = existingClass.Id,
TeacherId = existingClass.Id,
SubjectTaught = user.SubjectArea.ToString()
SubjectTaught = user.SubjectArea
};
await _work.GetRepository<ClassTeacher>().InsertAsync(classTeacher);

View File

@@ -1,56 +1,149 @@
using AutoMapper;
using Entities.Contracts;
using Entities.DTO;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
using Microsoft.VisualBasic;
using SharedDATA.Api;
using TechHelper.Context;
using TechHelper.Server.Repositories;
using TechHelper.Services;
using static TechHelper.Context.AutoMapperProFile;
namespace TechHelper.Server.Services
{
public class ExamService : IExamService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IRepository<Assignment> _assignmentRepo;
private readonly IRepository<AssignmentGroup> _assignmentGroupRepo;
private readonly IRepository<AssignmentQuestion> _assignmentQuestionRepo;
private readonly IRepository<Question> _questionRepo;
public ExamService(IUnitOfWork unitOfWork, IMapper mapper, UserManager<User> userManager)
private readonly IExamRepository _examRepository;
private readonly IMapper _mapper;
public ExamService(IUnitOfWork unitOfWork, IExamRepository examRepository, IMapper mapper)
{
_unitOfWork = unitOfWork;
_examRepository = examRepository;
_mapper = mapper;
_userManager = userManager;
_assignmentRepo = _unitOfWork.GetRepository<Assignment>();
_assignmentGroupRepo = _unitOfWork.GetRepository<AssignmentGroup>();
_assignmentQuestionRepo = _unitOfWork.GetRepository<AssignmentQuestion>();
_questionRepo = _unitOfWork.GetRepository<Question>();
}
private readonly IMapper _mapper;
private readonly UserManager<User> _userManager;
public async Task<ApiResponse> CreateExamAsync(AssignmentDto assignmentDto)
{
public async Task<ApiResponse> AddAsync(ExamDto model)
Assignment newAssi = _mapper.Map<Assignment>(assignmentDto);
await _examRepository.AddAsync(newAssi);
var context = _unitOfWork.GetDbContext<ApplicationContext>();
foreach (var entry in context.ChangeTracker.Entries())
{
try
if (entry.State == Microsoft.EntityFrameworkCore.EntityState.Added)
{
var result = await SaveParsedExam(model);
if (result.Status)
if(entry.Entity is Question newQues)
{
return ApiResponse.Success("保存试题成功");
newQues.CreatorId = newAssi.CreatorId;
}
else
{
return ApiResponse.Error($"保存试题数据失败{result.Message}");
}
}
catch (Exception ex)
{
return ApiResponse.Error($"保存试题数据失败: {ex.Message}");
await _unitOfWork.SaveChangesAsync();
return ApiResponse.Success();
}
private async void ParseStruct(AssignmentStructDto assignmentStruct, Guid ParentID)
{
var newStruct = _mapper.Map<AssignmentStruct>(assignmentStruct);
newStruct.ParentStructId = Guid.Empty == ParentID ? null : ParentID;
await _examRepository.AddAsync(newStruct);
foreach (var item in assignmentStruct.AssignmentQuestions)
{
var newQuestion = _mapper.Map<Question>(item);
//newQuestion.ParentQuestionId = item.ParentQuestion == null ? null : item.ParentQuestion.Id;
await _examRepository.AddAsync(newQuestion);
//await ParseAssignmentQuestion(assignmentStruct, item, newQuestion);
}
foreach (var item in assignmentStruct.ChildrenGroups)
{
ParseStruct(item, assignmentStruct.Id);
}
}
private async Task ParseAssignmentQuestion(AssignmentStructDto assignmentStruct, QuestionDto item, Question newQuestion)
{
AssignmentQuestion newAssignQues = new AssignmentQuestion();
newAssignQues.QuestionId = newQuestion.Id;
newAssignQues.AssignmentStructId = assignmentStruct.Id;
newAssignQues.CreatedAt = DateTime.UtcNow;
newAssignQues.Score = item.Score;
await _examRepository.AddAsync(newAssignQues);
}
private void SetEntityIdsAndRelations(AssignmentStruct group, Guid? assignmentId, Guid creatorId)
{
group.Id = Guid.NewGuid();
group.AssignmentId = assignmentId;
foreach (var aq in group.AssignmentQuestions)
{
aq.Id = Guid.NewGuid();
aq.AssignmentStructId = group.Id;
aq.Question.Id = Guid.NewGuid();
aq.Question.CreatorId = creatorId;
aq.CreatedAt = DateTime.UtcNow;
// ... 其他默认值
}
foreach (var childGroup in group.ChildrenGroups)
{
// 子题组的 AssignmentId 为 null通过 ParentGroup 关联
SetEntityIdsAndRelations(childGroup, null, creatorId);
childGroup.ParentStructId = group.Id;
}
}
public async Task<AssignmentDto> GetExamByIdAsync(Guid id)
{
var assignment = await _examRepository.GetFullExamByIdAsync(id);
if (assignment == null)
{
throw new InvalidOperationException("");
}
return _mapper.Map<AssignmentDto>(assignment);
}
public async Task<IEnumerable<AssignmentDto>> GetAllExamPreviewsAsync(Guid userId)
{
var assignments = await _examRepository.GetExamPreviewsByUserAsync(userId);
return _mapper.Map<IEnumerable<AssignmentDto>>(assignments);
}
public Task<ApiResponse> GetAllAsync(QueryParameter query)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<ApiResponse> AddAsync(AssignmentDto model)
{
throw new NotImplementedException();
}
public Task<ApiResponse> UpdateAsync(AssignmentDto model)
{
throw new NotImplementedException();
}
public Task<ApiResponse> DeleteAsync(Guid id)
@@ -58,392 +151,10 @@ namespace TechHelper.Server.Services
throw new NotImplementedException();
}
public Task<ApiResponse> GetAllAsync(QueryParameter query)
Task<ApiResponse> IExamService.GetAllExamPreviewsAsync(Guid userId)
{
throw new NotImplementedException();
}
public async Task<ApiResponse> GetAsync(Guid id)
{
try
{
var result = await GetExamByIdAsync(id);
return result;
}
catch (Exception ex)
{
return ApiResponse.Error($"获取试题数据失败: {ex.Message}");
}
}
public Task<ApiResponse> UpdateAsync(ExamDto model)
{
throw new NotImplementedException();
}
public async Task<IEnumerable<AssignmentGroup>> LoadFullGroupTree(Guid rootGroupId)
{
var query = @"
WITH RECURSIVE GroupTree AS (
SELECT
ag.*,
CAST(ag.`number` AS CHAR(255)) AS path
FROM assignment_group ag
WHERE ag.id = @rootId
UNION ALL
SELECT
c.*,
CONCAT(ct.path, '.', c.`number`)
FROM assignment_group c
INNER JOIN GroupTree ct ON c.parent_group = ct.id
)
SELECT * FROM GroupTree ORDER BY path;
";
// 执行查询
var groups = await _unitOfWork.GetRepository<AssignmentGroup>()
.FromSql(query, new MySqlParameter("rootId", rootGroupId))
.ToListAsync();
// 内存中构建树结构
var groupDict = groups.ToDictionary(g => g.Id);
var root = groupDict[rootGroupId];
foreach (var group in groups)
{
if (group.ParentGroup != null && groupDict.TryGetValue(group.ParentGroup.Value, out var parent))
{
parent.ChildAssignmentGroups ??= new List<AssignmentGroup>();
parent.ChildAssignmentGroups.Add(group);
}
}
return new List<AssignmentGroup> { root };
}
public async Task LoadRecursiveAssignmentGroups(IEnumerable<AssignmentGroup> groups)
{
foreach (var group in groups.ToList())
{
var loadedGroup = await _unitOfWork.GetRepository<AssignmentGroup>()
.GetFirstOrDefaultAsync(
predicate: ag => ag.Id == group.Id,
include: source => source
.Include(ag => ag.AssignmentQuestions.Where(aq => !aq.IsDeleted))
.ThenInclude(aq => aq.Question)
.Include(ag => ag.ChildAssignmentGroups)
);
if (loadedGroup == null) continue;
group.ChildAssignmentGroups = loadedGroup.ChildAssignmentGroups;
group.AssignmentQuestions = loadedGroup.AssignmentQuestions;
if (group.ChildAssignmentGroups is { Count: > 0 })
{
await LoadRecursiveAssignmentGroups(group.ChildAssignmentGroups);
}
}
}
public async Task<ApiResponse> GetExamByIdAsync(Guid assignmentId)
{
try
{
var assignment = await _unitOfWork.GetRepository<Assignment>().GetFirstOrDefaultAsync(
predicate: a => a.Id == assignmentId && !a.IsDeleted);
if (assignment == null)
{
return ApiResponse.Error($"找不到 ID 为 {assignmentId} 的试卷。");
}
// 获取所有相关题组和题目,并过滤掉已删除的
var allGroups = await _unitOfWork.GetRepository<AssignmentGroup>().GetFirstOrDefaultAsync(
predicate: ag => ag.AssignmentId == assignmentId && !ag.IsDeleted,
include: source => source
.Include(ag => ag.ChildAssignmentGroups)
);
await LoadRecursiveAssignmentGroups(allGroups.ChildAssignmentGroups);
if (allGroups == null || !allGroups.ChildAssignmentGroups.Any())
{
// 试卷存在但没有内容,返回一个空的 ExamDto
return ApiResponse.Success("试卷没有内容。", new ExamDto
{
AssignmentId = assignment.Id,
AssignmentTitle = assignment.Title,
Description = assignment.Description,
SubjectArea = assignment.Submissions.ToString()
});
}
var rootGroups = allGroups.ChildAssignmentGroups.ToList();
var rootqg = new QuestionGroupDto();
foreach (var ag in rootGroups.OrderBy(g => g.Number))
{
var agDto = MapAssignmentGroupToDto(ag);
rootqg.SubQuestionGroups.Add(agDto);
}
// 递归映射到 ExamDto
var examDto = new ExamDto
{
AssignmentId = assignment.Id,
AssignmentTitle = assignment.Title,
Description = assignment.Description,
SubjectArea = assignment.Submissions.ToString(),
QuestionGroups = rootqg
};
return ApiResponse.Success("试卷信息已成功获取。", examDto);
}
catch (Exception ex)
{
return ApiResponse.Error($"获取试卷时发生错误: {ex.Message}", ex);
}
}
public QuestionGroupDto MapAssignmentGroupToDto(AssignmentGroup ag)
{
// 创建当前节点的DTO
var dto = new QuestionGroupDto
{
Title = ag.Title,
Score = (int)(ag.TotalPoints ?? 0),
Descript = ag.Descript,
SubQuestions = ag.AssignmentQuestions?
.OrderBy(aq => aq.QuestionNumber)
.Select(aq => new SubQuestionDto
{
Index = aq.QuestionNumber,
Stem = aq.Question?.QuestionText,
Score = aq.Score ?? 0,
SampleAnswer = aq.Question?.CorrectAnswer,
QuestionType = aq.Question?.QuestionType.ToString(),
DifficultyLevel = aq.Question?.DifficultyLevel.ToString(),
Options = new List<OptionDto>() // 根据需要初始化
}).ToList() ?? new List<SubQuestionDto>(),
SubQuestionGroups = new List<QuestionGroupDto>() // 初始化子集合
};
// 递归处理子组
if (ag.ChildAssignmentGroups != null && ag.ChildAssignmentGroups.Count > 0)
{
foreach (var child in ag.ChildAssignmentGroups.OrderBy(c => c.Number))
{
var childDto = MapAssignmentGroupToDto(child); // 递归获取子DTO
dto.SubQuestionGroups.Add(childDto); // 添加到当前节点的子集合
}
}
return dto;
}
private List<QuestionGroupDto> MapAssignmentGroupsToDto2(
List<AssignmentGroup> currentLevelGroups,
IEnumerable<AssignmentGroup> allFetchedGroups)
{
var dtos = new List<QuestionGroupDto>();
foreach (var group in currentLevelGroups.OrderBy(g => g.Number))
{
var groupDto = new QuestionGroupDto
{
Title = group.Title,
Score = (int)(group.TotalPoints ?? 0),
Descript = group.Descript,
SubQuestions = group.AssignmentQuestions
.OrderBy(aq => aq.QuestionNumber)
.Select(aq => new SubQuestionDto
{
Index = aq.QuestionNumber,
Stem = aq.Question.QuestionText,
Score = aq.Score ?? 0,
SampleAnswer = aq.Question.CorrectAnswer,
QuestionType = aq.Question.QuestionType.ToString(),
DifficultyLevel = aq.Question.DifficultyLevel.ToString(),
Options = new List<OptionDto>()
}).ToList(),
// 递归映射子题组
SubQuestionGroups = MapAssignmentGroupsToDto2(
allFetchedGroups.Where(ag => ag.ParentGroup == group.Id && !ag.IsDeleted).ToList(),
allFetchedGroups)
};
dtos.Add(groupDto);
}
return dtos;
}
public async Task<TechHelper.Services.ApiResponse> SaveParsedExam(ExamDto examData)
{
// 获取当前登录用户
var currentUser = await _userManager.FindByEmailAsync(examData.CreaterEmail);
if (currentUser == null)
{
return ApiResponse.Error("未找到当前登录用户,无法保存试题。");
}
var currentUserId = currentUser.Id;
try
{
Guid assignmentId;
// 创建新的 Assignment 实体
var newAssignment = new Assignment
{
Id = Guid.NewGuid(),
Title = examData.AssignmentTitle,
Description = examData.Description,
SubjectArea = examData.SubjectArea,
CreatedAt = DateTime.UtcNow,
CreatedBy = currentUserId,
IsDeleted = false
};
await _assignmentRepo.InsertAsync(newAssignment);
assignmentId = newAssignment.Id;
// 从 ExamDto.QuestionGroups 获取根题组。
// 确保只有一个根题组,因为您的模型是“试卷只有一个根节点”。
if (examData.QuestionGroups == null)
{
throw new ArgumentException("试卷必须包含且只能包含一个根题组。");
}
await ProcessAndSaveAssignmentGroupsRecursive(
examData.QuestionGroups,
examData.SubjectArea.ToString(),
assignmentId,
null, // 根题组没有父级
currentUserId);
if (await _unitOfWork.SaveChangesAsync() > 0)
{
return ApiResponse.Success("试卷数据已成功保存。", new ExamDto { AssignmentId = assignmentId, AssignmentTitle = examData.AssignmentTitle });
}
else
{
return ApiResponse.Success("没有新的试卷数据需要保存。");
}
}
catch (Exception ex)
{
return ApiResponse.Error($"保存试卷数据失败: {ex.Message}", ex);
}
}
private async Task ProcessAndSaveAssignmentGroupsRecursive(
QuestionGroupDto qgDto,
string subjectarea,
Guid assignmentId,
Guid? parentAssignmentGroupId,
Guid createdById)
{
if (qgDto.ValidQuestionGroup)
{
await SaveQuestionGroup(qgDto);
}
else
{
byte groupNumber = 1;
var newAssignmentGroup = new AssignmentGroup
{
Id = Guid.NewGuid(), // 后端生成 GUID
Title = qgDto.Title,
Descript = qgDto.Descript,
TotalPoints = qgDto.Score,
Number = (byte)qgDto.Index,
ValidQuestionGroup = qgDto.ValidQuestionGroup,
ParentGroup = parentAssignmentGroupId,
AssignmentId = parentAssignmentGroupId == null ? assignmentId : (Guid?)null,
IsDeleted = false
};
await _unitOfWork.GetRepository<AssignmentGroup>().InsertAsync(newAssignmentGroup);
// 处理子题目
uint questionNumber = 1;
foreach (var sqDto in qgDto.SubQuestions.OrderBy(s => s.Index))
{
var newQuestion = _mapper.Map<Question>(sqDto);
newQuestion.Id = Guid.NewGuid();
newQuestion.CreatedBy = createdById;
newQuestion.CreatedAt = DateTime.UtcNow;
newQuestion.UpdatedAt = DateTime.UtcNow;
newQuestion.IsDeleted = false;
newQuestion.SubjectArea = EnumMappingHelpers.ParseEnumSafe(subjectarea, SubjectAreaEnum.Unknown);
await _unitOfWork.GetRepository<Question>().InsertAsync(newQuestion);
var newAssignmentQuestion = new AssignmentQuestion
{
Id = Guid.NewGuid(),
QuestionId = newQuestion.Id,
QuestionNumber = (byte)questionNumber,
AssignmentGroupId = newAssignmentGroup.Id,
Score = sqDto.Score,
IsDeleted = false,
CreatedAt = DateTime.UtcNow
};
await _unitOfWork.GetRepository<AssignmentQuestion>().InsertAsync(newAssignmentQuestion);
questionNumber++;
}
// 递归处理子题组
// 这里需要遍历 SubQuestionGroups并对每个子组进行递归调用
foreach (var subQgDto in qgDto.SubQuestionGroups.OrderBy(s => s.Index))
{
await ProcessAndSaveAssignmentGroupsRecursive(
subQgDto, // 传入当前的子题组 DTO
subjectarea,
assignmentId, // 顶层 AssignmentId 依然传递下去,但子组不会直接使用它
newAssignmentGroup.Id, // 将当前题组的 ID 作为下一层递归的 parentAssignmentGroupId
createdById);
}
}
}
private async Task SaveQuestionGroup(QuestionGroupDto qgDto)
{
}
public async Task<ApiResponse> GetAllExamPreview(Guid user)
{
try
{
var assignments = await _unitOfWork.GetRepository<Assignment>().GetAllAsync(
predicate: a => a.CreatedBy == user && !a.IsDeleted);
if (assignments.Any())
{
var exam = _mapper.Map<IEnumerable<ExamDto>>(assignments);
return ApiResponse.Success(result: exam);
}
return ApiResponse.Error("你还没有创建任何试卷");
}
catch (Exception ex)
{
return ApiResponse.Error($"查询出了一点问题 , 详细信息为: {ex.Message}, 请稍后再试");
}
}
}
}

View File

@@ -1,123 +0,0 @@
using AutoMapper;
using Entities.Contracts;
using Entities.DTO;
using SharedDATA.Api;
using TechHelper.Server.Repository;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public class ExamService2 : IExamService2
{
private readonly IUnitOfWork _unitOfWork;
private readonly IExamRepository _examRepository;
private readonly IMapper _mapper;
public ExamService2(IUnitOfWork unitOfWork, IExamRepository examRepository, IMapper mapper)
{
_unitOfWork = unitOfWork;
_examRepository = examRepository;
_mapper = mapper;
}
public async Task<Guid> CreateExamAsync(ExamDto examDto, Guid creatorId)
{
if (examDto.QuestionGroups == null)
{
throw new ArgumentException("试卷必须包含一个根题组。");
}
// 使用 AutoMapper 将 DTO 映射到实体
var assignment = _mapper.Map<Assignment>(examDto);
// 设置后端生成的属性
assignment.Id = Guid.NewGuid();
assignment.CreatedBy = creatorId;
assignment.CreatedAt = DateTime.UtcNow;
// 递归设置所有子实体的ID和关联关系
SetEntityIdsAndRelations(assignment.AssignmentGroups.First(), assignment.Id, creatorId);
await _examRepository.AddAsync(assignment);
await _unitOfWork.SaveChangesAsync();
return assignment.Id;
}
private void SetEntityIdsAndRelations(AssignmentGroup group, Guid? assignmentId, Guid creatorId)
{
group.Id = Guid.NewGuid();
group.AssignmentId = assignmentId;
foreach (var aq in group.AssignmentQuestions)
{
aq.Id = Guid.NewGuid();
aq.AssignmentGroupId = group.Id;
aq.Question.Id = Guid.NewGuid();
aq.Question.CreatedBy = creatorId;
aq.CreatedAt = DateTime.UtcNow;
// ... 其他默认值
}
foreach (var childGroup in group.ChildAssignmentGroups)
{
// 子题组的 AssignmentId 为 null通过 ParentGroup 关联
SetEntityIdsAndRelations(childGroup, null, creatorId);
childGroup.ParentGroup = group.Id;
}
}
public async Task<ExamDto> GetExamByIdAsync(Guid id)
{
var assignment = await _examRepository.GetFullExamByIdAsync(id);
if (assignment == null)
{
throw new InvalidOperationException("");
}
return _mapper.Map<ExamDto>(assignment);
}
public async Task<IEnumerable<ExamDto>> GetAllExamPreviewsAsync(Guid userId)
{
var assignments = await _examRepository.GetExamPreviewsByUserAsync(userId);
return _mapper.Map<IEnumerable<ExamDto>>(assignments);
}
public async Task AddAsync(QuestionGroupDto qg)
{
if (qg.ValidQuestionGroup)
{
var mapQG = _mapper.Map<QuestionGroup>(qg);
}
}
public Task<ApiResponse> GetAllAsync(QueryParameter query)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<ApiResponse> AddAsync(ExamDto model)
{
throw new NotImplementedException();
}
public Task<ApiResponse> UpdateAsync(ExamDto model)
{
throw new NotImplementedException();
}
public Task<ApiResponse> DeleteAsync(Guid id)
{
throw new NotImplementedException();
}
}
}

View File

@@ -3,7 +3,7 @@ using TechHelper.Services;
namespace TechHelper.Server.Services
{
public interface IAssignmentGroupService : IBaseService<AssignmentGroup, Guid>
public interface IAssignmentGroupService : IBaseService<AssignmentStruct, Guid>
{
}
}

View File

@@ -4,9 +4,23 @@ using TechHelper.Services;
namespace TechHelper.Server.Services
{
public interface IExamService : IBaseService<ExamDto, Guid>
public interface IExamService : IBaseService<AssignmentDto, Guid>
{
Task<ApiResponse> GetAllExamPreview(Guid user);
QuestionGroupDto MapAssignmentGroupToDto(AssignmentGroup ag);
/// <summary>
/// 根据 ID 获取试卷 DTO。
/// </summary>
Task<AssignmentDto> GetExamByIdAsync(Guid id);
/// <summary>
/// 获取指定用户的所有试卷预览。
/// </summary>
Task<ApiResponse> GetAllExamPreviewsAsync(Guid userId);
/// <summary>
/// 创建一个新的试卷。
/// </summary>
/// <returns>创建成功的试卷ID</returns>
Task<ApiResponse> CreateExamAsync(AssignmentDto examDto);
}
}

View File

@@ -1,26 +0,0 @@
using Entities.Contracts;
using Entities.DTO;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public interface IExamService2 : IBaseService<ExamDto, Guid>
{
/// <summary>
/// 根据 ID 获取试卷 DTO。
/// </summary>
Task<ExamDto> GetExamByIdAsync(Guid id);
/// <summary>
/// 获取指定用户的所有试卷预览。
/// </summary>
Task<IEnumerable<ExamDto>> GetAllExamPreviewsAsync(Guid userId);
/// <summary>
/// 创建一个新的试卷。
/// </summary>
/// <returns>创建成功的试卷ID</returns>
Task<Guid> CreateExamAsync(ExamDto examDto, Guid creatorId);
}
}

View File

@@ -1,9 +0,0 @@
using Entities.Contracts;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public interface IQuestionGroupService : IBaseService<QuestionGroup, Guid>
{
}
}

View File

@@ -1,66 +0,0 @@
using AutoMapper;
using Entities.Contracts;
using Entities.DTO;
using SharedDATA.Api;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public class QuestionGroupService : IAssignmentGroupService
{
private readonly IUnitOfWork _work;
// 如果不再需要 AutoMapper 进行实体到 DTO 的映射,可以移除 _mapper 字段
// 但如果 AutoMapper 在其他服务中用于其他映射,或者将来可能需要,可以保留
private readonly IMapper _mapper;
private readonly IExamService _examService;
public QuestionGroupService(IUnitOfWork work, IMapper mapper, IExamService examService)
{
_work = work;
_mapper = mapper;
_examService = examService;
}
public Task<ApiResponse> AddAsync(AssignmentGroup model)
{
throw new NotImplementedException();
}
public Task<ApiResponse> DeleteAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<ApiResponse> GetAllAsync(QueryParameter query)
{
throw new NotImplementedException();
}
public async Task<ApiResponse> GetAsync(Guid id)
{
try
{
var result = await _work.GetRepository<AssignmentGroup>().GetFirstOrDefaultAsync(predicate: ag => ag.Id == id);
QuestionGroupDto qgd = new QuestionGroupDto();
if (result != null)
{
qgd = _examService.MapAssignmentGroupToDto(result);
return ApiResponse.Success(result: qgd);
}
return ApiResponse.Error("没找到问题组");
}
catch (Exception ex)
{
return ApiResponse.Error($"出现了一点问题: {ex.Message}");
}
}
public Task<ApiResponse> UpdateAsync(AssignmentGroup model)
{
throw new NotImplementedException();
}
}
}

View File

@@ -31,12 +31,12 @@ namespace TechHelper.Server.Services
{
// 可以在此处进行业务逻辑校验,例如检查题目是否已存在
var existingQuestion = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
predicate: q => q.QuestionText == model.QuestionText && !q.IsDeleted
predicate: q => q.Title == model.Title && !q.IsDeleted
);
if (existingQuestion != null)
{
return ApiResponse.Error($"题目 '{model.QuestionText}' 已存在,请勿重复添加。");
return ApiResponse.Error($"题目 '{model.Title}' 已存在,请勿重复添加。");
}
// 设置创建时间、创建者等通用属性
@@ -44,8 +44,7 @@ namespace TechHelper.Server.Services
model.CreatedAt = DateTime.UtcNow;
model.UpdatedAt = DateTime.UtcNow;
model.IsDeleted = false;
model.ValidQuestion = true; // 假设新添加的题目默认为有效
// model.CreatedBy = ... // 实际应用中,这里应该从当前用户上下文获取
await _work.GetRepository<Question>().InsertAsync(model);
await _work.SaveChangesAsync();
@@ -90,7 +89,7 @@ namespace TechHelper.Server.Services
try
{
var question = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
predicate: q => q.QuestionText == title && !q.IsDeleted
predicate: q => q.Title == title && !q.IsDeleted
);
if (question == null)
@@ -119,10 +118,10 @@ namespace TechHelper.Server.Services
var distinctTitles = titles.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
var existingQuestions = await _work.GetRepository<Question>().GetAllAsync(
predicate: q => distinctTitles.Contains(q.QuestionText) && !q.IsDeleted
predicate: q => distinctTitles.Contains(q.Title) && !q.IsDeleted
);
var existingQuestionTexts = new HashSet<string>(existingQuestions.Select(q => q.QuestionText), StringComparer.OrdinalIgnoreCase);
var existingQuestionTexts = new HashSet<string>(existingQuestions.Select(q => q.Title), StringComparer.OrdinalIgnoreCase);
var resultDictionary = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
foreach (var title in titles)
@@ -146,7 +145,7 @@ namespace TechHelper.Server.Services
if (!string.IsNullOrWhiteSpace(query.Search))
{
predicate = predicate.And(q => q.QuestionText.Contains(query.Search));
predicate = predicate.And(q => q.Title.Contains(query.Search));
}
Func<IQueryable<Question>, IOrderedQueryable<Question>> orderBy = null;
@@ -214,12 +213,12 @@ namespace TechHelper.Server.Services
// 检查更新后的题目文本是否与现有其他题目重复
var duplicateCheck = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
predicate: q => q.Id != model.Id && q.QuestionText == model.QuestionText && !q.IsDeleted
predicate: q => q.Id != model.Id && q.Title == model.Title && !q.IsDeleted
);
if (duplicateCheck != null)
{
return ApiResponse.Error($"题目文本 '{model.QuestionText}' 已被其他题目占用,请修改。");
return ApiResponse.Error($"题目文本 '{model.Title}' 已被其他题目占用,请修改。");
}
// 手动复制属性或使用 AutoMapper (如果保留了 _mapper 字段)

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>

View File

@@ -6,7 +6,7 @@
}
},
"ConnectionStrings": {
"XSDB": "Server=mysql.eazygame.cn;Port=13002;Database=test;User=root;Password=wx1998WX"
"XSDB": "Server=mysql.eazygame.cn;Port=13002;Database=test1;User=root;Password=wx1998WX"
},
"JWTSettings": {
"securityKey": "MxcxQHVYVDQ0U3lqWkIwdjZlSGx4eFp6YnFpUGxodmc5Y3hPZk5vWm9MZEg2Y0I=",