AsiignmentStruct

This commit is contained in:
SpecialX
2025-06-20 18:58:11 +08:00
parent d20c051c51
commit 681c0862b6
32 changed files with 414 additions and 752 deletions

View File

@@ -67,11 +67,11 @@ namespace Entities.Contracts
ComputerScience, // 计算机科学
}
public enum QuestionGroupState : byte
public enum AssignmentStructType : byte
{
Standalone,
Group,
Subquestion
Question,
Struct,
SubQuestion
}
}

View File

@@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Entities.DTO;
namespace Entities.Contracts
{
@@ -53,7 +54,7 @@ namespace Entities.Contracts
[ForeignKey(nameof(CreatorId))]
public User Creator { get; set; }
public ICollection<AssignmentClass> AssignmentClasses { get; set; }
public AssignmentStruct ExamStruct { get; set; }
public AssignmentQuestion ExamStruct { get; set; }
public ICollection<AssignmentAttachment> AssignmentAttachments { get; set; }
public ICollection<Submission> Submissions { get; set; }

View File

@@ -18,23 +18,28 @@ namespace Entities.Contracts
public Guid Id { get; set; }
[Column("question_id")]
public Guid QuestionId { get; set; }
public Guid? QuestionId { get; set; }
[Required]
[Column("group_id")]
[ForeignKey("AssignmentGroup")]
public Guid AssignmentStructId { get; set; }
[Column("assignment")]
[ForeignKey("Assignment")]
public Guid? AssignmentId { get; set; }
[Column("title")]
[MaxLength(1024)]
public string? Title { get; set; }
[Column("description")]
public Guid? QuestionContextId { get; set; }
[Required]
[Column("question_number")]
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;
public AssignmentStructType StructType { get; set; } = AssignmentStructType.Question;
[Column("created_at")]
public DateTime CreatedAt { get; set; }
@@ -42,13 +47,17 @@ namespace Entities.Contracts
[Column("score")]
public float? Score { get; set; }
[Column("deleted")]
public bool IsDeleted { get; set; }
public Question Question { get; set; }
public AssignmentStruct AssignmentStruct { get; set; }
public Question? Question { get; set; }
public Assignment? Assignment { get; set; }
[ForeignKey(nameof(QuestionContextId))]
public QuestionContext? QuestionContext { get; set; }
public ICollection<SubmissionDetail> SubmissionDetails { get; set; }
[ForeignKey(nameof(ParentAssignmentQuestionId))]

View File

@@ -1,61 +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;
using System.Runtime.InteropServices;
namespace Entities.Contracts
{
[Table("assignment_group")]
public class AssignmentStruct
{
[Key]
[Column("id")]
public Guid Id { get; set; }
[Column("assignment")]
[ForeignKey("Assignment")]
public Guid? AssignmentId { get; set; }
[Required]
[Column("title")]
[MaxLength(65535)]
public string Title { get; set; }
[Column("descript")]
[MaxLength(65535)]
public string Description { get; set; }
[Column("layout")]
public Layout Layout { get; set; }
[Column("total_points")]
public float? Score { get; set; }
[Column("index")]
public byte Index { get; set; }
[Column("parent_group")]
public Guid? ParentStructId { get; set; }
[Column("deleted")]
public bool IsDeleted { get; set; } = false;
// Navigation Properties
public Assignment? Assignment { get; set; }
public AssignmentStruct? ParentStruct { get; set;}
public ICollection<AssignmentStruct> ChildrenGroups { get; set; }
public ICollection<AssignmentQuestion> AssignmentQuestions { get; set; }
public AssignmentStruct()
{
Id = Guid.NewGuid();
ChildrenGroups = new HashSet<AssignmentStruct>();
AssignmentQuestions = new HashSet<AssignmentQuestion>();
}
}
}

View File

@@ -24,8 +24,7 @@ namespace Entities.Contracts
[MaxLength(65535)]
public string? Answer { get; set; }
[Column("description")]
public Guid? DescriptionId { get; set; }
[Required]
[Column("type")]
@@ -68,11 +67,6 @@ namespace Entities.Contracts
[ForeignKey(nameof(CreatorId))]
public User Creator { 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))]
@@ -84,7 +78,6 @@ namespace Entities.Contracts
{
Id = Guid.NewGuid();
AssignmentQuestions = new HashSet<AssignmentQuestion>();
ChildrenQuestion = new HashSet<Question>();
}
}

View File

@@ -13,13 +13,13 @@ namespace Entities.Contracts
public string Description { get; set; } = string.Empty;
[InverseProperty(nameof(Question.Description))]
public ICollection<Question> Questions { get; set; } = new List<Question>();
[InverseProperty(nameof(AssignmentQuestion.QuestionContext))]
public ICollection<AssignmentQuestion>? Questions { get; set; } = new List<AssignmentQuestion>();
public QuestionContext()
{
Questions = new HashSet<Question>();
Questions = new HashSet<AssignmentQuestion>();
}
}
}

View File

@@ -9,17 +9,19 @@ namespace Entities.DTO
{
public class AssignmentQuestionDto
{
public float Score { get; set; } = 0;
public Guid Id { get; set; } = Guid.Empty;
public string Title { get; set; } = string.Empty;
public QuestionContextDto? Description { get; set; }
public byte Index { get; set; } = 0;
public QuestionGroupState GroupState { get; set; } = QuestionGroupState.Standalone;
public float Score { get; set; } = 0;
public Layout Layout { get; set; } = Layout.horizontal;
public AssignmentStructType StructType { get; set; } = AssignmentStructType.Question;
public AssignmentQuestionDto? ParentAssignmentQuestion { get; set; }
public ICollection<AssignmentQuestionDto> ChildrenAssignmentQuestion { get; set; } = new List<AssignmentQuestionDto>();
public QuestionDto Question { get; set; }
public QuestionDto? Question { get; set; }
}
}

View File

