This commit is contained in:
SpecialX
2025-05-30 12:46:55 +08:00
parent 95daf9471b
commit e824c081bf
35 changed files with 1800 additions and 363 deletions

View File

@@ -19,6 +19,28 @@ namespace TechHelper.Context
CreateMap<ClassDto, Class>()
.ForMember(d => d.Number, o => o.MapFrom(src => src.Class)).ReverseMap();
CreateMap<SubQuestionDto, Question>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.QuestionText, opt => opt.MapFrom(src => src.Stem))
.ForMember(dest => dest.CorrectAnswer, opt => opt.MapFrom(src => src.SampleAnswer))
.ForMember(dest => dest.QuestionType, opt => opt.MapFrom(src => Enum.Parse<QuestionType>(src.QuestionType, true)))
.ForMember(dest => dest.DifficultyLevel, opt => opt.MapFrom(src => Enum.Parse<DifficultyLevel>(src.DifficultyLevel, true)))
.ForMember(dest => dest.SubjectArea, opt => opt.Ignore()) // SubjectArea 来自 Assignment 而不是 SubQuestionDto
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedAt, opt => opt.Ignore())
.ForMember(dest => dest.IsDeleted, opt => opt.Ignore());
// 2. Question -> SubQuestionDto (查看时)
CreateMap<Question, SubQuestionDto>()
.ForMember(dest => dest.Stem, opt => opt.MapFrom(src => src.QuestionText))
.ForMember(dest => dest.Score, opt => opt.Ignore()) // Question 实体没有 Score 字段,需要从 AssignmentQuestion 获取
.ForMember(dest => dest.SampleAnswer, opt => opt.MapFrom(src => src.CorrectAnswer))
.ForMember(dest => dest.QuestionType, opt => opt.MapFrom(src => src.QuestionType.ToString()))
.ForMember(dest => dest.DifficultyLevel, opt => opt.MapFrom(src => src.DifficultyLevel.ToString()))
.ForMember(dest => dest.Options, opt => opt.Ignore()); // Options 需要单独处理
}
}
}

View File

@@ -35,6 +35,9 @@ namespace TechHelper.Context.Configuration
.HasColumnName("created_at")
.IsRequired(); // 通常创建时间字段是非空的
builder.Property(aq => aq.Score)
.HasColumnName("score");
// 配置 AssignmentGroupId 列
// 该列在数据库中名为 "detail_id"
builder.Property(aq => aq.AssignmentGroupId)

View File

@@ -12,7 +12,7 @@ using TechHelper.Context;
namespace TechHelper.Server.Migrations
{
[DbContext(typeof(ApplicationContext))]
[Migration("20250520094348_init")]
[Migration("20250528090233_init")]
partial class init
{
/// <inheritdoc />
@@ -53,14 +53,19 @@ namespace TechHelper.Server.Migrations
.HasColumnType("tinyint(1)")
.HasColumnName("deleted");
b.Property<string>("SubjectArea")
.IsRequired()
.HasColumnType("longtext")
.HasColumnName("subject_area");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("varchar(255)")
.HasColumnName("title");
b.Property<decimal?>("TotalPoints")
.HasColumnType("decimal(65,30)")
b.Property<float?>("TotalPoints")
.HasColumnType("float")
.HasColumnName("total_points");
b.Property<DateTime>("UpdatedAt")
@@ -221,10 +226,14 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("question_id");
b.Property<uint>("QuestionNumber")
.HasColumnType("int unsigned")
b.Property<byte>("QuestionNumber")
.HasColumnType("tinyint unsigned")
.HasColumnName("question_number");
b.Property<float?>("Score")
.HasColumnType("float")
.HasColumnName("score");
b.HasKey("Id");
b.HasIndex("AssignmentGroupId");
@@ -436,9 +445,9 @@ namespace TechHelper.Server.Migrations
.HasColumnType("longtext")
.HasColumnName("overall_feedback");
b.Property<decimal?>("OverallGrade")
b.Property<float?>("OverallGrade")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)")
.HasColumnType("float")
.HasColumnName("overall_grade");
b.Property<string>("Status")
@@ -494,9 +503,9 @@ namespace TechHelper.Server.Migrations
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<decimal?>("PointsAwarded")
b.Property<float?>("PointsAwarded")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)")
.HasColumnType("float")
.HasColumnName("points_awarded");
b.Property<string>("StudentAnswer")
@@ -644,19 +653,19 @@ namespace TechHelper.Server.Migrations
b.HasData(
new
{
Id = new Guid("ab2f54ba-5885-423b-b854-93bc63f8e93e"),
Id = new Guid("ea0c88d8-1a52-4034-bb37-5a95043821eb"),
Name = "Student",
NormalizedName = "STUDENT"
},
new
{
Id = new Guid("d54985cb-1616-48fd-8687-00d9c38c900d"),
Id = new Guid("9de22e41-c096-4d5a-b55a-ce0122aa3ada"),
Name = "Teacher",
NormalizedName = "TEACHER"
},
new
{
Id = new Guid("e6af92bf-1745-458f-b5c6-b51458261aaf"),
Id = new Guid("dee718d9-b731-485f-96bb-a59ce777870f"),
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
});

