Chinh sua UI

This commit is contained in:
Nguyễn Thái Phong 2025-10-15 14:35:02 +07:00
parent 8f144c1a8b
commit 8cca84ceec
24 changed files with 698 additions and 458 deletions

Binary file not shown.

View File

@ -6,29 +6,45 @@
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form1.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form1.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form2.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form2.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form1.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form1.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\response\\quiz.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:response\\quiz.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\response\\questionoption.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:response\\questionoption.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\service\\executeexcelservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:service\\executeexcelservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\response\\question.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:response\\question.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\dbhelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:dbhelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\service\\datautil.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:service\\datautil.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|E:\\QuizMaster\\QuizMaster\\form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form"
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\service\\excelvalidator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:service\\excelvalidator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form2.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form2.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\dbhelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:dbhelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}
],
"DocumentGroupContainers": [
@ -48,7 +64,7 @@
"RelativeDocumentMoniker": "Form1.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form1.cs",
"RelativeToolTip": "Form1.cs",
"ViewState": "AQIAAAYEAAAAAAAAAAAcwBEEAAApAAAA",
"ViewState": "AQIAAE8AAAAAAAAAAAAuwGUAAABVAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-05-26T01:54:54.164Z",
"IsPinned": true,
@ -56,28 +72,16 @@
},
{
"$type": "Document",
"DocumentIndex": 1,
"DocumentIndex": 2,
"Title": "Form2.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form2.cs",
"RelativeDocumentMoniker": "Form2.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form2.cs",
"RelativeToolTip": "Form2.cs",
"ViewState": "AQIAAGMAAAAAAAAAAAAAAGwAAAAxAAAA",
"ViewState": "AQIAAF0AAAAAAAAAAAAIwG8AAAAUAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-02T06:28:48.666Z",
"IsPinned": true,
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "Form1.cs [Design]",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form1.cs",
"RelativeDocumentMoniker": "Form1.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form1.cs [Design]",
"RelativeToolTip": "Form1.cs [Design]",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-08T02:47:37.753Z"
"IsPinned": true
},
{
"$type": "Bookmark",
@ -95,52 +99,111 @@
"$type": "Bookmark",
"Name": "ST:17:0:{e8034f19-ab72-4f06-83fd-f6832b41aa63}"
},
{
"$type": "Document",
"DocumentIndex": 6,
"Title": "Question.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Response\\Question.cs",
"RelativeDocumentMoniker": "Response\\Question.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Response\\Question.cs",
"RelativeToolTip": "Response\\Question.cs",
"ViewState": "AQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-14T06:24:38.262Z"
},
{
"$type": "Document",
"DocumentIndex": 3,
"Title": "Quiz.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Response\\Quiz.cs",
"RelativeDocumentMoniker": "Response\\Quiz.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Response\\Quiz.cs",
"RelativeToolTip": "Response\\Quiz.cs",
"ViewState": "AQIAAAAAAAAAAAAAAAAAAA0AAAAWAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-13T08:59:40.254Z"
},
{
"$type": "Document",
"DocumentIndex": 5,
"Title": "ExecuteExcelService.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Service\\ExecuteExcelService.cs",
"RelativeDocumentMoniker": "Service\\ExecuteExcelService.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Service\\ExecuteExcelService.cs",
"RelativeToolTip": "Service\\ExecuteExcelService.cs",
"ViewState": "AQIAAA8AAAAAAAAAAAAAADAAAAAAAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-13T03:41:10.651Z"
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "QuestionOption.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Response\\QuestionOption.cs",
"RelativeDocumentMoniker": "Response\\QuestionOption.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Response\\QuestionOption.cs",
"RelativeToolTip": "Response\\QuestionOption.cs",
"ViewState": "AQIAAAAAAAAAAAAAAAAAAAYAAAAWAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-10T06:59:55.277Z"
},
{
"$type": "Document",
"DocumentIndex": 9,
"Title": "ExcelValidator.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Service\\ExcelValidator.cs",
"RelativeDocumentMoniker": "Service\\ExcelValidator.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Service\\ExcelValidator.cs",
"RelativeToolTip": "Service\\ExcelValidator.cs",
"ViewState": "AQIAAFcAAAAAAAAAAAAQwEwAAAAJAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-10T03:12:07.301Z"
},
{
"$type": "Document",
"DocumentIndex": 8,
"Title": "DataUtil.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Service\\DataUtil.cs",
"RelativeDocumentMoniker": "Service\\DataUtil.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Service\\DataUtil.cs",
"RelativeToolTip": "Service\\DataUtil.cs",
"ViewState": "AQIAABsBAAAAAAAAAAAwwEABAAAAAAAA",
"ViewState": "AQIAAAAAAAAAAAAAAAAAABEAAAAJAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-05-29T01:45:12.308Z"
},
{
"$type": "Document",
"DocumentIndex": 2,
"DocumentIndex": 1,
"Title": "Form1.Designer.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form1.Designer.cs",
"RelativeDocumentMoniker": "Form1.Designer.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form1.Designer.cs",
"RelativeToolTip": "Form1.Designer.cs",
"ViewState": "AQIAABoBAAAAAAAAAAAYwCcBAAAwAAAA",
"ViewState": "AQIAAE8AAAAAAAAAAAAuwGUAAAAxAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-09-30T04:14:48.709Z",
"EditorCaption": ""
"WhenOpened": "2025-09-30T04:14:48.709Z"
},
{
"$type": "Document",
"DocumentIndex": 5,
"DocumentIndex": 10,
"Title": "Form2.Designer.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form2.Designer.cs",
"RelativeDocumentMoniker": "Form2.Designer.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form2.Designer.cs",
"RelativeToolTip": "Form2.Designer.cs",
"ViewState": "AQIAAAAAAAAAAAAAAAAAACcAAAAbAAAA",
"ViewState": "AQIAAAAAAAAAAAAAAAAAADYAAAAgAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-02T06:23:49.946Z"
},
{
"$type": "Document",
"DocumentIndex": 6,
"DocumentIndex": 7,
"Title": "DbHelper.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\DbHelper.cs",
"RelativeDocumentMoniker": "DbHelper.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\DbHelper.cs",
"RelativeToolTip": "DbHelper.cs",
"ViewState": "AQIAADAAAAAAAAAAAAAYwBIAAAAPAAAA",
"ViewState": "AQIAADYAAAAAAAAAAAAYwBIAAAAPAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-05-26T01:35:21.294Z"
}