@@ -8,25 +8,6 @@ using System.Xml.Serialization;
namespace Entities.DTO
{
public class AssignmentStructDto
{
public Guid Id { get; set; } = Guid.Empty;
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public float Score { get; set; } = 0;
public byte Index { get; set; } = 0;
public Layout Layout { get; set; } = Layout.horizontal;
public ICollection<AssignmentQuestionDto> AssignmentQuestions { get; set; } = new List<AssignmentQuestionDto>();
public AssignmentStructDto? ParentStruct { get; set; }
public ICollection<AssignmentStructDto> ChildrenGroups { get; set; } = new List<AssignmentStructDto>();
}
public class AssignmentDto
{
public Guid Id { get; set; } = Guid.Empty;
@@ -41,7 +22,7 @@ namespace Entities.DTO
public DateTime DueDate { get; set; }
public Guid CreatorId { get; set; }
public AssignmentStructDto ExamStruct { get; set; } = new AssignmentStructDto();
public AssignmentQuestionDto ExamStruct { get; set; } = new AssignmentQuestionDto();
}
public class AssignmentClassDto

View File

@@ -15,8 +15,6 @@ namespace Entities.DTO
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;

View File

@@ -26,12 +26,12 @@ namespace TechHelper.Client.Exam
Enum.TryParse<SubjectAreaEnum>(examPaper.SubjectArea, out SubjectArea);
dto.SubjectArea = SubjectArea;
AssignmentStructDto examStruct = new AssignmentStructDto();
AssignmentQuestionDto examStruct = new AssignmentQuestionDto();
foreach (var qg in examPaper.QuestionGroups)
{
examStruct.ChildrenGroups.Add(ParseMajorQuestionGroup(qg));
examStruct.ChildrenGroups.Last().Index = (byte)(examStruct.ChildrenGroups.Count());
examStruct.ChildrenAssignmentQuestion.Add(ParseMajorQuestionGroup(qg));
examStruct.ChildrenAssignmentQuestion.Last().Index = (byte)(examStruct.ChildrenAssignmentQuestion.Count());
}
dto.ExamStruct = examStruct;
@@ -39,28 +39,20 @@ namespace TechHelper.Client.Exam
return dto;
}
private static AssignmentStructDto ParseMajorQuestionGroup(MajorQuestionGroup sqg)
private static AssignmentQuestionDto ParseMajorQuestionGroup(MajorQuestionGroup sqg)
{
var examStruct = new AssignmentStructDto();
var examStruct = new AssignmentQuestionDto();
examStruct.Title = sqg.Title;
examStruct.Score = sqg.Score;
if (sqg.SubQuestionGroups != null)
{
examStruct.Title = sqg.Title;
examStruct.Score = sqg.Score;
examStruct.ChildrenGroups = new List<AssignmentStructDto>();
examStruct.ChildrenAssignmentQuestion = new List<AssignmentQuestionDto>();
sqg.SubQuestionGroups?.ForEach(ssqg =>
{
if (string.IsNullOrEmpty(ssqg.Descript))
{
examStruct.ChildrenGroups.Add(ParseMajorQuestionGroup(ssqg));
examStruct.ChildrenGroups.Last().Index = (byte)(examStruct.ChildrenGroups.Count());
}
else
{
examStruct.AssignmentQuestions.Add(ParseGroupToAssignmentQuestion(ssqg, false));
examStruct.AssignmentQuestions.Last().Index = (byte)(examStruct.AssignmentQuestions.Count());
}
examStruct.ChildrenAssignmentQuestion.Add(ParseMajorQuestionGroup(ssqg));
examStruct.ChildrenAssignmentQuestion.Last().Index = (byte)(examStruct.ChildrenAssignmentQuestion.Count());
});
@@ -72,12 +64,8 @@ namespace TechHelper.Client.Exam
sqg.SubQuestions?.ForEach(sq =>
{
if(sq.SubQuestions.Any())
{
}
examStruct.AssignmentQuestions.Add(ParseAssignmentQuestion(sq));
examStruct.AssignmentQuestions.Last().Index = (byte)(examStruct.AssignmentQuestions.Count());
examStruct.ChildrenAssignmentQuestion.Add(ParseAssignmentQuestion(sq));
examStruct.ChildrenAssignmentQuestion.Last().Index = (byte)(examStruct.ChildrenAssignmentQuestion.Count());
});
}
@@ -91,47 +79,6 @@ namespace TechHelper.Client.Exam
.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)
{
@@ -143,8 +90,8 @@ namespace TechHelper.Client.Exam
sq.SubQuestions?.ForEach(ssq =>
{
aq.Question.ChildrenQuestion.Add(ParseQuestion(ssq));
aq.Question.ChildrenQuestion.Last().Index = (byte)aq.Question.ChildrenQuestion.Count;
aq.ChildrenAssignmentQuestion.Add(ParseAssignmentQuestion(ssq));
aq.ChildrenAssignmentQuestion.Last().Index = (byte)aq.ChildrenAssignmentQuestion.Count;
});
@@ -156,14 +103,6 @@ namespace TechHelper.Client.Exam
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;
}

View File