View File

@@ -207,8 +207,10 @@ namespace TechHelper.Server.Migrations
.Annotation("MySql:CharSet", "utf8mb4"),
description = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
subject_area = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
due_date = table.Column<DateTime>(type: "datetime(6)", nullable: false),
total_points = table.Column<decimal>(type: "decimal(65,30)", nullable: true),
total_points = table.Column<float>(type: "float", nullable: true),
created_by = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false),
updated_at = table.Column<DateTime>(type: "datetime(6)", nullable: false),
@@ -360,7 +362,7 @@ namespace TechHelper.Server.Migrations
student_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
attempt_number = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
submission_time = table.Column<DateTime>(type: "datetime(6)", nullable: false),
overall_grade = table.Column<decimal>(type: "decimal(5,2)", precision: 5, scale: 2, nullable: true),
overall_grade = table.Column<float>(type: "float", precision: 5, scale: 2, nullable: true),
overall_feedback = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
graded_by = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
@@ -480,8 +482,9 @@ namespace TechHelper.Server.Migrations
{
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"),
question_number = table.Column<uint>(type: "int unsigned", nullable: false),
question_number = 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),
detail_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false)
},
@@ -514,7 +517,7 @@ namespace TechHelper.Server.Migrations
student_answer = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
is_correct = table.Column<bool>(type: "tinyint(1)", nullable: true),
points_awarded = table.Column<decimal>(type: "decimal(5,2)", precision: 5, scale: 2, nullable: true),
points_awarded = table.Column<float>(type: "float", precision: 5, scale: 2, nullable: true),
teacher_feedback = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false)
@@ -551,9 +554,9 @@ namespace TechHelper.Server.Migrations
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ new Guid("ab2f54ba-5885-423b-b854-93bc63f8e93e"), null, "Student", "STUDENT" },
{ new Guid("d54985cb-1616-48fd-8687-00d9c38c900d"), null, "Teacher", "TEACHER" },
{ new Guid("e6af92bf-1745-458f-b5c6-b51458261aaf"), null, "Administrator", "ADMINISTRATOR" }
{ new Guid("9de22e41-c096-4d5a-b55a-ce0122aa3ada"), null, "Teacher", "TEACHER" },
{ new Guid("dee718d9-b731-485f-96bb-a59ce777870f"), null, "Administrator", "ADMINISTRATOR" },
{ new Guid("ea0c88d8-1a52-4034-bb37-5a95043821eb"), null, "Student", "STUDENT" }
});
migrationBuilder.CreateIndex(

View File

@@ -50,14 +50,19 @@ namespace TechHelper.Server.Migrations
.HasColumnType("tinyint(1)")
.HasColumnName("deleted");
b.Property<string>("SubjectArea")
.IsRequired()
.HasColumnType("longtext")
.HasColumnName("subject_area");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("varchar(255)")
.HasColumnName("title");
b.Property<decimal?>("TotalPoints")
.HasColumnType("decimal(65,30)")
b.Property<float?>("TotalPoints")
.HasColumnType("float")
.HasColumnName("total_points");
b.Property<DateTime>("UpdatedAt")
@@ -218,10 +223,14 @@ namespace TechHelper.Server.Migrations
.HasColumnType("char(36)")
.HasColumnName("question_id");
b.Property<uint>("QuestionNumber")
.HasColumnType("int unsigned")
b.Property<byte>("QuestionNumber")
.HasColumnType("tinyint unsigned")
.HasColumnName("question_number");
b.Property<float?>("Score")
.HasColumnType("float")
.HasColumnName("score");
b.HasKey("Id");
b.HasIndex("AssignmentGroupId");
@@ -433,9 +442,9 @@ namespace TechHelper.Server.Migrations
.HasColumnType("longtext")
.HasColumnName("overall_feedback");
b.Property<decimal?>("OverallGrade")
b.Property<float?>("OverallGrade")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)")
.HasColumnType("float")
.HasColumnName("overall_grade");
b.Property<string>("Status")
@@ -491,9 +500,9 @@ namespace TechHelper.Server.Migrations
.HasDefaultValue(false)
.HasColumnName("deleted");
b.Property<decimal?>("PointsAwarded")
b.Property<float?>("PointsAwarded")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)")
.HasColumnType("float")
.HasColumnName("points_awarded");
b.Property<string>("StudentAnswer")
@@ -641,19 +650,19 @@ namespace TechHelper.Server.Migrations
b.HasData(
new
{
Id = new Guid("ab2f54ba-5885-423b-b854-93bc63f8e93e"),
Id = new Guid("ea0c88d8-1a52-4034-bb37-5a95043821eb"),
Name = "Student",
NormalizedName = "STUDENT"
},
new
{
Id = new Guid("d54985cb-1616-48fd-8687-00d9c38c900d"),
Id = new Guid("9de22e41-c096-4d5a-b55a-ce0122aa3ada"),
Name = "Teacher",
NormalizedName = "TEACHER"
},
new
{
Id = new Guid("e6af92bf-1745-458f-b5c6-b51458261aaf"),
Id = new Guid("dee718d9-b731-485f-96bb-a59ce777870f"),
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
});