View File

@ -98,8 +98,8 @@
panelcb.BorderStyle = BorderStyle.FixedSingle;
panelcb.Location = new Point(50, 50);
panelcb.Name = "panelcb";
panelcb.Size = new Size(600, 300);
panelcb.MaximumSize = new Size(600, 300);
panelcb.Size = new Size(770, 200);
panelcb.MaximumSize = new Size(770, 400);
panelcb.TabIndex = 1;
panelcb.AutoScroll = true;
panelcb.AutoSize = true;
@ -113,8 +113,8 @@
panelop.Controls.Add(lblNumberOfCopies);
panelop.Controls.Add(txtNumberOfCopies);
panelop.Name = "panelop";
panelop.Size = new Size(600, 100);
panelop.MaximumSize = new Size(600, 300);
panelop.Size = new Size(770, 100);
panelop.MaximumSize = new Size(770, 300);
panelop.TabIndex = 2;
panelop.AutoScroll = true;
panelop.AutoSize = true;

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,7 @@
this.txtResult.Location = new System.Drawing.Point(0, 0);
this.txtResult.Name = "txtResult";
this.txtResult.Size = new System.Drawing.Size(800, 450);
this.txtResult.WordWrap = true;
this.txtResult.TabIndex = 0;
//
//btnExoortWord
@ -49,8 +50,8 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.txtResult);
this.Controls.Add(this.btnExportWord);
this.Controls.Add(this.txtResult);
this.Name = "Form2";
this.Text = "Kết quả Generate";
this.ResumeLayout(false);

View File

@ -4,7 +4,6 @@ using System.Windows.Forms;
using Xceed.Words.NET;
using Xceed.Document.NET;
namespace QuizMaster
{
public partial class Form2 : Form
@ -16,7 +15,7 @@ namespace QuizMaster
{
InitializeComponent();
_content = content;
txtResult.Text = content;
txtResult.Text = _content.Replace("\n", Environment.NewLine);
}
private void btnExportWord_Click(object sender, EventArgs e)

View File

@ -6,5 +6,7 @@
public string Question { get; set; }
public string Option { get; set; }
public int IsCorrect { get; set; } // 1 = đúng, 0 = sai
public string DoKho { get; set; }
}
}

