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

View File

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

View File

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

View File

@@ -22,6 +22,6 @@ namespace Entities.Contracts
public User Teacher { get; set; } public User Teacher { get; set; }
[Column("subject_taught")] [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; } public Guid Id { get; set; }
[Required] [Required]
[Column("question_text")] [Column("title")]
[MaxLength(65535)] [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] [Required]
[Column("question_type")] [Column("type")]
[MaxLength(20)] [MaxLength(20)]
public QuestionType QuestionType { get; set; } public QuestionType Type { get; set; } = QuestionType.Unknown;
[Column("correct_answer")]
[MaxLength(65535)]
public string CorrectAnswer { get; set; }
[Column("question_group_id")]
public Guid? QuestionGroupId { get; set; }
[Column("difficulty_level")] [Column("difficulty_level")]
[MaxLength(10)] [MaxLength(10)]
public DifficultyLevel DifficultyLevel { get; set; } public DifficultyLevel DifficultyLevel { get; set; } = DifficultyLevel.easy;
[Column("subject_area")] [Column("subject_area")]
public SubjectAreaEnum SubjectArea { get; set; } public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown;
[Column("options")] [Column("options")]
public string? Options { get; set; } public string? Options { get; set; }
[Column("key_point")]
public Guid? KeyPointId { get; set; }
[Column("lesson")]
public Guid? LessonId { get; set; }
[Required] [Required]
[Column("created_by")] [Column("created_by")]
[ForeignKey("Creator")] public Guid CreatorId { get; set; }
public Guid CreatedBy { get; set; }
[Column("created_at")] [Column("created_at")]
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
@@ -56,54 +63,30 @@ namespace Entities.Contracts
[Column("deleted")] [Column("deleted")]
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }
[Column("valid_question")]
public bool ValidQuestion { get; set; }
// Navigation Properties // Navigation Properties
[ForeignKey(nameof(CreatorId))]
public User Creator { get; set; } 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() public Question()
{ {
Id = Guid.NewGuid(); Id = Guid.NewGuid();
AssignmentQuestions = new HashSet<AssignmentQuestion>(); 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")] [Column("graded_by")]
[ForeignKey("Grader")] [ForeignKey("Grader")]
public Guid? GradedBy { get; set; } public Guid? GraderId { get; set; }
[Column("graded_at")] [Column("graded_at")]
public DateTime? GradedAt { get; set; } public DateTime? GradedAt { get; set; }
@@ -74,6 +74,5 @@ namespace Entities.Contracts
Resubmission, // 待重新提交 (如果允许) Resubmission, // 待重新提交 (如果允许)
Late, // 迟交 Late, // 迟交
Draft, // 草稿 Draft, // 草稿
// ... 添加你需要的其他状态
} }
} }

View File