View File

@@ -2,22 +2,46 @@
{
public class ApiResponse
{
public ApiResponse(string message, bool status = false)
public string Message { get; set; }
public bool Status { get; set; }
public object? Result { get; set; }
private ApiResponse(bool status, string message, object? result)
{
this.Message = message;
this.Status = status;
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)
{
this.Status = status;
this.Result = result;
return new ApiResponse(true, message, result);
}
public string Message { get; set; }
public bool Status { get; set; }
public object Result { get; set; }
/// <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

@@ -0,0 +1,306 @@
using AutoMapper;
using Entities.Contracts;
using Entities.DTO;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SharedDATA.Api;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public class ExamService : IExamService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IRepository<Assignment> _assignmentRepo;
private readonly IRepository<AssignmentGroup> _assignmentGroupRepo;
private readonly IRepository<AssignmentQuestion> _assignmentQuestionRepo;
private readonly IRepository<Question> _questionRepo;
public ExamService(IUnitOfWork unitOfWork, IMapper mapper, UserManager<User> userManager)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_userManager = userManager;
_assignmentRepo = _unitOfWork.GetRepository<Assignment>();
_assignmentGroupRepo = _unitOfWork.GetRepository<AssignmentGroup>();
_assignmentQuestionRepo = _unitOfWork.GetRepository<AssignmentQuestion>();
_questionRepo = _unitOfWork.GetRepository<Question>();
}
private readonly IMapper _mapper;
private readonly UserManager<User> _userManager;
public async Task<ApiResponse> AddAsync(ExamDto model)
{
try
{
var result = await SaveParsedExam(model);
if (result.Status)
{
return ApiResponse.Success("保存试题成功");
}
else
{
return ApiResponse.Error($"保存试题数据失败{result.Message}");
}
}
catch (Exception ex)
{
return ApiResponse.Error($"保存试题数据失败: {ex.Message}");
}
}
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 GetExamByIdAsync(id);
return result;
}
catch (Exception ex)
{
return ApiResponse.Error($"获取试题数据失败: {ex.Message}");
}
}
public Task<ApiResponse> UpdateAsync(ExamDto model)
{
throw new NotImplementedException();
}
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>().GetAllAsync(
predicate: ag => ag.AssignmentId == assignmentId && !ag.IsDeleted,
include: source => source
.Include(ag => ag.AssignmentQuestions.Where(aq => !aq.IsDeleted))
.ThenInclude(aq => aq.Question)
);
if (allGroups == null || !allGroups.Any())
{
// 试卷存在但没有内容,返回一个空的 ExamDto
return ApiResponse.Success("试卷没有内容。", new ExamDto
{
AssignmentId = assignment.Id,
AssignmentTitle = assignment.Title,
Description = assignment.Description,
SubjectArea = assignment.Submissions.ToString()
});
}
var rootGroups = allGroups
.Where(ag => ag.ParentGroup == null)
.OrderBy(ag => ag.Number)
.ToList();
// 递归映射到 ExamDto
var examDto = new ExamDto
{
AssignmentId = assignment.Id,
AssignmentTitle = assignment.Title,
Description = assignment.Description,
SubjectArea = assignment.Submissions.ToString(),
QuestionGroups = MapAssignmentGroupsToDto(rootGroups, allGroups)
};
return ApiResponse.Success("试卷信息已成功获取。", examDto);
}
catch (Exception ex)
{
return ApiResponse.Error($"获取试卷时发生错误: {ex.Message}", ex);
}
}
private List<QuestionGroupDto> MapAssignmentGroupsToDto(
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),
QuestionReference = group.Descript,
SubQuestions = group.AssignmentQuestions
.OrderBy(aq => aq.QuestionNumber)
.Select(aq => new SubQuestionDto
{
Index = aq.QuestionNumber,
Stem = aq.Question.QuestionText,
Score = aq.Score?? 0, // 使用 AssignmentQuestion 上的 Score
SampleAnswer = aq.Question.CorrectAnswer,
QuestionType = aq.Question.QuestionType.ToString(),
DifficultyLevel = aq.Question.DifficultyLevel.ToString(),
Options = new List<OptionDto>() // 这里需要您根据实际存储方式填充 Option
}).ToList(),
// 递归映射子题组
SubQuestionGroups = MapAssignmentGroupsToDto(
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.GetUserAsync(null);
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 || examData.QuestionGroups.Count != 1)
{
throw new ArgumentException("试卷必须包含且只能包含一个根题组。");
}
// 递归处理根题组及其所有子题组和题目
// 传入的 assignmentId 仅用于设置根题组的 AssignmentId 字段
// 对于子题组ProcessAndSaveAssignmentGroupsRecursive 会将 AssignmentId 设置为 null
await ProcessAndSaveAssignmentGroupsRecursive(
examData.QuestionGroups.Single(),
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)
{
byte groupNumber = 1;
var newAssignmentGroup = new AssignmentGroup
{
Id = Guid.NewGuid(), // 后端生成 GUID
Title = qgDto.Title,
Descript = qgDto.QuestionReference,
TotalPoints = qgDto.Score,
Number = (byte)qgDto.Index, // 使用 DTO 的 Index 作为 Number
ParentGroup = parentAssignmentGroupId, // 设置父级题组 GUID
// 关键修正:只有当 parentAssignmentGroupId 为 null 时,才设置 AssignmentId
// 这意味着当前题组是顶级题组
AssignmentId = parentAssignmentGroupId == null ? assignmentId : Guid.Empty,
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 = (SubjectAreaEnum)Enum.Parse(typeof(SubjectAreaEnum), subjectarea, true);
// 处理 Options如果 Options 是 JSON 字符串或需要其他存储方式,在这里处理
// 例如newQuestion.QuestionText += (JsonConvert.SerializeObject(sqDto.Options));
await _unitOfWork.GetRepository<Question>().InsertAsync(newQuestion);
var newAssignmentQuestion = new AssignmentQuestion
{
Id = Guid.NewGuid(),
QuestionId = newQuestion.Id,
QuestionNumber = (byte)questionNumber, // 使用递增的 questionNumber
AssignmentGroupId = newAssignmentGroup.Id, // 关联到当前题组
Score = sqDto.Score, // 从 DTO 获取单个子题分数
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);
}
}
}
}

View File

@@ -2,10 +2,10 @@
{
public interface IBaseService<T, TId>
{
Task<ApiResponse> GetAllAsync(QueryParameter query);
Task<ApiResponse> GetAsync(TId id);
Task<ApiResponse> AddAsync(T model);
Task<ApiResponse> UpdateAsync(T model);
Task<ApiResponse> DeleteAsync(TId id);
Task<TechHelper.Services.ApiResponse> GetAllAsync(QueryParameter query);
Task<TechHelper.Services.ApiResponse> GetAsync(TId id);
Task<TechHelper.Services.ApiResponse> AddAsync(T model);
Task<TechHelper.Services.ApiResponse> UpdateAsync(T model);
Task<TechHelper.Services.ApiResponse> DeleteAsync(TId id);
}
}

View File

@@ -0,0 +1,10 @@
using Entities.DTO;
using TechHelper.Services;
namespace TechHelper.Server.Services
{
public interface IExamService : IBaseService<ExamDto, Guid>
{
}
}