View File

@ -10,6 +10,7 @@ namespace QuizMaster.Response
public string? B { get; set; }
public string? C { get; set; }
public string? D { get; set; }
public required string DapAn { get; set; }
public required List<string> DapAn { get; set; } = new List<string>();
public string DoKho { get; set; }
}
}

View File

@ -21,7 +21,7 @@ namespace QuizMaster.Service
// 1. Kiểm tra xem câu hỏi đã tồn tại chưa
var questionIdCmd = new NpgsqlCommand(@"
SELECT id FROM question WHERE content = @content AND category_id = @category_id;
SELECT id FROM question WHERE content = @content AND category_id = @category_id;
", conn);
questionIdCmd.Parameters.AddWithValue("content", questionContent);
@ -42,9 +42,9 @@ namespace QuizMaster.Service
try
{
var cmd = new NpgsqlCommand(@"
SELECT name
FROM category
ORDER BY name;
SELECT name
FROM category
ORDER BY name;
", conn);
await using var reader = await cmd.ExecuteReaderAsync();
@ -75,11 +75,11 @@ namespace QuizMaster.Service
try
{
var cmd = new NpgsqlCommand(@"
SELECT c.name
FROM category c
INNER JOIN department d ON c.department_id = d.id
WHERE d.name = @departmentName
ORDER BY c.name;
SELECT c.name
FROM category c
INNER JOIN department d ON c.department_id = d.id
WHERE d.name = @departmentName
ORDER BY c.name;
", conn);
cmd.Parameters.AddWithValue("departmentName", departmentName);
@ -103,6 +103,48 @@ namespace QuizMaster.Service
return categories;
}
public static async Task<List<CategoryInfo>> GetTotalOptionQuestionsAsync(List<string> lstCategory, string DoKho)
{
var categories = new List<CategoryInfo>();
if (lstCategory == null || lstCategory.Count == 0)
{
return categories;
}
string whereCate = string.Join(",", lstCategory.Select(c => $"'{c.Replace("'", "''")}'"));
await using var conn = new NpgsqlConnection(DbHelper.connectionString);
await conn.OpenAsync();
try
{
var cmd = new NpgsqlCommand($@"
SELECT c.name, COUNT(q.id) AS Total
FROM question q
INNER JOIN category c ON q.category_id = c.id
WHERE c.name IN ({whereCate})
AND q.level = '{DoKho}'
GROUP BY c.name
ORDER BY c.name;
", conn);
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
categories.Add(new CategoryInfo
{
Category = reader.GetString(0),
Total = reader.GetInt32(1)
});
}
}
catch (Exception ex)
{
MessageBox.Show($"Lỗi khi lấy danh sách số câu khó: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
throw;
}
return categories;
}
public static async Task<List<CategoryInfo>> GetTotalCategoriesAsync(List<string> lstCategory)
{
var categories = new List<CategoryInfo>();
@ -117,12 +159,12 @@ namespace QuizMaster.Service
try
{
var cmd = new NpgsqlCommand($@"
SELECT c.name, COUNT(q.id) AS Total
FROM question q
INNER JOIN category c ON q.category_id = c.id
WHERE c.name IN ({whereCate})
GROUP BY c.name
ORDER BY c.name;
SELECT c.name, COUNT(q.id) AS Total
FROM question q
INNER JOIN category c ON q.category_id = c.id
WHERE c.name IN ({whereCate})
GROUP BY c.name
ORDER BY c.name;
", conn);
await using var reader = await cmd.ExecuteReaderAsync();
@ -144,7 +186,6 @@ namespace QuizMaster.Service
return categories;
}
public static async Task<List<QuestionOption>> getAllQA(string Category)
{
var questionOption = new List<QuestionOption>();
@ -154,13 +195,12 @@ namespace QuizMaster.Service
try
{
var cmd = new NpgsqlCommand(@"
SELECT q.id, q.content, o.option_text, o.is_correct
FROM question q
INNER JOIN question_option o ON o.question_id = q.id
INNER JOIN category c ON q.category_id = c.id
WHERE c.name = @category
ORDER BY q.id, o.id;
var cmd = new NpgsqlCommand(@"SELECT q.id, q.content, q.level, o.option_text, o.is_correct
FROM question q
INNER JOIN question_option o ON o.question_id = q.id
INNER JOIN category c ON q.category_id = c.id
WHERE c.name = @category
ORDER BY q.id, o.id
", conn);
cmd.Parameters.AddWithValue("category", Category);
@ -170,10 +210,11 @@ namespace QuizMaster.Service
{
questionOption.Add(new QuestionOption
{
QuestionId = reader.IsDBNull(0) ? 0 : reader.GetInt32(0),
Question = reader.IsDBNull(1) ? string.Empty : reader.GetString(1),
Option = reader.IsDBNull(2) ? string.Empty : reader.GetString(2),
IsCorrect = reader.IsDBNull(3) ? 0 : reader.GetInt32(3)
QuestionId = reader.GetInt32(0),
Question = reader.GetString(1),
DoKho = reader.IsDBNull(2) ? "" : reader.GetString(2),
Option = reader.IsDBNull(3) ? string.Empty : reader.GetString(3),
IsCorrect = reader.IsDBNull(4) ? 0 : reader.GetInt32(4)
});
}
}
@ -186,37 +227,78 @@ namespace QuizMaster.Service
return questionOption;
}
public static async Task<bool> InsertQuestionAndOptionsAsync(List<Quiz> questions)
{
if (questions == null || questions.Count == 0)
return false;
await using var conn = new NpgsqlConnection(DbHelper.connectionString);
await conn.OpenAsync();
await using var transaction = await conn.BeginTransactionAsync();
try
{
// --- 1. Cache categoryId ---
var categoryCache = new Dictionary<string, int>();
foreach (var q in questions)
{
// Lấy category_id
var cmdCategory = new NpgsqlCommand("SELECT id FROM category WHERE name = @name", conn, transaction);
cmdCategory.Parameters.AddWithValue("name", q.LinhVuc);
var categoryIdObj = await cmdCategory.ExecuteScalarAsync();
if (categoryIdObj == null)
throw new Exception($"Category '{q.LinhVuc}' không tồn tại.");
if (!categoryCache.ContainsKey(q.LinhVuc))
{
var cmdCategory = new NpgsqlCommand(
"SELECT id FROM category WHERE name = @name", conn, transaction);
cmdCategory.Parameters.AddWithValue("name", q.LinhVuc);
var categoryIdObj = await cmdCategory.ExecuteScalarAsync();
if (categoryIdObj == null)
throw new Exception($"Category '{q.LinhVuc}' không tồn tại.");
categoryCache[q.LinhVuc] = (int)categoryIdObj;
}
}
int categoryId = (int)categoryIdObj;
// --- 2. Batch insert questions ---
var batchQuestions = new NpgsqlBatch(conn, transaction);
foreach (var q in questions)
{
var cmd = new NpgsqlBatchCommand(@"
INSERT INTO question (content, category_id, level)
VALUES (@content, @categoryId, @level)
RETURNING id;
");
cmd.Parameters.AddWithValue("content", q.CauHoi);
cmd.Parameters.AddWithValue("categoryId", categoryCache[q.LinhVuc]);
cmd.Parameters.AddWithValue("level", q.DoKho);
batchQuestions.BatchCommands.Add(cmd);
}
// Insert Question
var cmdQuestion = new NpgsqlCommand(@"
INSERT INTO question (content, category_id)
VALUES (@content, @categoryId)
RETURNING id;
", conn, transaction);
cmdQuestion.Parameters.AddWithValue("content", q.CauHoi);
cmdQuestion.Parameters.AddWithValue("categoryId", categoryId);
// --- 3. Execute batch question và lấy questionId ---
var questionIds = new List<int>();
await using (var reader = await batchQuestions.ExecuteReaderAsync())
{
foreach (var _ in questions)
{
if (!await reader.ReadAsync())
throw new Exception("Không lấy đủ questionId từ batch.");
questionIds.Add(reader.GetInt32(0));
// Chuyển sang result set tiếp theo (mỗi INSERT RETURNING tạo 1 result set)
await reader.NextResultAsync();
}
}
// --- 4. Batch insert options ---
var batchOptions = new NpgsqlBatch(conn, transaction);
for (int i = 0; i < questions.Count; i++)
{
var q = questions[i];
var qid = questionIds[i];
if (q.DapAn == null || q.DapAn.Count == 0 || q.DapAn.Any(a => !"ABCD".Contains(a)))
{
var dapAnStr = q.DapAn != null ? string.Join(",", q.DapAn) : "(null)";
throw new Exception($"Câu hỏi '{q.CauHoi}' có đáp án không hợp lệ: {dapAnStr}");
}
int questionId = (int)await cmdQuestion.ExecuteScalarAsync();
// Insert QuestionOption
var options = new Dictionary<string, string?>
{
{ "A", q.A },
@ -227,19 +309,20 @@ namespace QuizMaster.Service
foreach (var opt in options)
{
var cmdOption = new NpgsqlCommand(@"
INSERT INTO question_option (question_id, option_text, is_correct)
VALUES (@questionId, @optionText, @isCorrect);
", conn, transaction);
cmdOption.Parameters.AddWithValue("questionId", questionId);
var cmdOption = new NpgsqlBatchCommand(@"
INSERT INTO question_option (question_id, option_text, is_correct)
VALUES (@questionId, @optionText, @isCorrect);
");
cmdOption.Parameters.AddWithValue("questionId", qid);
cmdOption.Parameters.AddWithValue("optionText", opt.Value ?? (object)DBNull.Value);
cmdOption.Parameters.AddWithValue("isCorrect", opt.Key == q.DapAn ? 1 : 0);
await cmdOption.ExecuteNonQueryAsync();
cmdOption.Parameters.AddWithValue("isCorrect", q.DapAn.Contains(opt.Key) ? 1 : 0);
batchOptions.BatchCommands.Add(cmdOption);
}
}
// --- 5. Execute batch options ---
await batchOptions.ExecuteNonQueryAsync();
await transaction.CommitAsync();
return true;
}
@ -250,6 +333,7 @@ namespace QuizMaster.Service
}
}
public static async Task<int> GetTotalQuestionCount()
{
string query = "SELECT COUNT(*) FROM Question";
@ -267,26 +351,6 @@ namespace QuizMaster.Service
return Convert.ToInt32(result);
}
//public static async Task<List<Question>> GetRandomQuestions(int requestedCount)
//{
// int totalCount = await GetTotalQuestionCount();
// if (requestedCount <= 0)
// {
// MessageBox.Show("Vui lòng nhập số lượng lớn hơn 0.", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
// return new List<Question>();
// }
// if (requestedCount > totalCount)
// {
// MessageBox.Show($"Chỉ có {totalCount} câu hỏi trong cơ sở dữ liệu.", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
// return new List<Question>();
// }
// string query = $"SELECT * FROM Question ORDER BY RANDOM() LIMIT {requestedCount}";
// return await DbHelper.ExecuteSelectQuery<Question>(query);
//}
public static async Task<List<string>> GetDepartmentsAsync()
{
List<string> deplist = new List<string>();
@ -316,5 +380,6 @@ namespace QuizMaster.Service
return deplist;
}
}
}

View File

@ -2,8 +2,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using OfficeOpenXml;
using Xceed.Document.NET;
namespace QuizMaster.Service
{
@ -11,7 +13,7 @@ namespace QuizMaster.Service
{
private static readonly List<string> ExpectedHeaders = new()
{
"Lĩnh vực", "Câu hỏi", "A", "B", "C", "D", "Đáp án"
"Lĩnh vực", "Câu hỏi", "A", "B", "C", "D", "Đáp án", "Độ khó"
};
public sealed class RowData
@ -23,11 +25,14 @@ namespace QuizMaster.Service
public string B { get; init; } = string.Empty;
public string C { get; init; } = string.Empty;
public string D { get; init; } = string.Empty;
public string Answer { get; init; } = string.Empty;
public List<string> Answers { get; init; } = new List<string>();
public string Level { get; init; } = string.Empty;
}
public static List<string> ValidateHeaders(OfficeOpenXml.ExcelWorksheet worksheet)
public static List<string> ValidateHeaders(OfficeOpenXml.ExcelWorksheet worksheet)
{
var errors = new List<string>();
for (int i = 0; i < ExpectedHeaders.Count; i++)
@ -50,6 +55,9 @@ namespace QuizMaster.Service
if (string.IsNullOrWhiteSpace(data.Question))
errors.Add($"Dòng {data.Row}: 'Câu hỏi' không được trống");
if (string.IsNullOrWhiteSpace(data.Level))
errors.Add($"Dòng {data.Row}: 'Độ khó' không được trống");
return errors;
}
@ -71,31 +79,40 @@ namespace QuizMaster.Service
public static List<string> ValidateRowAnswer(RowData data)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(data.Answer))
if (data.Answers == null || data.Answers.Count == 0)
{
errors.Add($"Dòng {data.Row}: 'Đáp án' không được trống");
return errors;
}
var valid = new[] { "A", "B", "C", "D" };
if (!valid.Contains(data.Answer))
{
errors.Add($"Dòng {data.Row}: 'Đáp án' phải là A, B, C hoặc D (hiện tại là '{data.Answer}')");
return errors;
}
var validOptions = new HashSet<string> { "A", "B", "C", "D" };
bool answerPointsToEmpty = (data.Answer == "A" && string.IsNullOrWhiteSpace(data.A)) ||
(data.Answer == "B" && string.IsNullOrWhiteSpace(data.B)) ||
(data.Answer == "C" && string.IsNullOrWhiteSpace(data.C)) ||
(data.Answer == "D" && string.IsNullOrWhiteSpace(data.D));
if (answerPointsToEmpty)
foreach (var ans in data.Answers)
{
errors.Add($"Dòng {data.Row}: 'Đáp án' {data.Answer} không hợp lệ vì phương án {data.Answer} đang trống");
if (!validOptions.Contains(ans))
{
errors.Add($"Dòng {data.Row}: 'Đáp án' phải là A, B, C hoặc D (phát hiện '{ans}')");
continue;
}
bool isEmptyOption =
(ans == "A" && string.IsNullOrWhiteSpace(data.A)) ||
(ans == "B" && string.IsNullOrWhiteSpace(data.B)) ||
(ans == "C" && string.IsNullOrWhiteSpace(data.C)) ||
(ans == "D" && string.IsNullOrWhiteSpace(data.D));
if (isEmptyOption)
{
errors.Add($"Dòng {data.Row}: 'Đáp án' {ans} không hợp lệ vì phương án {ans} đang trống");
}
}
return errors;
}
//________________________________________________________________________________________________
public static List<string> ValidateRowDuplicate(RowData data, Dictionary<string, int> questionOptionDict)
{
var errors = new List<string>();
@ -134,6 +151,12 @@ namespace QuizMaster.Service
var firstCell = worksheet.Cells[row, 1];
if (string.IsNullOrWhiteSpace(firstCell.Text)) break; // Dòng trống → kết thúc
var answerText = worksheet.Cells[row, 7].Text.Trim().ToUpper();
var answers = Regex.Matches(answerText.ToUpper(), "[ABCD]")
.Select(m => m.Value.Trim())
.Distinct()
.ToList();
var data = new RowData
{
Row = row,
@ -143,7 +166,8 @@ namespace QuizMaster.Service
B = worksheet.Cells[row, 4].Text.Trim(),
C = worksheet.Cells[row, 5].Text.Trim(),
D = worksheet.Cells[row, 6].Text.Trim(),
Answer = worksheet.Cells[row, 7].Text.Trim().ToUpper()
Answers = answers,
Level = worksheet.Cells[row, 8].Text.Trim()
};
errors.AddRange(ValidateRowRequired(data));

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using OfficeOpenXml;
using QuizMaster.Response;
@ -21,6 +22,8 @@ namespace QuizMaster.Service
for (int row = 2; row <= rowCount; row++) // Bắt đầu từ dòng 2, bỏ dòng tiêu đề
{
var answerText = worksheet.Cells[row, 7].Text.Trim().ToUpper();
var ch = new Quiz
{
LinhVuc = worksheet.Cells[row, 1].Text,
@ -29,13 +32,16 @@ namespace QuizMaster.Service
B = worksheet.Cells[row, 4].Text,
C = worksheet.Cells[row, 5].Text,
D = worksheet.Cells[row, 6].Text,
DapAn = worksheet.Cells[row, 7].Text
};
DapAn = Regex.Matches(answerText.ToUpper(), "[ABCD]")
.Select(m => m.Value.Trim())
.Distinct()
.ToList(),
DoKho = worksheet.Cells[row, 8].Text
};
result.Add(ch);
}
}
return result;
}
}