@@ -23,7 +23,6 @@ namespace Entities.Contracts
[Required] [Required]
[Column("student_id")] [Column("student_id")]
[ForeignKey("User")]
public Guid StudentId { get; set; } public Guid StudentId { get; set; }
[Required] [Required]
@@ -52,8 +51,11 @@ namespace Entities.Contracts
[Column("deleted")] [Column("deleted")]
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }
[ForeignKey(nameof(StudentId))]
public User Student { get; set; }
public Submission Submission { get; set; } public Submission Submission { get; set; }
public User User { get; set; }
public AssignmentQuestion AssignmentQuestion { get; set; } public AssignmentQuestion AssignmentQuestion { get; set; }
public SubmissionDetail() 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -7,6 +8,59 @@ using System.Xml.Serialization;
namespace Entities.DTO 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 class ExamDto
{ {
public Guid? AssignmentId { get; set; } public Guid? AssignmentId { get; set; }
@@ -14,7 +68,7 @@ namespace Entities.DTO
public string AssignmentTitle { get; set; } = string.Empty; public string AssignmentTitle { get; set; } = string.Empty;
public string Description { get; set; } public string Description { get; set; }
public string SubjectArea { get; set; } public string SubjectArea { get; set; }
public QuestionGroupDto QuestionGroups { get; set; } = new QuestionGroupDto(); public QuestionGroupDto ExamStruct { get; set; } = new QuestionGroupDto();
} }
public class QuestionGroupDto public class QuestionGroupDto
@@ -29,7 +83,6 @@ namespace Entities.DTO
public List<SubQuestionDto> SubQuestions { get; set; } = new List<SubQuestionDto>(); public List<SubQuestionDto> SubQuestions { get; set; } = new List<SubQuestionDto>();
public List<QuestionGroupDto> SubQuestionGroups { get; set; } = new List<QuestionGroupDto>(); public List<QuestionGroupDto> SubQuestionGroups { get; set; } = new List<QuestionGroupDto>();
// 标记是否是一个具有上下文的单独问题
public bool ValidQuestionGroup { get; set; } = false; public bool ValidQuestionGroup { get; set; } = false;
} }
@@ -49,7 +102,6 @@ namespace Entities.DTO
public string? QuestionType { get; set; } public string? QuestionType { get; set; }
public string? DifficultyLevel { get; set; } public string? DifficultyLevel { get; set; }
// 标记是否是一个独立的问题
public bool ValidQuestion { get; set; } = false; public bool ValidQuestion { get; set; } = false;
} }
@@ -64,7 +116,7 @@ namespace Entities.DTO
{ {
public static void Convert(this ExamDto examDto) public static void Convert(this ExamDto examDto)
{ {
var qg = examDto.QuestionGroups; var qg = examDto.ExamStruct;
} }

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 Entities.DTO;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.Json; using System.Text.Json;
using Entities.Contracts;
using Microsoft.Extensions.Options;
namespace TechHelper.Client.Exam 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 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.Description = examPaper.Description;
dto.SubjectArea = examPaper.SubjectArea;
dto.QuestionGroups.Title = examPaper.AssignmentTitle; var SubjectArea = SubjectAreaEnum.Literature;
dto.QuestionGroups.Descript = examPaper.Description; Enum.TryParse<SubjectAreaEnum>(examPaper.SubjectArea, out SubjectArea);
dto.SubjectArea = SubjectArea;
AssignmentStructDto examStruct = new AssignmentStructDto();
foreach (var qg in examPaper.QuestionGroups) foreach (var qg in examPaper.QuestionGroups)
{ {
var qgd = new QuestionGroupDto(); examStruct.ChildrenGroups.Add(ParseMajorQuestionGroup(qg));
ParseMajorQuestionGroup(qg, qgd, false); examStruct.ChildrenGroups.Last().Index = (byte)(examStruct.ChildrenGroups.Count());
dto.QuestionGroups.SubQuestionGroups.Add(qgd);
} }
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, if (string.IsNullOrEmpty(ssqg.Descript))
Score = (int)question.Score, {
Descript = "", examStruct.ChildrenGroups.Add(ParseMajorQuestionGroup(ssqg));
}; examStruct.ChildrenGroups.Last().Index = (byte)(examStruct.ChildrenGroups.Count());
qgDto.ValidQuestionGroup = !string.IsNullOrEmpty(qgDto.Descript);
ParseQuestionWithSubQuestions(question, qgDto, qgDto.ValidQuestionGroup);
dto.QuestionGroups.SubQuestionGroups.Add(qgDto);
} }
else 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) private static void ParseMajorQuestionGroup(MajorQuestionGroup qg, QuestionGroupDto qgd, bool isParentGroupValidChain)
@@ -86,12 +192,10 @@ namespace TechHelper.Client.Exam
}); });
} }
// 处理 MajorQuestionGroup 下的 SubQuestions
if (qg.SubQuestions != null) if (qg.SubQuestions != null)
{ {
qg.SubQuestions.ForEach(sq => qg.SubQuestions.ForEach(sq =>
{ {
// 如果 MajorQuestionGroup 下的 Question 包含子问题,则转为 QuestionGroupDto
if (sq.SubQuestions != null && sq.SubQuestions.Any()) if (sq.SubQuestions != null && sq.SubQuestions.Any())
{ {
var subQgd = new QuestionGroupDto var subQgd = new QuestionGroupDto
@@ -101,7 +205,6 @@ namespace TechHelper.Client.Exam
Score = (int)sq.Score, Score = (int)sq.Score,
Descript = "" // 默认为空 Descript = "" // 默认为空
}; };
// 判断当前组是否有效:如果有描述,并且其父级链中没有任何一个组是有效组,则当前组有效
subQgd.ValidQuestionGroup = !string.IsNullOrEmpty(subQgd.Descript) && !nextIsParentGroupValidChain; subQgd.ValidQuestionGroup = !string.IsNullOrEmpty(subQgd.Descript) && !nextIsParentGroupValidChain;
ParseQuestionWithSubQuestions(sq, subQgd, subQgd.ValidQuestionGroup || nextIsParentGroupValidChain); ParseQuestionWithSubQuestions(sq, subQgd, subQgd.ValidQuestionGroup || nextIsParentGroupValidChain);
@@ -121,7 +224,7 @@ namespace TechHelper.Client.Exam
// 解析包含子问题的 Question将其转换为 QuestionGroupDto // 解析包含子问题的 Question将其转换为 QuestionGroupDto
// isParentGroupValidChain 参数表示从顶层到当前组的任一父组是否已经是“有效组” // 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.Title = question.Stem;
qgd.Score = (int)question.Score; qgd.Score = (int)question.Score;
@@ -165,7 +268,7 @@ namespace TechHelper.Client.Exam
} }
// 解析单个 Question (没有子问题) 为 SubQuestionDto // 解析单个 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.Stem = question.Stem;
subQd.Score = (int)question.Score; subQd.Score = (int)question.Score;
@@ -187,7 +290,7 @@ namespace TechHelper.Client.Exam
public static void SeqIndex(this ExamDto dto) 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 Description { get; set; } = "未识别试卷描述";
public string SubjectArea { get; set; } = "试卷类别"; public string SubjectArea { get; set; } = "试卷类别";
public List<MajorQuestionGroup> QuestionGroups { get; set; } = new List<MajorQuestionGroup>(); 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>(); 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 string Descript { get; set; } = string.Empty;
public float Score { get; set; } public float Score { get; set; }
public List<MajorQuestionGroup> SubQuestionGroups { get; set; } = new List<MajorQuestionGroup>(); 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 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 Number { get; set; } = string.Empty;
public string Stem { get; set; } = string.Empty; public string Stem { get; set; } = string.Empty;
public float Score { get; set; } public float Score { get; set; }
public List<Option> Options { get; set; } = new List<Option>(); 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 SampleAnswer { get; set; } = string.Empty;
public string QuestionType { get; set; } = string.Empty; public string QuestionType { get; set; } = string.Empty;
public int Priority { get; set; } public int Priority { get; set; }
@@ -120,17 +121,17 @@ namespace TechHelper.Client.Exam
public ExamParserConfig() public ExamParserConfig()
{ {
MajorQuestionGroupPatterns.Add(new RegexPatternConfig(@"^([一二三四五六七八九十]+)[、\.]\s*(.+?)(?:\s*\(((\d+(?:\.\d+)?))\s*分\))?\s*$", 1)); 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. 这是一个题目" // 模式 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) 这是一个子题目" // 模式 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分)" 或 "① 这是一个更深层次的子题目" // 模式 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)); // 大写字母选项 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."); _config = config ?? throw new ArgumentNullException(nameof(config), "ExamParserConfig cannot be null.");
} }
///
/// 一.基础
/// 1.听写
/// 2.阅读
/// 二.提升
/// 1.阅读
/// (1).选择
/// (2).填空
/// 三.写
/// (一)课文
///
///
///
/// <summary> /// <summary>
/// Builds the ExamPaper structure from raw text and potential matches. /// Builds the ExamPaper structure from raw text and potential matches.
/// Collects and returns parsing errors encountered during the process. /// 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> /// <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="ArgumentException">Thrown if fullExamText is null or empty.</exception>
/// <exception cref="ArgumentNullException">Thrown if allPotentialMatches is null.</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)) if (string.IsNullOrWhiteSpace(fullExamText))
@@ -289,8 +307,8 @@ namespace TechHelper.Client.Exam
var majorQGStack = new Stack<MajorQuestionGroup>(); var majorQGStack = new Stack<MajorQuestionGroup>();
MajorQuestionGroup currentMajorQG = null; MajorQuestionGroup currentMajorQG = null;
var questionStack = new Stack<Question>(); var questionStack = new Stack<PaperQuestion>();
Question currentQuestion = null; PaperQuestion currentQuestion = null;
int currentContentStart = 0; int currentContentStart = 0;
@@ -388,6 +406,7 @@ namespace TechHelper.Client.Exam
Title = pm.RegexMatch.Groups[2].Value.Trim(), // 标题是 Group 2 Title = pm.RegexMatch.Groups[2].Value.Trim(), // 标题是 Group 2
Score = score, Score = score,
Priority = pm.PatternConfig.Priority, Priority = pm.PatternConfig.Priority,
bGroup = true
}; };
if (majorQGStack.Any()) 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(), Number = pm.RegexMatch.Groups[1].Value.Trim(),
Stem = pm.RegexMatch.Groups[2].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. /// Processes the content of a Question, mainly for parsing Options and identifying unstructured text.
/// Logs errors to the provided error list instead of throwing. /// Logs errors to the provided error list instead of throwing.
/// </summary> /// </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."); 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); question.Options.Add(newOption);
lastOptionEndIndex = pm.EndIndex; lastOptionEndIndex = pm.EndIndex;
} }
// TODO: If there are SubQuestion types, they can be processed similarly here. else
// 你可以在此处添加对子问题的处理逻辑,同样需要小心处理其内容和嵌套。 {
question.Stem += contentText;
}
} }
catch (Exception innerEx) catch (Exception innerEx)
{ {
@@ -734,7 +755,7 @@ namespace TechHelper.Client.Exam
// 2. 构建:根据扫描结果和原始文本,线性遍历并构建层级结构 // 2. 构建:根据扫描结果和原始文本,线性遍历并构建层级结构
// BuildExamPaper 现在会返回一个包含错误列表的 ExamPaper 对象 // BuildExamPaper 现在会返回一个包含错误列表的 ExamPaper 对象
// 外部不再需要捕获内部解析异常,只需检查 ExamPaper.Errors 列表 // 外部不再需要捕获内部解析异常,只需检查 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 Title = dto.AssignmentTitle
}; };
GetSeqRecursive(dto.QuestionGroups, null, examStruct.Questions); GetSeqRecursive(dto.ExamStruct, null, examStruct.Questions);
return examStruct; return examStruct;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,10 +5,10 @@
{ {
<MudPaper Height="@Height" Class="@Class" Style="@Style" Width="@Width"> <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> <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> </MudPaper>
} }
@@ -24,7 +24,7 @@ else
@code { @code {
[Parameter] [Parameter]
public ExamDto ParsedExam { get; set; } = new ExamDto(); public AssignmentDto ParsedExam { get; set; } = new AssignmentDto();
[Parameter] [Parameter]
public string Height { get; set; } = "100%"; public string Height { get; set; } = "100%";
[Parameter] [Parameter]

View File

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

View File

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

View File

@@ -3,9 +3,9 @@ using TechHelper.Client.AI;
using TechHelper.Services; using TechHelper.Services;
using Entities.DTO; using Entities.DTO;
using System.Net.Http.Json; // 用于 PostAsJsonAsync 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 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 实例 // 直接使用注入的 _client 实例
var response = await _client.PostAsJsonAsync("exam/add", examDto); var response = await _client.PostAsJsonAsync("exam/add", assiDto);
if (response.IsSuccessStatusCode) // 检查是否是成功的状态码,例如 200 OK, 201 Created 等 if (response.IsSuccessStatusCode) // 检查是否是成功的状态码,例如 200 OK, 201 Created 等
{ {

View File

@@ -1,13 +1,13 @@
using Entities.DTO; using Entities.DTO;
using TechHelper.Services; using TechHelper.Services;
namespace TechHelper.Client.Exam namespace TechHelper.Client.Services
{ {
public interface IExamService public interface IExamService
{ {
public Task<ApiResponse> FormatExam(string examContent); public Task<ApiResponse> FormatExam(string examContent);
public Task<ApiResponse> DividExam(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 Task<ApiResponse> ParseSingleQuestionGroup(string examContent);
public ApiResponse ConvertToXML<T>(string xmlContent); public ApiResponse ConvertToXML<T>(string xmlContent);

View File

@@ -22,6 +22,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Blazor.LocalStorage.WebAssembly" Version="8.0.0" /> <PackageReference Include="Blazor.LocalStorage.WebAssembly" Version="8.0.0" />
<PackageReference Include="Blazored.TextEditor" Version="1.1.3" /> <PackageReference Include="Blazored.TextEditor" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.12" /> <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.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Entities.Contracts; using Entities.Contracts;
using TechHelper.Server.Context.Configuration;
namespace TechHelper.Context namespace TechHelper.Context
{ {
@@ -36,7 +35,6 @@ namespace TechHelper.Context
builder.ApplyConfiguration(new ClassTeacherConfiguration()); builder.ApplyConfiguration(new ClassTeacherConfiguration());
builder.ApplyConfiguration(new QuestionConfiguration()); builder.ApplyConfiguration(new QuestionConfiguration());
builder.ApplyConfiguration(new SubmissionConfiguration()); builder.ApplyConfiguration(new SubmissionConfiguration());
builder.ApplyConfiguration(new QuestionGroupConfiguration());
builder.ApplyConfiguration(new SubmissionDetailConfiguration()); builder.ApplyConfiguration(new SubmissionDetailConfiguration());
} }
} }

View File

@@ -36,60 +36,41 @@ namespace TechHelper.Context
CreateMap<SubQuestionDto, Question>() CreateMap<SubQuestionDto, Question>()
.ForMember(dest => dest.Id, opt => opt.Ignore()) .ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.QuestionText, opt => opt.MapFrom(src => src.Stem)) .ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Stem))
.ForMember(dest => dest.CorrectAnswer, opt => opt.MapFrom(src => src.SampleAnswer)) .ForMember(dest => dest.Answer, opt => opt.MapFrom(src => src.SampleAnswer))
.ForMember(dest => dest.QuestionType, opt => opt.MapFrom(src => EnumMappingHelpers.ParseEnumSafe(src.QuestionType, QuestionType.Unknown))) .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.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.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.CreatedAt, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedAt, opt => opt.Ignore()) .ForMember(dest => dest.UpdatedAt, opt => opt.Ignore())
.ForMember(dest => dest.IsDeleted, 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)) // ENTITY -> DTO Mappings (用于读取/查询)
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description)) // =============================================================
.ForMember(dest => dest.AssignmentId, opt => opt.MapFrom(src => src.Id)) CreateMap<Assignment, AssignmentDto>()
.ForMember(dest => dest.QuestionGroups, opt => opt.MapFrom(src => .ForMember(dest => dest.ExamStruct, opt => opt.MapFrom(src => src.ExamStruct));
src.AssignmentGroups.FirstOrDefault(ag => ag.ParentGroup == null)))
.ForMember(dest => dest.SubjectArea, opt => opt.MapFrom(src => src.SubjectArea.ToString()));
CreateMap<AssignmentGroup, QuestionGroupDto>() CreateMap<AssignmentStruct, AssignmentStructDto>(); // 直接映射,因为成员现在对等了
.ForMember(dest => dest.SubQuestionGroups, opt => opt.MapFrom(src => src.ChildAssignmentGroups))
.ForMember(dest => dest.SubQuestions, opt => opt.MapFrom(src => src.AssignmentQuestions));
CreateMap<AssignmentQuestion, SubQuestionDto>() // 新增!从实体到新的 DTO 的映射
.ForMember(dest => dest.Stem, opt => opt.MapFrom(src => src.Question.QuestionText)) CreateMap<AssignmentQuestion, AssignmentQuestionDto>();
.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()));
CreateMap<Question, QuestionDto>();
// =================================================================
// DTO -> ENTITY Mappings (用于创建/更新) - 现在变得极其简单!
// =================================================================
CreateMap<AssignmentDto, Assignment>();
CreateMap<AssignmentStructDto, AssignmentStruct>();
CreateMap<QuestionGroupDto, AssignmentGroup>() // 新增!从新的 DTO 到实体的映射
.ForMember(dest => dest.ChildAssignmentGroups, opt => opt.MapFrom(src => src.SubQuestionGroups)) CreateMap<AssignmentQuestionDto, AssignmentQuestion>();
.ForMember(dest => dest.AssignmentQuestions, opt => opt.MapFrom(src => src.SubQuestions));
CreateMap<SubQuestionDto, AssignmentQuestion>() CreateMap<QuestionDto, Question>();
.ForMember(dest => dest.Question, opt => opt.MapFrom(src => src)); // 映射到嵌套的 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() .IsRequired()
.HasColumnName("due_date"); .HasColumnName("due_date");
builder.Property(a => a.TotalPoints) builder.Property(a => a.TotalQuestions)
.HasColumnName("total_points"); .HasColumnName("total_points");
builder.Property(a => a.CreatedBy) builder.Property(a => a.CreatorId)
.HasColumnName("created_by"); .HasColumnName("created_by");
builder.Property(a => a.CreatedAt) builder.Property(a => a.CreatedAt)
@@ -54,7 +54,7 @@ namespace TechHelper.Context.Configuration
// 如果 User 有一个名为 AssignmentsCreated 的导航属性,应写为 .WithMany(u => u.AssignmentsCreated) // 如果 User 有一个名为 AssignmentsCreated 的导航属性,应写为 .WithMany(u => u.AssignmentsCreated)
builder.HasOne(a => a.Creator) builder.HasOne(a => a.Creator)
.WithMany() // User 实体没有指向 Assignment 的导航属性 (或我们不知道) .WithMany() // User 实体没有指向 Assignment 的导航属性 (或我们不知道)
.HasForeignKey(a => a.CreatedBy) .HasForeignKey(a => a.CreatorId)
.IsRequired(); // CreatedBy 是必填的,对应 [Required] 在外键属性上 .IsRequired(); // CreatedBy 是必填的,对应 [Required] 在外键属性上
// 关系: Assignment (一) 到 AssignmentClass (多) // 关系: Assignment (一) 到 AssignmentClass (多)