@@ -6,14 +6,14 @@ using System.Text.RegularExpressions;
namespace TechHelper.Client.Exam
{
// --- 新增错误处理相关类 ---
public class ParseError
{
public ParseErrorType Type { get; }
public string Message { get; }
public int? Index { get; } // 错误发生的文本索引或匹配项索引
public string MatchedText { get; } // 如果与某个匹配项相关,记录其文本
public Exception InnerException { get; } // 捕获到的原始异常
public int? Index { get; }
public string MatchedText { get; }
public Exception InnerException { get; }
public ParseError(ParseErrorType type, string message, int? index = null, string matchedText = null, Exception innerException = null)
{

View File

@@ -0,0 +1,17 @@
@using Entities.DTO
@using TechHelper.Client.Exam
<MudPaper>
<MudText>@AssignmentQuestion.Id</MudText>
<MudTextField @bind-Value="AssignmentQuestion.Title" Label="Title" Variant="Variant.Outlined" Margin="Margin.Dense" AutoFocus="true" />
<MudTextField @bind-Value="AssignmentQuestion.Index" Label="Index" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" />
<MudTextField @bind-Value="AssignmentQuestion.Score" Label="Score" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoFocus="true" />
<QuestionEdit Question="AssignmentQuestion.Question"/>
</MudPaper>
@code {
[Parameter]
public AssignmentQuestionDto AssignmentQuestion { get; set; } = new AssignmentQuestionDto();
}

View File

@@ -9,20 +9,32 @@
@using System.Globalization;
@using TechHelper.Client.Pages.Editor
<MudPaper Elevation="5" Class="d-flex overflow-hidden flex-grow-1" Style="overflow:hidden; position:relative;height:100%">
<MudDrawerContainer Class="mud-height-full flex-grow-1" Style="height:100%">
<MudDrawer @bind-Open="@_open" Elevation="0" Variant="@DrawerVariant.Persistent" Color="Color.Primary" Anchor="Anchor.End" OverlayAutoClose="true">
<MudDrawerHeader>
<MudText Typo="Typo.h6"> 配置 </MudText>
</MudDrawerHeader>
<MudStack Class="overflow-auto">
<ParseRoleConfig />
<MudButton Color="Color.Success"> ParseExam </MudButton>
</MudStack>
@if (_edit)
{
<AssignmentQuestionEdit AssignmentQuestion="selectedAssignmentQuestion" />
}
else
{
<MudDrawerHeader>
<MudText Typo="Typo.h6"> 配置 </MudText>
</MudDrawerHeader>
<MudStack Class="overflow-auto">
<ParseRoleConfig />
<MudButton Color="Color.Success"> ParseExam </MudButton>
</MudStack>
}
</MudDrawer>
<MudStack Row="true" Class="flex-grow-1" Style="height:100%">
<ExamView Class="overflow-auto" ParsedExam="ExamContent"></ExamView>
<ExamView Class="overflow-auto" ClickedStruct="HandleClickedStruct" ParsedExam="ExamContent"></ExamView>
<MudPaper Class="ma-2">
<MudPaper Elevation="0" Style="height:calc(100% - 80px)">
@@ -75,11 +87,15 @@
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
private AssignmentQuestionDto selectedAssignmentQuestion = new AssignmentQuestionDto();
private bool _open = false;
private bool _edit = false;
private void ToggleDrawer()
{
_open = !_open;
_edit = false;
}
private BlazoredTextEditor _textEditor = new BlazoredTextEditor();
private ExamPaper _parsedExam = new ExamPaper();
@@ -87,6 +103,14 @@
private ExamParserConfig _examParserConfig { get; set; } = new ExamParserConfig();
private void HandleClickedStruct(AssignmentQuestionDto dto)
{
_open = true;
_edit = true;
selectedAssignmentQuestion = dto;
StateHasChanged();
}
private async Task ParseExam()
{

View File

@@ -1,36 +0,0 @@
@using Entities.DTO
@using TechHelper.Client.Exam
<MudPaper Elevation=@Elevation Class=@Class>
<MudStack Row="true">
<MudText Typo="Typo.h6">@ExamStruct.Title</MudText>
@if (ExamStruct.Score > 0)
{
<MudText Typo="Typo.body2"><b>总分:</b> @ExamStruct.Score 分</MudText>
}
</MudStack>
@foreach (var childStruct in ExamStruct.ChildrenGroups)
{
<ExamGroupView ExamStruct="childStruct"/>
}
@foreach (var question in ExamStruct.AssignmentQuestions)
{
<QuestionCard Question="question.Question" Elevation=@Elevation Class="my-2 pa-1" />
}
</MudPaper>
@code {
[Parameter]
public AssignmentStructDto ExamStruct { get; set; } = new AssignmentStructDto();
[Parameter]
public string Class { get; set; } = "my-2 pa-1";
[Parameter]
public int Elevation { get; set; } = 0;
}

View File

@@ -0,0 +1,51 @@
@using Entities.DTO
@using TechHelper.Client.Exam
<MudPaper @onclick:stopPropagation>
<MudPaper Elevation=@Elevation Class=@Class @onclick="HandleClick">
<MudStack Row="true">
<MudText Typo="Typo.h6">@ExamStruct.Title</MudText>
@if (ExamStruct.Score > 0)
{
<MudText Typo="Typo.body2"><b>总分:</b> @ExamStruct.Score 分</MudText>
}
</MudStack>
@if (ExamStruct.Question != null)
{
<QuestionCard Question="ExamStruct.Question" Index="ExamStruct.Index" Elevation=@Elevation Class="my-2 pa-1" />
}
@foreach (var examStruct in ExamStruct.ChildrenAssignmentQuestion)
{
<ExamStructView ExamStruct="examStruct" ClickedStruct="HandleChildStructClick" Elevation=@Elevation Class="my-2 pa-1" />
}
</MudPaper>
</MudPaper>
@code {
[Parameter]
public AssignmentQuestionDto ExamStruct { get; set; } = new AssignmentQuestionDto();
[Parameter]
public EventCallback<AssignmentQuestionDto> ClickedStruct { get; set; }
[Parameter]
public string Class { get; set; } = "my-2 pa-1";
[Parameter]
public int Elevation { get; set; } = 0;
private async void HandleClick()
{
await ClickedStruct.InvokeAsync(ExamStruct);
}
private async void HandleChildStructClick(AssignmentQuestionDto clickedChildExamStruct)
{
await ClickedStruct.InvokeAsync(clickedChildExamStruct);
}
}

View File

@@ -8,7 +8,7 @@
<MudText Class="d-flex justify-content-center" Typo="Typo.h6"> @ParsedExam.Title </MudText>
<MudText Typo="Typo.body1"> @ParsedExam.Description </MudText>
<ExamGroupView ExamStruct="@ParsedExam.ExamStruct" Elevation="1" Class="ma-0 pa-2" />
<ExamStructView ExamStruct="@ParsedExam.ExamStruct" Elevation="1" ClickedStruct="HandleClickedStruct" Class="ma-0 pa-2" />
</MudPaper>
}
@@ -25,6 +25,10 @@ else
[Parameter]
public AssignmentDto ParsedExam { get; set; } = new AssignmentDto();
[Parameter]
public EventCallback<AssignmentQuestionDto> ClickedStruct { get; set; }
[Parameter]
public string Height { get; set; } = "100%";
[Parameter]
@@ -33,4 +37,10 @@ else
public string Class { get; set; } = "";
[Parameter]
public string Style { get; set; } = "";
private void HandleClickedStruct(AssignmentQuestionDto dto)
{
ClickedStruct.InvokeAsync(dto);
}
}

View File

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

View File

@@ -0,0 +1,17 @@
@using Entities.DTO
@using TechHelper.Client.Exam
<MudPaper>
<MudText>@Question.Id</MudText>
<MudTextField @bind-Value="Question.Title" Label="Title" Variant="Variant.Outlined" Margin="Margin.Dense" AutoGrow="true" />
<MudTextField @bind-Value="Question.Answer" Label="Answer" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoGrow="true" />
<MudTextField @bind-Value="Question.Options" Label="Options" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="." Margin="Margin.Dense" AutoGrow="true" />
</MudPaper>
@code {
[Parameter]
public QuestionDto Question { get; set; } = new QuestionDto();
}

View File

@@ -13,7 +13,6 @@ namespace TechHelper.Context
public DbSet<AssignmentClass> AssignmentClasses { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<AssignmentQuestion> AssignmentGroups { get; set; }
public DbSet<AssignmentQuestion> AssignmentQuestions { get; set; }
public DbSet<Class> Classes { get; set; }
public DbSet<ClassTeacher> ClassStudents { get; set; }
@@ -21,6 +20,7 @@ namespace TechHelper.Context
public DbSet<Question> Questions { get; set; }
public DbSet<Submission> Submissions { get; set; }
public DbSet<SubmissionDetail> SubmissionDetails { get; set; }
public DbSet<QuestionContext> QuestionContexts { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
@@ -28,7 +28,6 @@ namespace TechHelper.Context
builder.ApplyConfiguration(new RoleConfiguration());
builder.ApplyConfiguration(new AssignmentConfiguration());
builder.ApplyConfiguration(new AssignmentClassConfiguration());
builder.ApplyConfiguration(new AssignmentGroupConfiguration());
builder.ApplyConfiguration(new AssignmentQuestionConfiguration());
builder.ApplyConfiguration(new ClassConfiguration());
builder.ApplyConfiguration(new ClassStudentConfiguration());

View File

@@ -50,10 +50,11 @@ namespace TechHelper.Context
// =============================================================
// ENTITY -> DTO Mappings (用于读取/查询)
// =============================================================
CreateMap<Assignment, AssignmentDto>()
.ForMember(dest => dest.ExamStruct, opt => opt.MapFrom(src => src.ExamStruct));
CreateMap<Assignment, AssignmentDto>();
CreateMap<QuestionContext, QuestionContextDto>().ReverseMap();
CreateMap<AssignmentStruct, AssignmentStructDto>(); // 直接映射,因为成员现在对等了
// 新增!从实体到新的 DTO 的映射
CreateMap<AssignmentQuestion, AssignmentQuestionDto>();
@@ -64,7 +65,6 @@ namespace TechHelper.Context
// DTO -> ENTITY Mappings (用于创建/更新) - 现在变得极其简单!
// =================================================================
CreateMap<AssignmentDto, Assignment>();
CreateMap<AssignmentStructDto, AssignmentStruct>();
// 新增!从新的 DTO 到实体的映射
CreateMap<AssignmentQuestionDto, AssignmentQuestion>();

View File

@@ -1,83 +0,0 @@
using Entities.Contracts;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
namespace TechHelper.Context.Configuration
{
public class AssignmentGroupConfiguration : IEntityTypeConfiguration<AssignmentStruct>
{
public void Configure(EntityTypeBuilder<AssignmentStruct> builder)
{
// 1. 设置表名
// 将此实体映射到数据库中名为 "assignment_detail" 的表。
builder.ToTable("assignment_group");
// 2. 配置主键
// Id 属性作为主键。
builder.HasKey(ag => ag.Id);
// 3. 配置列名、必需性、长度和默认值
// 配置 Id 属性对应的数据库列名为 "id"。
builder.Property(ag => ag.Id)
.HasColumnName("id");
// EF Core 默认 Guid 类型主键由应用程序生成,因此无需 ValueGeneratedOnAdd()。
// 配置 AssignmentId 属性对应的数据库列名为 "assignment",并设置为必需字段。
builder.Property(ag => ag.AssignmentId)
.HasColumnName("assignment");
// 配置 Title 属性对应的数据库列名为 "title",设置为必需字段,并设置最大长度。
builder.Property(ag => ag.Title)
.HasColumnName("title")
.IsRequired()
.HasMaxLength(65535); // 对应 MaxLength(65535)
// 配置 Descript 属性对应的数据库列名为 "descript",并设置最大长度。
builder.Property(ag => ag.Description)
.HasColumnName("descript")
.HasMaxLength(65535); // 对应 MaxLength(65535)
// 配置 TotalPoints 属性对应的数据库列名为 "total_points"。
// TotalPoints 是 decimal? 类型,默认就是可选的,无需 IsRequired(false)。
builder.Property(ag => ag.Score)
.HasColumnName("total_points");
// 配置 Number 属性对应的数据库列名为 "number"。
builder.Property(ag => ag.Index)
.HasColumnName("number")
.IsRequired(); // byte 默认非空,显式 IsRequired 增加可读性。
// 配置 ParentGroup 属性对应的数据库列名为 "sub_group"。
// ParentGroup 是 Guid? 类型,默认就是可选的,无需 IsRequired(false)。
builder.Property(ag => ag.ParentStructId)
.HasColumnName("parent_group")
.IsRequired(false);
// 配置 IsDeleted 属性对应的数据库列名为 "deleted",并设置默认值为 false。
builder.Property(ag => ag.IsDeleted)
.HasColumnName("deleted")
.HasDefaultValue(false); // 适用于软删除策略
// 4. 配置导航属性和外键关系
// 配置 AssignmentGroup 到 Assignment 的多对一关系。
// 一个 AssignmentGroup 记录属于一个 Assignment。
builder.HasOne(ag => ag.Assignment) // 当前 AssignmentGroup 有一个 Assignment
.WithOne(a => a.ExamStruct) // 该 Assignment 可以有多个 AssignmentGroup 记录
.HasForeignKey<AssignmentStruct>(ag => ag.AssignmentId); // 通过 AssignmentId 建立外键
// 配置 AssignmentGroup 到 AssignmentGroup 的自引用关系(父子关系)。
// 一个 AssignmentGroup 可以有一个父 AssignmentGroup (SubAssignmentGroup)。
// 假设父 AssignmentGroup 实体中有一个名为 ChildAssignmentGroups 的集合属性来表示它所包含的所有子组。
builder.HasOne(ag => ag.ParentStruct) // 当前 AssignmentGroup 有一个父 AssignmentGroup
.WithMany(parentAg => parentAg.ChildrenGroups) // 该父 AssignmentGroup 可以有多个子 AssignmentGroup
.HasForeignKey(ag => ag.ParentStructId) // 通过 SubGroup 建立外键
.IsRequired(false) // SubGroup 是可空的 (Guid?),所以这个关系是可选的。
.OnDelete(DeleteBehavior.SetNull); // 当父 AssignmentGroup 被删除时,其子 AssignmentGroup 的 SubGroup 外键将被设置为 NULL。
// 如果你希望父组被删除时子组不能脱离父组(即不允许父组被删除),
// 可以使用 DeleteBehavior.Restrict 或 DeleteBehavior.NoAction。
}
}
}

View File

@@ -38,12 +38,6 @@ namespace TechHelper.Context.Configuration
builder.Property(aq => aq.Score)
.HasColumnName("score");
// 配置 AssignmentGroupId 列
// 该列在数据库中名为 "detail_id"
builder.Property(aq => aq.AssignmentStructId)
.HasColumnName("group_id")
.IsRequired();
builder.Property(aq => aq.IsDeleted)
.HasColumnName("deleted")
.HasDefaultValue(false); // 适用于软删除策略
@@ -60,17 +54,11 @@ namespace TechHelper.Context.Configuration
.HasForeignKey(aq => aq.QuestionId) // 外键是 AssignmentQuestion.QuestionId
.OnDelete(DeleteBehavior.Cascade); // 当 Question 被删除时,相关的 AssignmentQuestion 也级联删除。
// ---
// 配置 AssignmentQuestion 到 AssignmentGroup 的关系 (多对一)
// 一个 AssignmentQuestion 属于一个 AssignmentGroup。
//
// 你的 `AssignmentQuestion` 类现在有了 `public AssignmentGroup AssignmentGroup { get; set; }`
// 这是一个非常好的改进,它与 `AssignmentGroupId` 外键完美匹配。
// 假设 `AssignmentGroup` 实体中有一个名为 `AssignmentQuestions` 的 `ICollection<AssignmentQuestion>` 集合属性。
builder.HasOne(aq => aq.AssignmentStruct) // 当前 AssignmentQuestion 有一个 AssignmentGroup
.WithMany(ag => ag.AssignmentQuestions) // 那个 AssignmentGroup 可以有多个 AssignmentQuestion
.HasForeignKey(aq => aq.AssignmentStructId) // 外键是 AssignmentQuestion.AssignmentGroupId (列名 detail_id)
.OnDelete(DeleteBehavior.Cascade); // 当 AssignmentGroup 被删除时,相关的 AssignmentQuestion 也级联删除。
builder.HasOne(aq => aq.QuestionContext)
.WithMany(qc => qc.Questions)
.HasForeignKey(aq => aq.QuestionContextId)
.OnDelete(DeleteBehavior.SetNull);
// ---
// 配置 AssignmentQuestion 到 SubmissionDetail 的关系 (一对多)

View File

@@ -111,11 +111,6 @@ namespace TechHelper.Context.Configuration
.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

@@ -12,7 +12,7 @@ using TechHelper.Context;
namespace TechHelper.Server.Migrations
{
[DbContext(typeof(ApplicationContext))]
[Migration("20250619070929_init")]
[Migration("20250620104952_init")]
partial class init
{
/// <inheritdoc />
@@ -161,9 +161,9 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("id");
b.Property<Guid>("AssignmentGroupId")
b.Property<Guid?>("AssignmentId")
.HasColumnType("char(36)")
.HasColumnName("group_id");
.HasColumnName("assignment");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)")
@@ -179,7 +179,15 @@ namespace TechHelper.Server.Migrations
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<Guid>("QuestionId")
b.Property<Guid?>("ParentAssignmentQuestionId")
.HasColumnType("char(36)")
.HasColumnName("parent_question_group_id");
b.Property<Guid?>("QuestionContextId")
.HasColumnType("char(36)")
.HasColumnName("description");
b.Property<Guid?>("QuestionId")
.HasColumnType("char(36)")
.HasColumnName("question_id");
@@ -187,58 +195,13 @@ namespace TechHelper.Server.Migrations
.HasColumnType("float")
.HasColumnName("score");
b.HasKey("Id");
b.HasIndex("AssignmentGroupId");
b.HasIndex("QuestionId");
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")
b.Property<byte>("StructType")
.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");
.HasColumnName("group_state");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasMaxLength(1024)
.HasColumnType("varchar(1024)")
.HasColumnName("title");
b.HasKey("Id");
@@ -246,9 +209,13 @@ namespace TechHelper.Server.Migrations
b.HasIndex("AssignmentId")
.IsUnique();
b.HasIndex("ParentGroupId");
b.HasIndex("ParentAssignmentQuestionId");
b.ToTable("assignment_group", (string)null);
b.HasIndex("QuestionContextId");
b.HasIndex("QuestionId");
b.ToTable("assignment_questions", (string)null);
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
@@ -408,6 +375,7 @@ namespace TechHelper.Server.Migrations
b.Property<string>("Question")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext");
b.HasKey("Id");
@@ -445,10 +413,6 @@ namespace TechHelper.Server.Migrations
.HasColumnType("tinyint unsigned")
.HasColumnName("difficulty_level");
b.Property<byte>("GroupState")
.HasColumnType("tinyint unsigned")
.HasColumnName("group_state");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
@@ -467,10 +431,6 @@ namespace TechHelper.Server.Migrations
.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")
@@ -501,14 +461,27 @@ namespace TechHelper.Server.Migrations
b.HasIndex("LessonId");
b.HasIndex("ParentQuestionId");
b.HasIndex("Title")
.HasAnnotation("MySql:IndexPrefixLength", new[] { 20 });
b.ToTable("questions", (string)null);
});
modelBuilder.Entity("Entities.Contracts.QuestionContext", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("QuestionContexts");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>
{
b.Property<Guid>("Id")
@@ -774,19 +747,19 @@ namespace TechHelper.Server.Migrations
b.HasData(
new
{
Id = new Guid("895d8f32-714e-4a14-bd97-8fa262b83172"),
Id = new Guid("577dbfe8-7b77-4ead-9386-678f02dea5f4"),
Name = "Student",
NormalizedName = "STUDENT"
},
new
{
Id = new Guid("d182c396-c656-42da-965a-d93c17a1f74f"),
Id = new Guid("04b04eed-32b9-4eb0-b5f5-a97bb4626718"),
Name = "Teacher",
NormalizedName = "TEACHER"
},
new
{
Id = new Guid("4e65fab9-3315-4474-b92c-bdab5a617e65"),
Id = new Guid("82354e4d-902d-4dd6-9790-6ef50ba9bc11"),
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
});
@@ -942,37 +915,31 @@ namespace TechHelper.Server.Migrations
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.HasOne("Entities.Contracts.AssignmentStruct", "AssignmentGroup")
.WithMany("AssignmentQuestions")
.HasForeignKey("AssignmentGroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Entities.Contracts.Assignment", "Assignment")
.WithOne("ExamStruct")
.HasForeignKey("Entities.Contracts.AssignmentQuestion", "AssignmentId");
b.HasOne("Entities.Contracts.AssignmentQuestion", "ParentAssignmentQuestion")
.WithMany("ChildrenAssignmentQuestion")
.HasForeignKey("ParentAssignmentQuestionId");
b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext")
.WithMany("Questions")
.HasForeignKey("QuestionContextId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.Question", "Question")
.WithMany("AssignmentQuestions")
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AssignmentGroup");
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);
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Assignment");
b.Navigation("ParentGroup");
b.Navigation("ParentAssignmentQuestion");
b.Navigation("Question");
b.Navigation("QuestionContext");
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
@@ -1075,18 +1042,11 @@ namespace TechHelper.Server.Migrations
.HasForeignKey("LessonId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.Question", "ParentQuestion")
.WithMany("ChildrenQuestion")
.HasForeignKey("ParentQuestionId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Creator");
b.Navigation("KeyPoint");
b.Navigation("Lesson");
b.Navigation("ParentQuestion");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>
@@ -1207,16 +1167,11 @@ namespace TechHelper.Server.Migrations
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.Navigation("ChildrenAssignmentQuestion");
b.Navigation("SubmissionDetails");
});
modelBuilder.Entity("Entities.Contracts.AssignmentStruct", b =>
{
b.Navigation("AssignmentQuestions");
b.Navigation("ChildrenGroups");
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
{
b.Navigation("AssignmentClasses");
@@ -1243,8 +1198,11 @@ namespace TechHelper.Server.Migrations
modelBuilder.Entity("Entities.Contracts.Question", b =>
{
b.Navigation("AssignmentQuestions");
});
b.Navigation("ChildrenQuestion");
modelBuilder.Entity("Entities.Contracts.QuestionContext", b =>
{
b.Navigation("Questions");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>

View File

@@ -77,6 +77,20 @@ namespace TechHelper.Server.Migrations
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "QuestionContexts",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Description = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_QuestionContexts", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "textbook",
columns: table => new
@@ -327,39 +341,6 @@ namespace TechHelper.Server.Migrations
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "assignment_group",
columns: table => new
{
id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
assignment = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
title = table.Column<string>(type: "longtext", maxLength: 65535, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
descript = table.Column<string>(type: "longtext", maxLength: 65535, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
layout = table.Column<byte>(type: "tinyint unsigned", nullable: false),
total_points = table.Column<float>(type: "float", nullable: true),
number = table.Column<byte>(type: "tinyint unsigned", nullable: false),
parent_group = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false)
},
constraints: table =>
{
table.PrimaryKey("PK_assignment_group", x => x.id);
table.ForeignKey(
name: "FK_assignment_group_assignment_group_parent_group",
column: x => x.parent_group,
principalTable: "assignment_group",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_assignment_group_assignments_assignment",
column: x => x.assignment,
principalTable: "assignments",
principalColumn: "id");
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "submissions",
columns: table => new
@@ -507,7 +488,7 @@ namespace TechHelper.Server.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Question = table.Column<string>(type: "longtext", nullable: false)
Question = table.Column<string>(type: "longtext", maxLength: 65535, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
LessonID = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
@@ -535,12 +516,10 @@ namespace TechHelper.Server.Migrations
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),
@@ -568,12 +547,6 @@ namespace TechHelper.Server.Migrations
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");
@@ -582,9 +555,14 @@ namespace TechHelper.Server.Migrations
columns: table => new
{
id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
question_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
group_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
question_id = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
assignment = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
title = table.Column<string>(type: "varchar(1024)", maxLength: 1024, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
description = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
question_number = table.Column<byte>(type: "tinyint unsigned", nullable: false),
parent_question_group_id = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
group_state = table.Column<byte>(type: "tinyint unsigned", nullable: false),
created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false),
score = table.Column<float>(type: "float", nullable: true),
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false)
@@ -593,11 +571,21 @@ namespace TechHelper.Server.Migrations
{
table.PrimaryKey("PK_assignment_questions", x => x.id);
table.ForeignKey(
name: "FK_assignment_questions_assignment_group_group_id",
column: x => x.group_id,
principalTable: "assignment_group",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
name: "FK_assignment_questions_QuestionContexts_description",
column: x => x.description,
principalTable: "QuestionContexts",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_assignment_questions_assignment_questions_parent_question_gr~",
column: x => x.parent_question_group_id,
principalTable: "assignment_questions",
principalColumn: "id");
table.ForeignKey(
name: "FK_assignment_questions_assignments_assignment",
column: x => x.assignment,
principalTable: "assignments",
principalColumn: "id");
table.ForeignKey(
name: "FK_assignment_questions_questions_question_id",
column: x => x.question_id,
@@ -655,9 +643,9 @@ namespace TechHelper.Server.Migrations
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("4e65fab9-3315-4474-b92c-bdab5a617e65"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("895d8f32-714e-4a14-bd97-8fa262b83172"), null, "Student", "STUDENT" },
{ new Guid("d182c396-c656-42da-965a-d93c17a1f74f"), null, "Teacher", "TEACHER" }
{ new Guid("04b04eed-32b9-4eb0-b5f5-a97bb4626718"), null, "Teacher", "TEACHER" },
{ new Guid("577dbfe8-7b77-4ead-9386-678f02dea5f4"), null, "Student", "STUDENT" },
{ new Guid("82354e4d-902d-4dd6-9790-6ef50ba9bc11"), null, "Administrator", "ADMINISTRATOR" }
});
migrationBuilder.CreateIndex(
@@ -708,20 +696,20 @@ namespace TechHelper.Server.Migrations
column: "class_id");
migrationBuilder.CreateIndex(
name: "IX_assignment_group_assignment",
table: "assignment_group",
name: "IX_assignment_questions_assignment",
table: "assignment_questions",
column: "assignment",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_assignment_group_parent_group",
table: "assignment_group",
column: "parent_group");
name: "IX_assignment_questions_description",
table: "assignment_questions",
column: "description");
migrationBuilder.CreateIndex(
name: "IX_assignment_questions_group_id",
name: "IX_assignment_questions_parent_question_group_id",
table: "assignment_questions",
column: "group_id");
column: "parent_question_group_id");
migrationBuilder.CreateIndex(
name: "IX_assignment_questions_question_id",
@@ -783,11 +771,6 @@ namespace TechHelper.Server.Migrations
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",
@@ -874,7 +857,7 @@ namespace TechHelper.Server.Migrations
name: "submissions");
migrationBuilder.DropTable(
name: "assignment_group");
name: "QuestionContexts");
migrationBuilder.DropTable(
name: "questions");

View File

@@ -158,9 +158,9 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("id");
b.Property<Guid>("AssignmentGroupId")
b.Property<Guid?>("AssignmentId")
.HasColumnType("char(36)")
.HasColumnName("group_id");
.HasColumnName("assignment");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)")
@@ -176,7 +176,15 @@ namespace TechHelper.Server.Migrations
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<Guid>("QuestionId")
b.Property<Guid?>("ParentAssignmentQuestionId")
.HasColumnType("char(36)")
.HasColumnName("parent_question_group_id");
b.Property<Guid?>("QuestionContextId")
.HasColumnType("char(36)")
.HasColumnName("description");
b.Property<Guid?>("QuestionId")
.HasColumnType("char(36)")
.HasColumnName("question_id");
@@ -184,58 +192,13 @@ namespace TechHelper.Server.Migrations
.HasColumnType("float")
.HasColumnName("score");
b.HasKey("Id");
b.HasIndex("AssignmentGroupId");
b.HasIndex("QuestionId");
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")
b.Property<byte>("StructType")
.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");
.HasColumnName("group_state");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext")
.HasMaxLength(1024)
.HasColumnType("varchar(1024)")
.HasColumnName("title");
b.HasKey("Id");
@@ -243,9 +206,13 @@ namespace TechHelper.Server.Migrations
b.HasIndex("AssignmentId")
.IsUnique();
b.HasIndex("ParentGroupId");
b.HasIndex("ParentAssignmentQuestionId");
b.ToTable("assignment_group", (string)null);
b.HasIndex("QuestionContextId");
b.HasIndex("QuestionId");
b.ToTable("assignment_questions", (string)null);
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
@@ -405,6 +372,7 @@ namespace TechHelper.Server.Migrations
b.Property<string>("Question")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("longtext");
b.HasKey("Id");
@@ -442,10 +410,6 @@ namespace TechHelper.Server.Migrations
.HasColumnType("tinyint unsigned")
.HasColumnName("difficulty_level");
b.Property<byte>("GroupState")
.HasColumnType("tinyint unsigned")
.HasColumnName("group_state");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
@@ -464,10 +428,6 @@ namespace TechHelper.Server.Migrations
.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")
@@ -498,14 +458,27 @@ namespace TechHelper.Server.Migrations
b.HasIndex("LessonId");
b.HasIndex("ParentQuestionId");
b.HasIndex("Title")
.HasAnnotation("MySql:IndexPrefixLength", new[] { 20 });
b.ToTable("questions", (string)null);
});
modelBuilder.Entity("Entities.Contracts.QuestionContext", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("QuestionContexts");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>
{
b.Property<Guid>("Id")
@@ -771,19 +744,19 @@ namespace TechHelper.Server.Migrations
b.HasData(
new
{
Id = new Guid("895d8f32-714e-4a14-bd97-8fa262b83172"),
Id = new Guid("577dbfe8-7b77-4ead-9386-678f02dea5f4"),
Name = "Student",
NormalizedName = "STUDENT"
},
new
{
Id = new Guid("d182c396-c656-42da-965a-d93c17a1f74f"),
Id = new Guid("04b04eed-32b9-4eb0-b5f5-a97bb4626718"),
Name = "Teacher",
NormalizedName = "TEACHER"
},
new
{
Id = new Guid("4e65fab9-3315-4474-b92c-bdab5a617e65"),
Id = new Guid("82354e4d-902d-4dd6-9790-6ef50ba9bc11"),
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
});
@@ -939,37 +912,31 @@ namespace TechHelper.Server.Migrations
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.HasOne("Entities.Contracts.AssignmentStruct", "AssignmentGroup")
.WithMany("AssignmentQuestions")
.HasForeignKey("AssignmentGroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Entities.Contracts.Assignment", "Assignment")
.WithOne("ExamStruct")
.HasForeignKey("Entities.Contracts.AssignmentQuestion", "AssignmentId");
b.HasOne("Entities.Contracts.AssignmentQuestion", "ParentAssignmentQuestion")
.WithMany("ChildrenAssignmentQuestion")
.HasForeignKey("ParentAssignmentQuestionId");
b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext")
.WithMany("Questions")
.HasForeignKey("QuestionContextId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.Question", "Question")
.WithMany("AssignmentQuestions")
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AssignmentGroup");
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);
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Assignment");
b.Navigation("ParentGroup");
b.Navigation("ParentAssignmentQuestion");
b.Navigation("Question");
b.Navigation("QuestionContext");
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
@@ -1072,18 +1039,11 @@ namespace TechHelper.Server.Migrations
.HasForeignKey("LessonId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Entities.Contracts.Question", "ParentQuestion")
.WithMany("ChildrenQuestion")
.HasForeignKey("ParentQuestionId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Creator");
b.Navigation("KeyPoint");
b.Navigation("Lesson");
b.Navigation("ParentQuestion");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>
@@ -1204,16 +1164,11 @@ namespace TechHelper.Server.Migrations
modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b =>
{
b.Navigation("ChildrenAssignmentQuestion");
b.Navigation("SubmissionDetails");
});
modelBuilder.Entity("Entities.Contracts.AssignmentStruct", b =>
{
b.Navigation("AssignmentQuestions");
b.Navigation("ChildrenGroups");
});
modelBuilder.Entity("Entities.Contracts.Class", b =>
{
b.Navigation("AssignmentClasses");
@@ -1240,8 +1195,11 @@ namespace TechHelper.Server.Migrations
modelBuilder.Entity("Entities.Contracts.Question", b =>
{
b.Navigation("AssignmentQuestions");
});
b.Navigation("ChildrenQuestion");
modelBuilder.Entity("Entities.Contracts.QuestionContext", b =>
{
b.Navigation("Questions");
});
modelBuilder.Entity("Entities.Contracts.Submission", b =>

View File

@@ -27,12 +27,12 @@ builder.Services.AddDbContext<ApplicationContext>(options =>
).AddUnitOfWork<ApplicationContext>()
.AddCustomRepository<Assignment, AssignmentRepository>()
.AddCustomRepository<AssignmentAttachment, AssignmentAttachmentRepository>()
.AddCustomRepository<AssignmentStruct, AssignmentGroupRepository>()
.AddCustomRepository<AssignmentQuestion, AssignmentQuestionRepository>()
.AddCustomRepository<Class, ClassRepository>()
.AddCustomRepository<ClassStudent, ClassStudentRepository>()
.AddCustomRepository<ClassTeacher, ClassTeacherRepository>()
.AddCustomRepository<Question, QuestionRepository>()
.AddCustomRepository<QuestionContext, QuestionContextRepository>()
.AddCustomRepository<Submission, SubmissionRepository>();
builder.Services.AddAutoMapper(typeof(AutoMapperProFile).Assembly);

View File

@@ -10,14 +10,12 @@ namespace TechHelper.Server.Repositories
{
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)
@@ -27,30 +25,7 @@ namespace TechHelper.Server.Repositories
}
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)
{
@@ -72,11 +47,6 @@ namespace TechHelper.Server.Repositories
}
}
public async Task AddAsync(AssignmentStruct assignment)
{
}
public async Task AddAsync(AssignmentQuestion assignment)
{
}

View File

@@ -26,7 +26,6 @@ namespace TechHelper.Server.Repositories
Task AddAsync(AssignmentStruct assignment);
Task AddAsync(AssignmentQuestion assignment);

View File

@@ -5,10 +5,13 @@ using TechHelper.Context;
namespace TechHelper.Repository
{
public class AssignmentGroupRepository : Repository<AssignmentStruct>, IRepository<AssignmentStruct>
public class QuestionContextRepository : Repository<QuestionContext>, IRepository<QuestionContext>
{
public AssignmentGroupRepository(ApplicationContext dbContext) : base(dbContext)
public QuestionContextRepository(ApplicationContext dbContext) : base(dbContext)
{
}
}
}

View File

@@ -25,87 +25,43 @@ namespace TechHelper.Server.Services
public async Task<ApiResponse> CreateExamAsync(AssignmentDto assignmentDto)
{
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)
Assignment newAssi = _mapper.Map<Assignment>(assignmentDto);
await _examRepository.AddAsync(newAssi);
var context = _unitOfWork.GetDbContext<ApplicationContext>();
foreach (var entry in context.ChangeTracker.Entries())
{
if(entry.Entity is Question newQues)
if (entry.State == Microsoft.EntityFrameworkCore.EntityState.Added)
{
newQues.CreatorId = newAssi.CreatorId;
if (entry.Entity is Question newQues)
{
newQues.CreatorId = newAssi.CreatorId;
}
}
}
if (await _unitOfWork.SaveChangesAsync() > 0)
{
return ApiResponse.Success();
}
return ApiResponse.Error("保存失败");
}
await _unitOfWork.SaveChangesAsync();
return ApiResponse.Success();
}
private async void ParseStruct(AssignmentStructDto assignmentStruct, Guid ParentID)
{
var newStruct = _mapper.Map<AssignmentStruct>(assignmentStruct);
newStruct.ParentStructId = Guid.Empty == ParentID ? null : ParentID;
await _examRepository.AddAsync(newStruct);
foreach (var item in assignmentStruct.AssignmentQuestions)
catch (Exception ex)
{
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);
return ApiResponse.Error(ex.Message);
}
}
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)
{

View File

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