View File

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

View File

@@ -25,12 +25,8 @@ namespace TechHelper.Context.Configuration
.HasColumnName("question_id"); .HasColumnName("question_id");
builder.Property(aq => aq.QuestionGroupId)
.HasColumnName("question_group_id");
// 配置 QuestionNumber 列 // 配置 QuestionNumber 列
builder.Property(aq => aq.QuestionNumber) builder.Property(aq => aq.Index)
.HasColumnName("question_number") .HasColumnName("question_number")
.IsRequired(); // uint 类型默认非空 .IsRequired(); // uint 类型默认非空
@@ -44,15 +40,10 @@ namespace TechHelper.Context.Configuration
// 配置 AssignmentGroupId 列 // 配置 AssignmentGroupId 列
// 该列在数据库中名为 "detail_id" // 该列在数据库中名为 "detail_id"
builder.Property(aq => aq.AssignmentGroupId) builder.Property(aq => aq.AssignmentStructId)
.HasColumnName("group_id") .HasColumnName("group_id")
.IsRequired(); .IsRequired();
builder.Property(aq => aq.IsGroup)
.HasColumnName("is_group") // 修正为一致的列名
.IsRequired(); // IsGroup 应该是必需的
// 配置 IsDeleted 列
builder.Property(aq => aq.IsDeleted) builder.Property(aq => aq.IsDeleted)
.HasColumnName("deleted") .HasColumnName("deleted")
.HasDefaultValue(false); // 适用于软删除策略 .HasDefaultValue(false); // 适用于软删除策略
@@ -69,12 +60,6 @@ namespace TechHelper.Context.Configuration
.HasForeignKey(aq => aq.QuestionId) // 外键是 AssignmentQuestion.QuestionId .HasForeignKey(aq => aq.QuestionId) // 外键是 AssignmentQuestion.QuestionId
.OnDelete(DeleteBehavior.Cascade); // 当 Question 被删除时,相关的 AssignmentQuestion 也级联删除。 .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 的关系 (多对一)
// 一个 AssignmentQuestion 属于一个 AssignmentGroup。 // 一个 AssignmentQuestion 属于一个 AssignmentGroup。
@@ -82,9 +67,9 @@ namespace TechHelper.Context.Configuration
// 你的 `AssignmentQuestion` 类现在有了 `public AssignmentGroup AssignmentGroup { get; set; }` // 你的 `AssignmentQuestion` 类现在有了 `public AssignmentGroup AssignmentGroup { get; set; }`
// 这是一个非常好的改进,它与 `AssignmentGroupId` 外键完美匹配。 // 这是一个非常好的改进,它与 `AssignmentGroupId` 外键完美匹配。
// 假设 `AssignmentGroup` 实体中有一个名为 `AssignmentQuestions` 的 `ICollection<AssignmentQuestion>` 集合属性。 // 假设 `AssignmentGroup` 实体中有一个名为 `AssignmentQuestions` 的 `ICollection<AssignmentQuestion>` 集合属性。
builder.HasOne(aq => aq.AssignmentGroup) // 当前 AssignmentQuestion 有一个 AssignmentGroup builder.HasOne(aq => aq.AssignmentStruct) // 当前 AssignmentQuestion 有一个 AssignmentGroup
.WithMany(ag => ag.AssignmentQuestions) // 那个 AssignmentGroup 可以有多个 AssignmentQuestion .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 也级联删除。 .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. 设置主键 // 2. 设置主键
builder.HasKey(q => q.Id); builder.HasKey(q => q.Id);
builder.HasIndex(q => q.QuestionText); builder.HasIndex(q => q.Title)
.HasPrefixLength(20);
// 3. 配置列名、必需性、长度及其他属性 // 3. 配置列名、必需性、长度及其他属性
@@ -23,44 +24,37 @@ namespace TechHelper.Context.Configuration
.HasColumnName("id"); .HasColumnName("id");
// 对于 Guid 类型的主键EF Core 默认由应用程序生成值,无需 ValueGeneratedOnAdd() // 对于 Guid 类型的主键EF Core 默认由应用程序生成值,无需 ValueGeneratedOnAdd()
builder.Property(q => q.QuestionGroupId)
.HasColumnName("question_group_id")
.IsRequired(false); // 可为空,因为题目不一定属于某个题组
// QuestionText // QuestionText
builder.Property(q => q.QuestionText) builder.Property(q => q.Title)
.HasColumnName("question_text") .HasColumnName("question_text")
.IsRequired() .IsRequired()
.HasMaxLength(65535); // 对应 MaxLength(65535) .HasMaxLength(65535); // 对应 MaxLength(65535)
// QuestionType (枚举作为字符串存储) // QuestionType (枚举作为字符串存储)
builder.Property(q => q.QuestionType) builder.Property(q => q.Type)
.HasColumnName("question_type") .HasColumnName("question_type")
.IsRequired() .IsRequired()
.HasConversion<string>() // <-- 重要:将枚举存储为字符串 .HasMaxLength(20);
.HasMaxLength(20); // <-- 应用最大长度
// CorrectAnswer // CorrectAnswer
builder.Property(q => q.CorrectAnswer) builder.Property(q => q.Answer)
.HasColumnName("correct_answer") .HasColumnName("correct_answer")
.HasMaxLength(65535); // 对应 MaxLength(65535) .HasMaxLength(65535);
// DifficultyLevel (枚举作为字符串存储) // DifficultyLevel (枚举作为字符串存储)
builder.Property(q => q.DifficultyLevel) builder.Property(q => q.DifficultyLevel)
.HasColumnName("difficulty_level") .HasColumnName("difficulty_level")
.HasConversion<string>() // <-- 重要:将枚举存储为字符串 .HasMaxLength(10);
.HasMaxLength(10); // <-- 应用最大长度
// DifficultyLevel 属性没有 [Required],所以默认是可选的
// SubjectArea // SubjectArea
builder.Property(q => q.SubjectArea) builder.Property(q => q.SubjectArea)
.HasColumnName("subject_area") .HasColumnName("subject_area")
.HasConversion<string>() // <--- 重要:将枚举存储为字符串 .HasMaxLength(100);
.HasMaxLength(100); // <--- 应用最大长度 (从原 StringLength 继承)
// CreatedBy // CreatedBy
builder.Property(q => q.CreatedBy) builder.Property(q => q.CreatorId)
.HasColumnName("created_by") .HasColumnName("created_by")
.IsRequired(); .IsRequired();
@@ -91,7 +85,7 @@ namespace TechHelper.Context.Configuration
// 假设 `User` 实体中有一个名为 `CreatedQuestions` 的 `ICollection<Question>` 集合属性。 // 假设 `User` 实体中有一个名为 `CreatedQuestions` 的 `ICollection<Question>` 集合属性。
builder.HasOne(q => q.Creator) // 当前 Question 有一个 Creator builder.HasOne(q => q.Creator) // 当前 Question 有一个 Creator
.WithMany(u => u.CreatedQuestions) // 那个 Creator 可以创建多个 Question .WithMany(u => u.CreatedQuestions) // 那个 Creator 可以创建多个 Question
.HasForeignKey(q => q.CreatedBy) // 外键是 Question.CreatedBy .HasForeignKey(q => q.CreatorId) // 外键是 Question.CreatedBy
.OnDelete(DeleteBehavior.Restrict); // 当 User (Creator) 被删除时,如果还有他/她创建的 Question则会阻止删除。 .OnDelete(DeleteBehavior.Restrict); // 当 User (Creator) 被删除时,如果还有他/她创建的 Question则会阻止删除。
// 由于 CreatedBy 是 [Required]Prevent/Restrict 是一个安全的选择。 // 由于 CreatedBy 是 [Required]Prevent/Restrict 是一个安全的选择。
@@ -105,11 +99,24 @@ namespace TechHelper.Context.Configuration
.WithOne(aq => aq.Question); // 每一个 AssignmentQuestion 都有一个 Question .WithOne(aq => aq.Question); // 每一个 AssignmentQuestion 都有一个 Question
// .HasForeignKey(aq => aq.QuestionId); // 外键的配置应在 `AssignmentQuestionConfiguration` 中进行 // .HasForeignKey(aq => aq.QuestionId); // 外键的配置应在 `AssignmentQuestionConfiguration` 中进行
builder.HasOne(q => q.QuestionGroup) // Question 实体中的 QuestionGroup 导航属性
.WithMany(qg => qg.Questions) // QuestionGroup 实体中的 Questions 集合 builder.HasOne(q => q.KeyPoint)
.HasForeignKey(q => q.QuestionGroupId) // Question 实体中的 QuestionGroupId 外键 .WithMany(kp => kp.Questions)
.IsRequired(false) // QuestionGroupId 在 Question 实体中是可空的 .HasForeignKey(q => q.KeyPointId)
.OnDelete(DeleteBehavior.SetNull); // 如果 QuestionGroup 被删除,关联的 Question 的外键设置为 NULL .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") .HasColumnName("student_id")
.IsRequired(); .IsRequired();
// AttemptNumber
// 注意:如果 AttemptNumber 应该是一个递增的数字Guid 可能不是最合适的类型。
// 但根据你的定义,这里按 Guid 类型配置。
builder.Property(s => s.AttemptNumber) builder.Property(s => s.AttemptNumber)
.HasColumnName("attempt_number") .HasColumnName("attempt_number")
.IsRequired(); .IsRequired();
// SubmissionTime
builder.Property(s => s.SubmissionTime) builder.Property(s => s.SubmissionTime)
.HasColumnName("submission_time"); // 没有 [Required] 属性,所以可以是可空的 .HasColumnName("submission_time"); // 没有 [Required] 属性,所以可以是可空的
// OverallGrade
builder.Property(s => s.OverallGrade) builder.Property(s => s.OverallGrade)
.HasColumnName("overall_grade") .HasColumnName("overall_grade")
.HasPrecision(5, 2); // 应用精度设置 .HasPrecision(5, 2); // 应用精度设置
@@ -51,59 +46,45 @@ namespace TechHelper.Context.Configuration
.HasColumnName("overall_feedback"); .HasColumnName("overall_feedback");
// GradedBy (现为 Guid? 类型) // GradedBy (现为 Guid? 类型)
builder.Property(s => s.GradedBy) builder.Property(s => s.GraderId)
.HasColumnName("graded_by"); // 作为可空外键,不需要 IsRequired() .HasColumnName("graded_by");
// GradedAt
builder.Property(s => s.GradedAt) builder.Property(s => s.GradedAt)
.HasColumnName("graded_at"); .HasColumnName("graded_at");
// IsDeleted
builder.Property(s => s.IsDeleted) builder.Property(s => s.IsDeleted)
.HasColumnName("deleted") .HasColumnName("deleted")
.HasDefaultValue(false); .HasDefaultValue(false);
// Status (枚举作为字符串存储)
builder.Property(s => s.Status) builder.Property(s => s.Status)
.HasColumnName("status") .HasColumnName("status")
.IsRequired() .IsRequired()
.HasConversion<string>() // <--- 重要:将枚举存储为字符串 .HasMaxLength(15);
.HasMaxLength(15); // <--- 应用最大长度
// 4. 配置导航属性和外键关系
// ---
// 配置 Submission 到 Assignment 的关系 (多对一)
// 一个 Submission 属于一个 Assignment。
builder.HasOne(s => s.Assignment) // 当前 Submission 有一个 Assignment builder.HasOne(s => s.Assignment) // 当前 Submission 有一个 Assignment
.WithMany(a => a.Submissions) // 那个 Assignment 可以有多个 Submission .WithMany(a => a.Submissions) // 那个 Assignment 可以有多个 Submission
.HasForeignKey(s => s.AssignmentId) // 外键是 Submission.AssignmentId .HasForeignKey(s => s.AssignmentId) // 外键是 Submission.AssignmentId
.OnDelete(DeleteBehavior.Cascade); // 当 Assignment 被删除时,相关的 Submission 也级联删除。 .OnDelete(DeleteBehavior.Cascade); // 当 Assignment 被删除时,相关的 Submission 也级联删除。
// ---
// 配置 Submission 到 User (Student) 的关系 (多对一)
// 一个 Submission 由一个 User (Student) 提交。
builder.HasOne(s => s.Student) // 当前 Submission 有一个 Student (User) builder.HasOne(s => s.Student) // 当前 Submission 有一个 Student (User)
.WithMany(u => u.SubmissionsAsStudent) // 那个 User (Student) 可以有多个 Submission .WithMany(u => u.SubmissionsAsStudent) // 那个 User (Student) 可以有多个 Submission
.HasForeignKey(s => s.StudentId) // 外键是 Submission.StudentId .HasForeignKey(s => s.StudentId) // 外键是 Submission.StudentId
.OnDelete(DeleteBehavior.Restrict); // 当 User (Student) 被删除时,如果还有其提交的 Submission则会阻止删除。 .OnDelete(DeleteBehavior.Restrict); // 当 User (Student) 被删除时,如果还有其提交的 Submission则会阻止删除。
// ---
// 配置 Submission 到 User (Grader) 的关系 (多对一)
// 一个 Submission 可以由一个 User (Grader) 批改 (可选)。
builder.HasOne(s => s.Grader) // 当前 Submission 有一个 Grader (User),可以是空的 builder.HasOne(s => s.Grader) // 当前 Submission 有一个 Grader (User),可以是空的
.WithMany(u => u.GradedSubmissions) // 那个 User (Grader) 可以批改多个 Submission .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。 .OnDelete(DeleteBehavior.SetNull); // 当 User (Grader) 被删除时,如果 GradedBy 是可空的,则将其设置为 NULL。
// 如果 GradedBy 是不可空的,需要改为 Restrict 或 Cascade。
// ---
// 配置 Submission 到 SubmissionDetail 的关系 (一对多)
// 一个 Submission 可以有多个 SubmissionDetail。
// 这个关系的外键配置通常在 "多" 的一方 (`SubmissionDetail` 实体) 进行。
builder.HasMany(s => s.SubmissionDetails) // 当前 Submission 有多个 SubmissionDetail builder.HasMany(s => s.SubmissionDetails) // 当前 Submission 有多个 SubmissionDetail
.WithOne(sd => sd.Submission); // 每一个 SubmissionDetail 都有一个 Submission .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) public void Configure(EntityTypeBuilder<SubmissionDetail> builder)
{ {
// 1. 设置表名
// 将此实体映射到数据库中名为 "submission_details" 的表。
builder.ToTable("submission_details"); builder.ToTable("submission_details");
// 2. 设置主键
// 将 Id 属性设置为主键。
builder.HasKey(sd => sd.Id); builder.HasKey(sd => sd.Id);
// 3. 配置列名、必需性、精度及其他属性
// Id 属性对应的数据库列名为 "id"。
builder.Property(sd => sd.Id) builder.Property(sd => sd.Id)
.HasColumnName("id"); .HasColumnName("id");
// SubmissionId 属性对应的数据库列名为 "submission_id",并设置为必需字段。
builder.Property(sd => sd.SubmissionId) builder.Property(sd => sd.SubmissionId)
.HasColumnName("submission_id") .HasColumnName("submission_id")
.IsRequired(); .IsRequired();
// StudentId 属性对应的数据库列名为 "student_id",并设置为必需字段。
// 此外键指向 User 实体,代表提交该详情的学生。
builder.Property(sd => sd.StudentId) builder.Property(sd => sd.StudentId)
.HasColumnName("student_id") .HasColumnName("student_id")
.IsRequired(); .IsRequired();
// AssignmentQuestionId 属性对应的数据库列名为 "assignment_question_id",并设置为必需字段。
builder.Property(sd => sd.AssignmentQuestionId) builder.Property(sd => sd.AssignmentQuestionId)
.HasColumnName("assignment_question_id") .HasColumnName("assignment_question_id")
.IsRequired(); .IsRequired();
// StudentAnswer 属性对应的数据库列名为 "student_answer"。
builder.Property(sd => sd.StudentAnswer) builder.Property(sd => sd.StudentAnswer)
.HasColumnName("student_answer"); // string 默认可空 .HasColumnName("student_answer"); // string 默认可空
// IsCorrect 属性对应的数据库列名为 "is_correct"。
builder.Property(sd => sd.IsCorrect) builder.Property(sd => sd.IsCorrect)
.HasColumnName("is_correct"); // bool? 默认可空 .HasColumnName("is_correct"); // bool? 默认可空
// PointsAwarded 属性对应的数据库列名为 "points_awarded",并设置精度。
builder.Property(sd => sd.PointsAwarded) builder.Property(sd => sd.PointsAwarded)
.HasColumnName("points_awarded") .HasColumnName("points_awarded")
.HasPrecision(5, 2); // 应用 [Precision(5, 2)] 设置 .HasPrecision(5, 2); // 应用 [Precision(5, 2)] 设置
// TeacherFeedback 属性对应的数据库列名为 "teacher_feedback"。
builder.Property(sd => sd.TeacherFeedback) builder.Property(sd => sd.TeacherFeedback)
.HasColumnName("teacher_feedback"); // string 默认可空 .HasColumnName("teacher_feedback"); // string 默认可空
// CreatedAt 属性对应的数据库列名为 "created_at",设置为必需字段,并在添加时自动生成值。
builder.Property(sd => sd.CreatedAt) builder.Property(sd => sd.CreatedAt)
.HasColumnName("created_at") .HasColumnName("created_at")
.IsRequired() .IsRequired()
.ValueGeneratedOnAdd(); // 在实体首次保存时自动设置值 .ValueGeneratedOnAdd(); // 在实体首次保存时自动设置值
// UpdatedAt 属性对应的数据库列名为 "updated_at",设置为必需字段,并在添加或更新时自动生成值,同时作为并发令牌。
builder.Property(sd => sd.UpdatedAt) builder.Property(sd => sd.UpdatedAt)
.HasColumnName("updated_at") .HasColumnName("updated_at")
.IsRequired() .IsRequired()
.ValueGeneratedOnAddOrUpdate() // 在实体添加或更新时自动设置值 .ValueGeneratedOnAddOrUpdate() // 在实体添加或更新时自动设置值
.IsConcurrencyToken(); // 用作乐观并发控制,防止同时修改同一记录 .IsConcurrencyToken(); // 用作乐观并发控制,防止同时修改同一记录
// IsDeleted 属性对应的数据库列名为 "deleted",并设置默认值为 false。
builder.Property(sd => sd.IsDeleted) builder.Property(sd => sd.IsDeleted)
.HasColumnName("deleted") .HasColumnName("deleted")
.HasDefaultValue(false); // 常用作软删除标记 .HasDefaultValue(false);
// 4. 配置导航属性和外键关系
// ---
// 配置 SubmissionDetail 到 Submission 的关系 (多对一)
// 一个 SubmissionDetail 记录属于一个 Submission。
builder.HasOne(sd => sd.Submission) // 当前 SubmissionDetail 有一个 Submission builder.HasOne(sd => sd.Submission) // 当前 SubmissionDetail 有一个 Submission
.WithMany(s => s.SubmissionDetails) // 那个 Submission 可以有多个 SubmissionDetail 记录 .WithMany(s => s.SubmissionDetails) // 那个 Submission 可以有多个 SubmissionDetail 记录
.HasForeignKey(sd => sd.SubmissionId) // 外键是 SubmissionDetail.SubmissionId .HasForeignKey(sd => sd.SubmissionId) // 外键是 SubmissionDetail.SubmissionId
.OnDelete(DeleteBehavior.Cascade); // 当 Submission 被删除时,相关的 SubmissionDetail 记录也级联删除。 .OnDelete(DeleteBehavior.Cascade); // 当 Submission 被删除时,相关的 SubmissionDetail 记录也级联删除。
// --- builder.HasOne(sd => sd.Student) // 当前 SubmissionDetail 有一个 User (作为学生)
// 配置 SubmissionDetail 到 User (作为 Student) 的关系 (多对一)
// 一个 SubmissionDetail 记录与一个 User (提交该详情的学生) 相关联。
// 假设 `User` 实体中有一个名为 `SubmissionDetailsAsStudent` 的 `ICollection<SubmissionDetail>` 集合属性。
builder.HasOne(sd => sd.User) // 当前 SubmissionDetail 有一个 User (作为学生)
.WithMany(u => u.SubmissionDetails) .WithMany(u => u.SubmissionDetails)
.HasForeignKey(sd => sd.StudentId) // 外键是 SubmissionDetail.StudentId .HasForeignKey(sd => sd.StudentId) // 外键是 SubmissionDetail.StudentId
.OnDelete(DeleteBehavior.Restrict); // 当 User (学生) 被删除时,如果他/她还有提交详情,则会阻止删除。 .OnDelete(DeleteBehavior.Restrict); // 当 User (学生) 被删除时,如果他/她还有提交详情,则会阻止删除。
// 这是一个更安全的选择,以防止意外数据丢失。 // 这是一个更安全的选择,以防止意外数据丢失。
// ---
// 配置 SubmissionDetail 到 AssignmentQuestion 的关系 (多对一)
// 一个 SubmissionDetail 记录对应一个 AssignmentQuestion。
builder.HasOne(sd => sd.AssignmentQuestion) // 当前 SubmissionDetail 有一个 AssignmentQuestion builder.HasOne(sd => sd.AssignmentQuestion) // 当前 SubmissionDetail 有一个 AssignmentQuestion
.WithMany(aq => aq.SubmissionDetails) // 那个 AssignmentQuestion 可以有多个 SubmissionDetail 记录 .WithMany(aq => aq.SubmissionDetails) // 那个 AssignmentQuestion 可以有多个 SubmissionDetail 记录
.HasForeignKey(sd => sd.AssignmentQuestionId) // 外键是 SubmissionDetail.AssignmentQuestionId .HasForeignKey(sd => sd.AssignmentQuestionId) // 外键是 SubmissionDetail.AssignmentQuestionId

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ using System.Text;
using TechHelper.Features; using TechHelper.Features;
using TechHelper.Services; using TechHelper.Services;
using TechHelper.Server.Services; using TechHelper.Server.Services;
using TechHelper.Server.Repositories;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -26,7 +27,7 @@ builder.Services.AddDbContext<ApplicationContext>(options =>
).AddUnitOfWork<ApplicationContext>() ).AddUnitOfWork<ApplicationContext>()
.AddCustomRepository<Assignment, AssignmentRepository>() .AddCustomRepository<Assignment, AssignmentRepository>()
.AddCustomRepository<AssignmentAttachment, AssignmentAttachmentRepository>() .AddCustomRepository<AssignmentAttachment, AssignmentAttachmentRepository>()
.AddCustomRepository<AssignmentGroup, AssignmentGroupRepository>() .AddCustomRepository<AssignmentStruct, AssignmentGroupRepository>()
.AddCustomRepository<AssignmentQuestion, AssignmentQuestionRepository>() .AddCustomRepository<AssignmentQuestion, AssignmentQuestionRepository>()
.AddCustomRepository<Class, ClassRepository>() .AddCustomRepository<Class, ClassRepository>()
.AddCustomRepository<ClassStudent, ClassStudentRepository>() .AddCustomRepository<ClassStudent, ClassStudentRepository>()
@@ -85,6 +86,7 @@ builder.Services.AddScoped<IEmailSender, QEmailSender>();
builder.Services.AddTransient<IUserRegistrationService, UserRegistrationService>(); builder.Services.AddTransient<IUserRegistrationService, UserRegistrationService>();
builder.Services.AddScoped<IClassService, ClassService>(); builder.Services.AddScoped<IClassService, ClassService>();
builder.Services.AddScoped<IExamService, ExamService>(); builder.Services.AddScoped<IExamService, ExamService>();
builder.Services.AddScoped<IExamRepository, ExamRepository>();
builder.Services.AddEndpointsApiExplorer(); 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; using Entities.Contracts;
namespace TechHelper.Server.Repository namespace TechHelper.Server.Repositories
{ {
public interface IExamRepository public interface IExamRepository
{ {
@@ -23,5 +23,16 @@ namespace TechHelper.Server.Repository
/// </summary> /// </summary>
/// <param name="assignment">要添加的试卷实体。</param> /// <param name="assignment">要添加的试卷实体。</param>
Task AddAsync(Assignment assignment); 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 namespace TechHelper.Repository
{ {
public class AssignmentGroupRepository : Repository<AssignmentGroup>, IRepository<AssignmentGroup> public class AssignmentGroupRepository : Repository<AssignmentStruct>, IRepository<AssignmentStruct>
{ {
public AssignmentGroupRepository(ApplicationContext dbContext) : base(dbContext) public AssignmentGroupRepository(ApplicationContext dbContext) : base(dbContext)
{ {

View File

@@ -10,5 +10,10 @@ namespace TechHelper.Repository
public AssignmentRepository(ApplicationContext dbContext) : base(dbContext) 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, ClassId = existingClass.Id,
TeacherId = existingClass.Id, TeacherId = existingClass.Id,
SubjectTaught = user.SubjectArea.ToString() SubjectTaught = user.SubjectArea
}; };
await _work.GetRepository<ClassTeacher>().InsertAsync(classTeacher); await _work.GetRepository<ClassTeacher>().InsertAsync(classTeacher);

View File

@@ -1,56 +1,149 @@
using AutoMapper; using AutoMapper;
using Entities.Contracts; using Entities.Contracts;
using Entities.DTO; using Entities.DTO;
using Microsoft.AspNetCore.Identity; using Microsoft.VisualBasic;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
using SharedDATA.Api; using SharedDATA.Api;
using TechHelper.Context;
using TechHelper.Server.Repositories;
using TechHelper.Services; using TechHelper.Services;
using static TechHelper.Context.AutoMapperProFile;
namespace TechHelper.Server.Services namespace TechHelper.Server.Services
{ {
public class ExamService : IExamService public class ExamService : IExamService
{ {
private readonly IUnitOfWork _unitOfWork; private readonly IUnitOfWork _unitOfWork;
private readonly IRepository<Assignment> _assignmentRepo; private readonly IExamRepository _examRepository;
private readonly IRepository<AssignmentGroup> _assignmentGroupRepo; private readonly IMapper _mapper;
private readonly IRepository<AssignmentQuestion> _assignmentQuestionRepo;
private readonly IRepository<Question> _questionRepo; public ExamService(IUnitOfWork unitOfWork, IExamRepository examRepository, IMapper mapper)
public ExamService(IUnitOfWork unitOfWork, IMapper mapper, UserManager<User> userManager)
{ {
_unitOfWork = unitOfWork; _unitOfWork = unitOfWork;
_examRepository = examRepository;
_mapper = mapper; _mapper = mapper;
_userManager = userManager;
_assignmentRepo = _unitOfWork.GetRepository<Assignment>();
_assignmentGroupRepo = _unitOfWork.GetRepository<AssignmentGroup>();
_assignmentQuestionRepo = _unitOfWork.GetRepository<AssignmentQuestion>();
_questionRepo = _unitOfWork.GetRepository<Question>();
} }
private readonly IMapper _mapper; public async Task<ApiResponse> CreateExamAsync(AssignmentDto assignmentDto)
private readonly UserManager<User> _userManager; {
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(entry.Entity is Question newQues)
if (result.Status)
{ {
return ApiResponse.Success("保存试题成功"); newQues.CreatorId = newAssi.CreatorId;
} }
else
{
return ApiResponse.Error($"保存试题数据失败{result.Message}");
} }
} }
catch (Exception ex)
{ await _unitOfWork.SaveChangesAsync();
return ApiResponse.Error($"保存试题数据失败: {ex.Message}");
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) public Task<ApiResponse> DeleteAsync(Guid id)
@@ -58,392 +151,10 @@ namespace TechHelper.Server.Services
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<ApiResponse> GetAllAsync(QueryParameter query) Task<ApiResponse> IExamService.GetAllExamPreviewsAsync(Guid userId)
{ {
throw new NotImplementedException(); 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 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 namespace TechHelper.Server.Services
{ {
public interface IExamService : IBaseService<ExamDto, Guid> public interface IExamService : IBaseService<AssignmentDto, Guid>
{ {
Task<ApiResponse> GetAllExamPreview(Guid user); /// <summary>
QuestionGroupDto MapAssignmentGroupToDto(AssignmentGroup ag); /// 根据 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( 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) 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.CreatedAt = DateTime.UtcNow;
model.UpdatedAt = DateTime.UtcNow; model.UpdatedAt = DateTime.UtcNow;
model.IsDeleted = false; model.IsDeleted = false;
model.ValidQuestion = true; // 假设新添加的题目默认为有效
// model.CreatedBy = ... // 实际应用中,这里应该从当前用户上下文获取
await _work.GetRepository<Question>().InsertAsync(model); await _work.GetRepository<Question>().InsertAsync(model);
await _work.SaveChangesAsync(); await _work.SaveChangesAsync();
@@ -90,7 +89,7 @@ namespace TechHelper.Server.Services
try try
{ {
var question = await _work.GetRepository<Question>().GetFirstOrDefaultAsync( var question = await _work.GetRepository<Question>().GetFirstOrDefaultAsync(
predicate: q => q.QuestionText == title && !q.IsDeleted predicate: q => q.Title == title && !q.IsDeleted
); );
if (question == null) if (question == null)
@@ -119,10 +118,10 @@ namespace TechHelper.Server.Services
var distinctTitles = titles.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); var distinctTitles = titles.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
var existingQuestions = await _work.GetRepository<Question>().GetAllAsync( 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); var resultDictionary = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
foreach (var title in titles) foreach (var title in titles)
@@ -146,7 +145,7 @@ namespace TechHelper.Server.Services
if (!string.IsNullOrWhiteSpace(query.Search)) 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; Func<IQueryable<Question>, IOrderedQueryable<Question>> orderBy = null;
@@ -214,12 +213,12 @@ namespace TechHelper.Server.Services
// 检查更新后的题目文本是否与现有其他题目重复 // 检查更新后的题目文本是否与现有其他题目重复
var duplicateCheck = await _work.GetRepository<Question>().GetFirstOrDefaultAsync( 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) if (duplicateCheck != null)
{ {
return ApiResponse.Error($"题目文本 '{model.QuestionText}' 已被其他题目占用,请修改。"); return ApiResponse.Error($"题目文本 '{model.Title}' 已被其他题目占用,请修改。");
} }
// 手动复制属性或使用 AutoMapper (如果保留了 _mapper 字段) // 手动复制属性或使用 AutoMapper (如果保留了 _mapper 字段)

View File

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

View File

@@ -6,7 +6,7 @@
} }
}, },
"ConnectionStrings": { "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": { "JWTSettings": {
"securityKey": "MxcxQHVYVDQ0U3lqWkIwdjZlSGx4eFp6YnFpUGxodmc5Y3hPZk5vWm9MZEg2Y0I=", "securityKey": "MxcxQHVYVDQ0U3lqWkIwdjZlSGx4eFp6YnFpUGxodmc5Y3hPZk5vWm9MZEg2Y0I=",