ソースを参照

增加事务操作

guqiang 1 ヶ月 前
コミット
62d64a408d
24 ファイル変更812 行追加534 行削除
  1. 19 0
      src/Hotline.Application/Exam/Extensions/CollectionExtensions.cs
  2. 2 0
      src/Hotline.Application/Exam/Interface/TestPapers/ITestPaperService.cs
  3. 262 78
      src/Hotline.Application/Exam/Service/Questions/QuestionService.cs
  4. 1 1
      src/Hotline.Application/Exam/Service/Sourcewares/SourcewareService.cs
  5. 152 11
      src/Hotline.Application/Exam/Service/TestPapers/TestPaperService.cs
  6. 1 1
      src/Hotline.Repository.SqlSugar/Exam/Core/Constants/InfrastructureConstant.cs
  7. 11 0
      src/Hotline.Repository.SqlSugar/Exam/Core/Constants/OperationConstant.cs
  8. 1 1
      src/Hotline.Repository.SqlSugar/Exam/Core/Validation/IValidationErrors.cs
  9. 1 1
      src/Hotline.Repository.SqlSugar/Exam/Core/Validation/ValidationErrors.cs
  10. 0 113
      src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarExtensions.cs
  11. 0 22
      src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarRepositoryExtensions.cs
  12. 0 285
      src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarStartupExtensions.cs
  13. 52 0
      src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamRepository.cs
  14. 3 3
      src/Hotline.Repository.SqlSugar/Exam/Validators/Questions/QuestionValidator.cs
  15. 42 1
      src/Hotline.Repository.SqlSugar/Interface/IExamRepository.cs
  16. 199 7
      src/Hotline.Repository.SqlSugar/Service/ApiService.cs
  17. 12 9
      src/Hotline.Share/Dtos/TestPapers/TestPaperRuleDto.cs
  18. 2 0
      src/Hotline.Share/Dtos/TestPapers/TestPaperRuleTagDto.cs
  19. 13 0
      src/Hotline.Share/Requests/Exam/GenerateTestPaperRequest.cs
  20. 15 0
      src/Hotline/Exams/Questions/Question.cs
  21. 9 0
      src/Hotline/Exams/TestPapers/TestPaperItemAnswer.cs
  22. 7 0
      src/Hotline/Exams/TestPapers/TestPaperItemKnowladge.cs
  23. 7 0
      src/Hotline/Exams/TestPapers/TestPaperItemSourceware.cs
  24. 1 1
      src/Hotline/Snapshot/CommunityInfo.cs

+ 19 - 0
src/Hotline.Application/Exam/Extensions/CollectionExtensions.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Exam.Extensions
+{
+    public static class CollectionExtensions
+    {
+        public static List<T> AddRangeExt<T>(this List<T> collection, List<T> extand)
+        {
+            if (extand != null)
+                collection.AddRange(extand);
+
+            return collection;
+        }
+    }
+}

+ 2 - 0
src/Hotline.Application/Exam/Interface/TestPapers/ITestPaperService.cs

@@ -11,5 +11,7 @@ namespace Exam.Application.Interface.TestPapers
     public interface ITestPaperService:IQueryService<TestPaperViewResponse,TestPaperDto,TestPaperPagedRequest>,IApiService<AddTestPaperDto,UpdateTestPaperDto,TestPaper>
     {
         public Task<List<TestPaperQuestionCountViewResponse>> GetTestPaperQuestionCount(TestPaperQuestionCountRequest testPaperQuestionCountRequest);
+        
+        public Task GenerateTestPaper(GenerateTestPaperRequest generateTestPaperRequest,CancellationToken cancellationToken);
     }
 }

+ 262 - 78
src/Hotline.Application/Exam/Service/Questions/QuestionService.cs

@@ -27,6 +27,8 @@ using XF.Domain.Entities;
 using Hotline.Application.Exam.Extensions;
 using Hotline.Application.Exam.Core.Extensions;
 using Hotline.Application.Exam.Constants.Messages;
+using Hotline.Repository.SqlSugar.Exam.Core.Constants;
+using NPOI.SS.Formula.Functions;
 
 namespace Hotline.Application.Exam.Service.Questions
 {
@@ -131,6 +133,7 @@ namespace Hotline.Application.Exam.Service.Questions
                 SortIndex = s.SortIndex,
                 Status = s.Status,
                 Tag = t.Name,
+                QuestionType = s.QuestionType,
                 Title = s.Title,
                 Id = s.Id
             });
@@ -149,38 +152,46 @@ namespace Hotline.Application.Exam.Service.Questions
 
         public override async Task<string> AddAsync(AddQuestionDto actionRequest, CancellationToken cancellationToken)
         {
+            base.StartTran();
+
             var id = await base.AddAsync(actionRequest, cancellationToken);
 
             ResolveQuestionId(actionRequest,id);
 
-            await AddQuestionTags(actionRequest, cancellationToken);
+            base.Entity.QuestionTags = await AddQuestionTags(actionRequest, cancellationToken);
+
+            base.Entity.QuestionOptionses = await AddQuestionOptions(actionRequest, cancellationToken);
 
-            await AddQuestionOptions(actionRequest, cancellationToken);
+            base.Entity.QuestionAnswerE = await AddQuestionAnswer(actionRequest, cancellationToken);
 
-            await AddQuestionAnswer(actionRequest, cancellationToken);
+            base.Entity.QuestionKnowladges =  await AddKnowladges(actionRequest, cancellationToken);
 
-            await AddKnowladges(actionRequest, cancellationToken);
+            base.Entity.QuestionSourcewares = await AddSourcewares(actionRequest, cancellationToken);
 
-            await AddSourcewares(actionRequest, cancellationToken);
+            await base.Complete(base.Entity, OperationConstant.Create);           
 
             return id;
         }
 
         public override async Task UpdateAsync(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
         {
+            base.StartTran();
+
             await base.UpdateAsync(actionRequest, cancellationToken);
 
             ResolveQuestionId(actionRequest, actionRequest.Id);
 
-            await ModifyQuestionTags(actionRequest, cancellationToken);
+            base.Entity.QuestionTags =  await ModifyQuestionTags(actionRequest, cancellationToken);
 
-            await ModifyQuestionOptions(actionRequest, cancellationToken);
+            base.Entity.QuestionOptionses =  await ModifyQuestionOptions(actionRequest, cancellationToken);
 
-            await ModifyQuestionAnswer(actionRequest, cancellationToken);
+            base.Entity.QuestionAnswerE = await ModifyQuestionAnswer(actionRequest, cancellationToken);
 
-            await ModifyKnowladges(actionRequest, cancellationToken);
+            base.Entity.QuestionKnowladges =  await ModifyKnowladges(actionRequest, cancellationToken);
 
-            await ModifySourcewares(actionRequest, cancellationToken);
+            base.Entity.QuestionSourcewares =  await ModifySourcewares(actionRequest, cancellationToken);
+
+           await base.Complete(base.Entity,OperationConstant.Update);
         }
 
         public override async Task DeleteAsync(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken)
@@ -226,20 +237,41 @@ namespace Hotline.Application.Exam.Service.Questions
 
         #region private method
 
-        private void ResolveQuestionId(AddQuestionDto actionRequest,string id)
+        private void ResolveQuestionId(AddQuestionDto actionRequest,string id) 
         {
-            actionRequest.QuestionKnowladgeDtos.ForEach(x => x.QuestionId = id);
+            actionRequest.QuestionKnowladgeDtos?.ForEach(x => x.QuestionId = id);
+
+            actionRequest.QuestionOptionsDtos?.ForEach(x => x.QuestionId = id);
 
-            actionRequest.QuestionOptionsDtos.ForEach(x => x.QuestionId = id);
+            actionRequest.QuestionSourcewareDtos?.ForEach(x => x.QuestionId = id);
 
-            actionRequest.QuestionSourcewareDtos.ForEach(x => x.QuestionId = id);
+            actionRequest.QuestionTagDtos?.ForEach(x => x.QuestionId = id);
 
-            actionRequest.QuestionTagDtos.ForEach(x => x.QuestionId = id);
+            if (actionRequest.QuestionAnswerDto != null)
+            {
+                actionRequest.QuestionAnswerDto.QuestionId = id;
+            }
         }
 
-        private async Task AddQuestionTags(AddQuestionDto actionRequest, CancellationToken cancellationToken)
+        private void ResolveQuestionId(UpdateQuestionDto actionRequest, string id)
         {
-            if (actionRequest.QuestionTagDtos == null) return;
+            actionRequest.QuestionKnowladgeDtos?.ForEach(x => x.QuestionId = id);
+
+            actionRequest.QuestionOptionsDtos?.ForEach(x => x.QuestionId = id);
+
+            actionRequest.QuestionSourcewareDtos?.ForEach(x => x.QuestionId = id);
+
+            actionRequest.QuestionTagDtos?.ForEach(x => x.QuestionId = id);
+
+            if (actionRequest.QuestionAnswerDto != null)
+            {
+                actionRequest.QuestionAnswerDto.QuestionId = id;
+            }
+        }
+
+        private async Task<List<QuestionTag>> AddQuestionTags(AddQuestionDto actionRequest, CancellationToken cancellationToken)
+        {
+            if (actionRequest.QuestionTagDtos == null) return null;
 
             actionRequest.QuestionTagDtos.ResolveOperationStatus();
 
@@ -249,17 +281,19 @@ namespace Hotline.Application.Exam.Service.Questions
 
             questionTags.ToInsert();
 
-            await _questionTagRepository.AddWithValidateAsync(questionTags, cancellationToken);
+            await _questionTagRepository.ValidateAddAsync(questionTags, cancellationToken);
+
+            return questionTags;
         }
 
-        private async Task AddQuestionOptions(AddQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<List<QuestionOptions>> AddQuestionOptions(AddQuestionDto actionRequest, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionOptionsDtos == null) return;
+            if (actionRequest.QuestionOptionsDtos == null) return null;
 
             actionRequest.QuestionOptionsDtos.ResolveOperationStatus();
             // 简答和填空没有选项
             if (actionRequest.QuestionType == Share.Enums.Exams.EQuestionType.Essay || actionRequest.QuestionType == Share.Enums.Exams.EQuestionType.Blank)
-                return;
+                return null;
 
             var questionOptionseDtos = actionRequest.QuestionOptionsDtos.Where(x => x.OperationStatus == EEOperationStatus.Add).ToList();
 
@@ -276,11 +310,13 @@ namespace Hotline.Application.Exam.Service.Questions
 
             questionOptionses.ToInsert();
 
-            await _questionOptionRepository.AddWithValidateAsync(questionOptionses, cancellationToken);
+            await _questionOptionRepository.ValidateAddAsync(questionOptionses, cancellationToken);
+
+            return questionOptionses;
         }
-        private async Task AddQuestionAnswer(AddQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<QuestionAnswer> AddQuestionAnswer(AddQuestionDto actionRequest, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionAnswerDto == null) return;
+            if (actionRequest.QuestionAnswerDto == null) return null;
 
             if (actionRequest.QuestionAnswerDto.Answer != null)
             {
@@ -288,17 +324,18 @@ namespace Hotline.Application.Exam.Service.Questions
             }
 
             // 简答和填空没有选项
-            if (actionRequest.QuestionType.CheckSelectType()) return;
+            if (actionRequest.QuestionType.CheckSelectType()) return null;
             var questionAnswer = _mapper.Map<QuestionAnswer>(actionRequest.QuestionAnswerDto);
 
             questionAnswer.ToInsert();
 
-            await _questionAnswerRepository.AddWithValidateAsync(questionAnswer, cancellationToken);
+            await _questionAnswerRepository.ValidateAddAsync(questionAnswer, cancellationToken);
 
+            return questionAnswer;
         }
-        private async Task AddSourcewares(AddQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<List<QuestionSourceware>> AddSourcewares(AddQuestionDto actionRequest, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionSourcewareDtos == null) return;
+            if (actionRequest.QuestionSourcewareDtos == null) return null;
 
             actionRequest.QuestionSourcewareDtos.ResolveOperationStatus();
 
@@ -308,11 +345,13 @@ namespace Hotline.Application.Exam.Service.Questions
                         
             questionSourcewares.ToInsert();
 
-            await _questionSourcewareRepository.AddWithValidateAsync(questionSourcewares, cancellationToken);
+            await _questionSourcewareRepository.ValidateAddAsync(questionSourcewares, cancellationToken);
+
+            return questionSourcewares;
         }
-        private async Task AddKnowladges(AddQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<List<QuestionKnowladge>> AddKnowladges(AddQuestionDto actionRequest, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionKnowladgeDtos == null) return;
+            if (actionRequest.QuestionKnowladgeDtos == null) return null;
 
             actionRequest.QuestionKnowladgeDtos.ResolveOperationStatus();
 
@@ -322,11 +361,13 @@ namespace Hotline.Application.Exam.Service.Questions
 
             questionKnowladges.ToInsert();
 
-            await _questionKnowladgeRepository.AddWithValidateAsync(questionKnowladges, cancellationToken);
+            await _questionKnowladgeRepository.ValidateAddAsync(questionKnowladges, cancellationToken);
+
+            return questionKnowladges;
         }
-        private async Task UpdateSourcewares(UpdateQuestionDto actionRequest, List<QuestionSourceware> all, CancellationToken cancellationToken)
+        private async Task<List<QuestionSourceware>> UpdateSourcewares(UpdateQuestionDto actionRequest, List<QuestionSourceware> all, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionSourcewareDtos == null) return;
+            if (actionRequest.QuestionSourcewareDtos == null) return null;
 
             actionRequest.QuestionSourcewareDtos.ResolveOperationStatus();
 
@@ -336,18 +377,32 @@ namespace Hotline.Application.Exam.Service.Questions
 
             var questionSourcewares = all.Where(x => ids.Contains(x.Id)).ToList();
 
-            questionSourcewares = _mapper.Map<List<UpdateQuestionSourcewareDto>,List<QuestionSourceware>>(questionSourcewareDtos,questionSourcewares);
+            var entitys = new List<QuestionSourceware>();
+            foreach (var questionSourcewareDto in questionSourcewareDtos)
+            {
+                var entity = questionSourcewares.FirstOrDefault(x => x.Id == questionSourcewareDto.Id);
+                if (entity != null)
+                {
+                    entity.QuestionId = actionRequest.Id;
+                    entitys.Add(_mapper.Map<UpdateQuestionSourcewareDto, QuestionSourceware>(questionSourcewareDto, entity));
+                }
 
-            questionSourcewares.ForEach(x => x.QuestionId = actionRequest.Id);
+            }
+
+            //questionSourcewares = _mapper.Map<List<UpdateQuestionSourcewareDto>,List<QuestionSourceware>>(questionSourcewareDtos,questionSourcewares);
+
+            //questionSourcewares.ForEach(x => x.QuestionId = actionRequest.Id);
 
             questionSourcewares.ToUpdate();
 
-            await _questionSourcewareRepository.UpdateWithValidateAsync(questionSourcewares, cancellationToken);
+            await _questionSourcewareRepository.ValidateUpdateAsync(questionSourcewares, cancellationToken);
+
+            return questionSourcewares;
         }
 
-        private async Task UpdateKnowladges(UpdateQuestionDto actionRequest, List<QuestionKnowladge> all, CancellationToken cancellationToken)
+        private async Task<List<QuestionKnowladge>> UpdateKnowladges(UpdateQuestionDto actionRequest, List<QuestionKnowladge> all, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionKnowladgeDtos == null) return;
+            if (actionRequest.QuestionKnowladgeDtos == null) return null;
 
             actionRequest.QuestionKnowladgeDtos.ResolveOperationStatus();
 
@@ -357,20 +412,35 @@ namespace Hotline.Application.Exam.Service.Questions
 
             var questionKnowladges = all.Where(x => ids.Contains(x.Id)).ToList();
 
-            questionKnowladges = _mapper.Map<List<UpdateQuestionKnowladgeDto>, List<QuestionKnowladge>>(questionKnowladgeDtos,questionKnowladges);
+            var entitys = new List<QuestionKnowladge>();
+            foreach (var questionKnowladgeDto in questionKnowladgeDtos)
+            {
+                var entity = questionKnowladges.FirstOrDefault(x => x.Id == questionKnowladgeDto.Id);
+                if (entity != null)
+                {
+                    entity.QuestionId = actionRequest.Id;
+                    entitys.Add(_mapper.Map<UpdateQuestionKnowladgeDto, QuestionKnowladge>(questionKnowladgeDto, entity));
+                }
+               
+            }
+
 
-            questionKnowladges.ForEach(x => x.QuestionId = actionRequest.Id);
+            //questionKnowladges = _mapper.Map<List<UpdateQuestionKnowladgeDto>, List<QuestionKnowladge>>(questionKnowladgeDtos,questionKnowladges);
+
+            //questionKnowladges.ForEach(x => x.QuestionId = actionRequest.Id);
 
             questionKnowladges.ToUpdate();
 
-            await _questionKnowladgeRepository.UpdateWithValidateAsync(questionKnowladges, cancellationToken);
+            await _questionKnowladgeRepository.ValidateUpdateAsync(questionKnowladges, cancellationToken);
+
+            return questionKnowladges;
         }
-        private async Task UpdateQuestionAnswer(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<QuestionAnswer> UpdateQuestionAnswer(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionAnswerDto == null) return;
+            if (actionRequest.QuestionAnswerDto == null) return null;
 
             // 简单和填空没有选项
-            if (actionRequest.QuestionType.CheckSelectType()) return;
+            if (actionRequest.QuestionType.CheckSelectType()) return null;
 
             var questionAnswer = _mapper.Map<QuestionAnswer>(actionRequest.QuestionAnswerDto);
 
@@ -378,15 +448,17 @@ namespace Hotline.Application.Exam.Service.Questions
 
             questionAnswer.ToUpdate();
 
-            await _questionAnswerRepository.UpdateWithValidateAsync(questionAnswer, cancellationToken);
+            await _questionAnswerRepository.ValidateUpdateAsync(questionAnswer, cancellationToken);
+
+            return questionAnswer;
         }
-        private async Task UpdateQuestionOptions(UpdateQuestionDto actionRequest,List<QuestionOptions> all, CancellationToken cancellationToken)
+        private async Task<List<QuestionOptions>> UpdateQuestionOptions(UpdateQuestionDto actionRequest,List<QuestionOptions> all, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionOptionsDtos == null) return;
+            if (actionRequest.QuestionOptionsDtos == null) return null;
 
             // 简单和填空没有选项
             if (actionRequest.QuestionType == Share.Enums.Exams.EQuestionType.Essay || actionRequest.QuestionType == Share.Enums.Exams.EQuestionType.Blank)
-                return;
+                return null;
 
             var questionOptionsDtos = actionRequest.QuestionOptionsDtos.Where(x => x.OperationStatus == EEOperationStatus.Update).ToList();
 
@@ -398,21 +470,27 @@ namespace Hotline.Application.Exam.Service.Questions
             foreach(var questionOptionsDto in questionOptionsDtos)
             {
                 var entity = questionOptionses.FirstOrDefault(x => x.Id == questionOptionsDto.Id);
-                entitys.Add(_mapper.Map<UpdateQuestionOptionsDto, QuestionOptions>(questionOptionsDto, entity));
+                if (entity != null)
+                {
+                    entity.QuestionId = actionRequest.Id;
+                    entitys.Add(_mapper.Map<UpdateQuestionOptionsDto, QuestionOptions>(questionOptionsDto, entity));
+                }               
             }
 
             //questionOptionses =  _mapper.Map<List<QuestionOptionsDto>, List<QuestionOptions>>(questionOptionsDtos,questionOptionses);
 
-            entitys.ForEach(x => x.QuestionId = actionRequest.Id);
+            //entitys.ForEach(x => x.QuestionId = actionRequest.Id);
 
             entitys.ToUpdate();
 
-            await _questionOptionRepository.UpdateWithValidateAsync(entitys, cancellationToken);
+            await _questionOptionRepository.ValidateUpdateAsync(entitys, cancellationToken);
+
+            return entitys;
         }
 
-        private async Task UpdateQuestionTags(UpdateQuestionDto actionRequest,List<QuestionTag> all, CancellationToken cancellationToken)
+        private async Task<List<QuestionTag>> UpdateQuestionTags(UpdateQuestionDto actionRequest,List<QuestionTag> all, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionTagDtos == null) return;
+            if (actionRequest.QuestionTagDtos == null) return null;
 
             var questionTagDtos = actionRequest.QuestionTagDtos.Where(x => x.OperationStatus == EEOperationStatus.Update).ToList();
 
@@ -420,11 +498,24 @@ namespace Hotline.Application.Exam.Service.Questions
 
             var questionTags = all.Where(x => ids.Contains(x.Id)).ToList();
 
-            questionTags = _mapper.Map<List<UpdateQuestionTagDto>, List<QuestionTag>>(questionTagDtos,questionTags);
+            var entitys = new List<QuestionTag>();
+            foreach (var questionOptionsDto in questionTagDtos)
+            {
+                var entity = questionTags.FirstOrDefault(x => x.Id == questionOptionsDto.Id);
+                if (entity != null)
+                {
+                    entity.QuestionId = actionRequest.Id;
+                    entitys.Add(_mapper.Map<UpdateQuestionTagDto, QuestionTag>(questionOptionsDto, entity));
+                }
+                
+            }
+            //questionTags = _mapper.Map<List<UpdateQuestionTagDto>, List<QuestionTag>>(questionTagDtos,questionTags);
 
             questionTags.ToUpdate();
 
-            await _questionTagRepository.UpdateWithValidateAsync(questionTags, cancellationToken);
+            await _questionTagRepository.ValidateUpdateAsync(questionTags, cancellationToken);
+
+            return questionTags;
         }
         private async Task DeleteSourcewares(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken)
         {
@@ -446,54 +537,62 @@ namespace Hotline.Application.Exam.Service.Questions
         {
             await _questionTagRepository.DeleteWithValidateAsync(entityQueryRequest, cancellationToken);
         }
-        private async Task ModifySourcewares(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<List<QuestionSourceware>> ModifySourcewares(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
         {
             var all = await _questionSourcewareRepository.Queryable().Where(x => x.QuestionId == actionRequest.Id).ToListAsync();
 
-            if (actionRequest.QuestionSourcewareDtos == null) return;
+            if (actionRequest.QuestionSourcewareDtos == null) return null;
+
+            var questionSourcewares = new List<QuestionSourceware>();
 
             actionRequest.QuestionSourcewareDtos.ResolveOperationStatus(all);
 
-            await AddSourcewares(actionRequest, cancellationToken);
+            questionSourcewares.AddRangeExt(await AddSourcewares(actionRequest, cancellationToken));
 
-            await UpdateSourcewares(actionRequest, all, cancellationToken);
+            questionSourcewares.AddRangeExt(await UpdateSourcewares(actionRequest, all, cancellationToken));
 
             var questionSourcewareDtos = actionRequest.QuestionSourcewareDtos.Where(x => x.OperationStatus == EEOperationStatus.Delete);
             var ids = questionSourcewareDtos.Select(m => m.Id);
             EntityQueryRequest entityQueryRequest = ResovleDelete<QuestionSourceware>(ids);
 
             await DeleteSourcewares(entityQueryRequest, cancellationToken);
+
+            return questionSourcewares;
         }
 
-        private async Task ModifyKnowladges(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<List<QuestionKnowladge>> ModifyKnowladges(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
         {
             var all = await _questionKnowladgeRepository.Queryable().Where(x => x.QuestionId == actionRequest.Id).ToListAsync();
 
-            if (actionRequest.QuestionKnowladgeDtos == null) return;
+            if (actionRequest.QuestionKnowladgeDtos == null) return null;
+
+            var questionKnowladges = new List<QuestionKnowladge>();
 
             actionRequest.QuestionKnowladgeDtos.ResolveOperationStatus(all);
 
-            await AddKnowladges(actionRequest, cancellationToken);
+            questionKnowladges.AddRangeExt(await AddKnowladges(actionRequest, cancellationToken));
 
-            await UpdateKnowladges(actionRequest, all, cancellationToken);
+            questionKnowladges.AddRangeExt(await UpdateKnowladges(actionRequest, all, cancellationToken));
 
             var questionKnowladgeDtos = actionRequest.QuestionKnowladgeDtos.Where(x => x.OperationStatus == EEOperationStatus.Delete);
             var ids = questionKnowladgeDtos.Select(m => m.Id);
             EntityQueryRequest entityQueryRequest = ResovleDelete<QuestionKnowladge>(ids);
 
             await DeleteKnowladges(entityQueryRequest, cancellationToken);
+
+            return questionKnowladges;
         }
 
-        private async Task ModifyQuestionAnswer(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<QuestionAnswer> ModifyQuestionAnswer(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
         {
             var all = await _questionAnswerRepository.Queryable().Where(x => x.QuestionId == actionRequest.Id).ToListAsync();
 
-
+            var questionAnswer = new QuestionAnswer();
 
             if (actionRequest.QuestionAnswerDto == null)
             {
                 if (all == null) 
-                    return;
+                    return null;
                 else
                 {
                     var ids = all.Select(m => m.Id).ToList();
@@ -505,7 +604,7 @@ namespace Hotline.Application.Exam.Service.Questions
 
                     await DeleteQuestionAnswer(entityQueryRequest, cancellationToken);
 
-                    return;
+                    return null;
                 }
 
             }
@@ -515,9 +614,19 @@ namespace Hotline.Application.Exam.Service.Questions
                 actionRequest.QuestionAnswerDto.OperationStatus = EEOperationStatus.Update;
             }
 
-            await AddQuestionAnswer(actionRequest, cancellationToken);
+            var add = await AddQuestionAnswer(actionRequest, cancellationToken);
 
-            await UpdateQuestionAnswer(actionRequest, cancellationToken);
+            if (add!=null)
+            {
+                questionAnswer = add;
+            }
+
+            var update= await UpdateQuestionAnswer(actionRequest, cancellationToken);
+
+            if(update != null)
+            {
+                questionAnswer = update;
+            }
 
             if (actionRequest.QuestionAnswerDto != null && actionRequest.QuestionAnswerDto.OperationStatus == EEOperationStatus.Delete)
             {
@@ -529,44 +638,54 @@ namespace Hotline.Application.Exam.Service.Questions
                 await DeleteQuestionAnswer(entityQueryRequest, cancellationToken);
             }
 
+            return questionAnswer;
+
         }
 
-        private async Task ModifyQuestionOptions(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<List<QuestionOptions>> ModifyQuestionOptions(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionOptionsDtos == null) return;
+            if (actionRequest.QuestionOptionsDtos == null) return null;
+
+            var questionOptions = new List<QuestionOptions>();
 
             var all = await _questionOptionRepository.Queryable().Where(x=>x.QuestionId == actionRequest.Id).ToListAsync();
 
             actionRequest.QuestionOptionsDtos.ResolveOperationStatus(all);
 
-            await AddQuestionOptions(actionRequest, cancellationToken);
+            questionOptions.AddRangeExt(await AddQuestionOptions(actionRequest, cancellationToken));
 
-            await UpdateQuestionOptions(actionRequest, all, cancellationToken);
+            questionOptions.AddRangeExt(await UpdateQuestionOptions(actionRequest, all, cancellationToken));
 
             var questionOptionsDtos = actionRequest.QuestionOptionsDtos.Where(x => x.OperationStatus == EEOperationStatus.Delete);
             var ids = questionOptionsDtos.Select(m => m.Id);
             EntityQueryRequest entityQueryRequest = ResovleDelete<QuestionOptions>(ids);
 
             await DeleteQuestionOptions(entityQueryRequest, cancellationToken);
+
+            return questionOptions;
         }
 
-        private async Task ModifyQuestionTags(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
+        private async Task<List<QuestionTag>> ModifyQuestionTags(UpdateQuestionDto actionRequest, CancellationToken cancellationToken)
         {
-            if (actionRequest.QuestionTagDtos == null) return;
+            if (actionRequest.QuestionTagDtos == null) return null;
+
+            var questionTags = new List<QuestionTag>();
 
             var all = await _questionTagRepository.Queryable().Where(x => x.QuestionId == actionRequest.Id).ToListAsync();
 
             actionRequest.QuestionTagDtos.ResolveOperationStatus(all);
 
-            await AddQuestionTags(actionRequest, cancellationToken);
+            questionTags.AddRangeExt(await AddQuestionTags(actionRequest, cancellationToken));
 
-            await UpdateQuestionTags(actionRequest, all, cancellationToken);
+            questionTags.AddRangeExt(await UpdateQuestionTags(actionRequest, all, cancellationToken));
 
             var questionTagDtos = actionRequest.QuestionTagDtos.Where(x => x.OperationStatus == EEOperationStatus.Delete);
             var ids = questionTagDtos.Select(m => m.Id);
             EntityQueryRequest entityQueryRequest = ResovleDelete<QuestionTag>(ids);
 
             await DeleteQuestionTags(entityQueryRequest, cancellationToken);
+
+            return questionTags;
         }
 
         private EntityQueryRequest ResovleDelete<T>(IEnumerable<string> ids) where T:class,IEntity<string>,new()
@@ -642,5 +761,70 @@ namespace Hotline.Application.Exam.Service.Questions
             return await questionTagDtos.ToListAsync();
         }
         #endregion
+
+        #region protected method
+
+        //protected override async Task CompleteAdd(Question entity)
+        //{
+        //    if (entity.QuestionType.CheckSelectType())
+        //    {
+        //        await base.AddNav(entity)
+        //        .Include(x => x.QuestionTags)
+        //        .Include(x => x.QuestionOptionses)
+        //        .Include(x => x.QuestionSourcewares)
+        //        .Include(x => x.QuestionKnowladges).ExecuteCommandAsync();
+        //    }
+        //    else
+        //    {
+        //        await base.AddNav(entity)
+        //       .Include(x => x.QuestionTags)
+        //       .Include(x => x.QuestionAnswerE)
+        //       .Include(x => x.QuestionSourcewares)
+        //       .Include(x => x.QuestionKnowladges).ExecuteCommandAsync();
+        //    }
+        //}
+
+        protected override async Task CompleteUpdate(Question entity)
+        {
+            if (entity.QuestionType.CheckSelectType())
+            {
+                await base.UpdateNav(entity)
+                .Include(x => x.QuestionTags,new UpdateNavOptions
+                {
+                    OneToManyInsertOrUpdate = true
+                })
+                .Include(x => x.QuestionOptionses, new UpdateNavOptions
+                {
+                    OneToManyInsertOrUpdate = true
+                })
+                .Include(x => x.QuestionSourcewares, new UpdateNavOptions
+                {
+                    OneToManyInsertOrUpdate = true
+                })
+                .Include(x => x.QuestionKnowladges, new UpdateNavOptions
+                {
+                    OneToManyInsertOrUpdate = true
+                }).ExecuteCommandAsync();
+            }
+            else
+            {
+                await base.UpdateNav(entity)
+               .Include(x => x.QuestionTags, new UpdateNavOptions
+               {
+                   OneToManyInsertOrUpdate = true
+               })
+               .Include(x => x.QuestionAnswerE)
+               .Include(x => x.QuestionSourcewares, new UpdateNavOptions
+               {
+                   OneToManyInsertOrUpdate = true
+               })
+               .Include(x => x.QuestionKnowladges, new UpdateNavOptions
+               {
+                   OneToManyInsertOrUpdate = true
+               }).ExecuteCommandAsync();
+            }
+        }
+
+        #endregion
     }
 }

+ 1 - 1
src/Hotline.Application/Exam/Service/Sourcewares/SourcewareService.cs

@@ -109,7 +109,7 @@ namespace Hotline.Application.Exam.Service.Sourcewares
         /// <returns></returns>
         private async Task SetDefaultCategory(AddSourcewareDto actionRequest)
         {
-            var category = await new BaseRepository<SourcewareCategory>(_repository.UOW, _datePermissionFilterBuilder, _serviceProvider).Queryable().FirstAsync(x => x.Name == InfrastructureContant.NoCategory);
+            var category = await new BaseRepository<SourcewareCategory>(_repository.UOW, _datePermissionFilterBuilder, _serviceProvider).Queryable().FirstAsync(x => x.Name == InfrastructureConstant.NoCategory);
 
             if (actionRequest.CategoryId.IsNullOrEmpty())
             {

+ 152 - 11
src/Hotline.Application/Exam/Service/TestPapers/TestPaperService.cs

@@ -1,5 +1,6 @@
 using DocumentFormat.OpenXml.Office2010.Excel;
 using Exam.Application.Interface.TestPapers;
+using Exam.ExamManages;
 using Exam.Infrastructure.Data.Entity;
 using Exam.Infrastructure.Enums;
 using Exam.Infrastructure.Extensions;
@@ -30,6 +31,7 @@ using JiebaNet.Segmenter.Common;
 using MapsterMapper;
 using NPOI.SS.Formula.Functions;
 using SqlSugar;
+using System.Collections.Immutable;
 using XF.Domain.Dependency;
 
 namespace Hotline.Application.Exam.Service.TestPapers
@@ -227,6 +229,149 @@ namespace Hotline.Application.Exam.Service.TestPapers
             }
             return testPaperQuestionCountViewResponses;
         }
+
+
+        public async Task GenerateTestPaper(GenerateTestPaperRequest generateTestPaperRequest, CancellationToken cancellationToken)
+        {
+            var testPaper = await _repository.GetAsync(x => x.Id == generateTestPaperRequest.TestPaperId);
+
+            if (testPaper != null)
+            {
+                if (testPaper.Mode == Share.Enums.Exams.EExamMode.Random)
+                {
+                    List<TestPaperItem> testPaperItems = await SyncQuestion(generateTestPaperRequest, cancellationToken);
+
+                    await SyncQuestionOptions(testPaperItems, cancellationToken);
+                    await SyncQuestionSourcewares(testPaperItems, cancellationToken);
+                    await SyncQuestionKnowladge(testPaperItems, cancellationToken);
+                    await SyncQuestionAnswer(testPaperItems, cancellationToken);
+
+                }
+            }
+            
+        }
+
+        private async Task SyncQuestionAnswer(List<TestPaperItem> testPaperItems, CancellationToken cancellationToken)
+        {
+            var questionAnswersRepository = new ExamRepository<QuestionAnswer>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider);
+            var questionIds = testPaperItems.Select(x => x.QuestionId);
+            var questionAnswers = await questionAnswersRepository.Queryable().Where(x => questionIds.Contains(x.QuestionId)).ToListAsync();
+
+            var testPaperItemAnswers = new List<TestPaperItemAnswer>();
+
+            questionAnswers.ForEach(item =>
+            {
+                var testPaperItemAnswer = _mapper.Map<TestPaperItemAnswer>(item);
+                var testPaperItem = testPaperItems.FirstOrDefault(x => x.QuestionId == item.QuestionId);
+                testPaperItemAnswer.QueswerAnswerId = item.Id;
+                testPaperItemAnswer.TestPaperItemId = testPaperItem?.Id ?? string.Empty;
+                testPaperItemAnswers.Add(testPaperItemAnswer);
+            });
+
+            testPaperItemAnswers.ToInsert();
+
+            await _testPaperItemAnswerRepository.AddWithValidateAsync(testPaperItemAnswers, cancellationToken);
+        }
+
+        private async Task SyncQuestionKnowladge(List<TestPaperItem> testPaperItems, CancellationToken cancellationToken)
+        {
+            var questionKnowladgesRepository = new ExamRepository<QuestionKnowladge>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider);
+            var questionIds = testPaperItems.Select(x => x.QuestionId);
+            var questionKnowladges = await questionKnowladgesRepository.Queryable().Where(x => questionIds.Contains(x.QuestionId)).ToListAsync();
+
+            var testPaperItemKnowladges = new List<TestPaperItemKnowladge>();
+
+            questionKnowladges.ForEach(item =>
+            {
+                var testPaperItemKnowladge = _mapper.Map<TestPaperItemKnowladge>(item);
+                var testPaperItem = testPaperItems.FirstOrDefault(x => x.QuestionId == item.QuestionId);
+                testPaperItemKnowladge.KnowladgeId = item.Id;
+                testPaperItemKnowladge.TestPaperItemId = testPaperItem?.Id ?? string.Empty;
+                testPaperItemKnowladges.Add(testPaperItemKnowladge);
+            });
+
+            testPaperItemKnowladges.ToInsert();
+
+            await _testPaperItemKnowladgeRepository.AddWithValidateAsync(testPaperItemKnowladges, cancellationToken);
+        }
+
+        private async Task SyncQuestionSourcewares(List<TestPaperItem> testPaperItems, CancellationToken cancellationToken)
+        {
+            var questionSourcewaresRepository = new ExamRepository<QuestionSourceware>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider);
+            var questionIds = testPaperItems.Select(x => x.QuestionId);
+            var questionSourcewares = await questionSourcewaresRepository.Queryable().Where(x => questionIds.Contains(x.QuestionId)).ToListAsync();
+
+            var testPaperItemSourcewares = new List<TestPaperItemSourceware>();
+
+            questionSourcewares.ForEach(item =>
+            {
+                var testPaperItemSourceware = _mapper.Map<TestPaperItemSourceware>(item);
+                var testPaperItem = testPaperItems.FirstOrDefault(x => x.QuestionId == item.QuestionId);
+                testPaperItemSourceware.SourcewareId = item.Id;
+                testPaperItemSourceware.TestPaperItemId = testPaperItem?.Id ?? string.Empty;
+                testPaperItemSourcewares.Add(testPaperItemSourceware);
+            });
+
+            testPaperItemSourcewares.ToInsert();
+
+            await _testPaperItemSourcewareRepository.AddWithValidateAsync(testPaperItemSourcewares, cancellationToken);
+        }
+
+        private async Task SyncQuestionOptions(List<TestPaperItem> testPaperItems, CancellationToken cancellationToken)
+        {
+            var questionOptionsRepository =new ExamRepository<QuestionOptions>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider);
+            var questionIds = testPaperItems.Select(x => x.QuestionId);
+            var questionOptions = await questionOptionsRepository.Queryable().Where(x=>questionIds.Contains(x.QuestionId)).ToListAsync();
+
+            var testPaperItemOptions = new List<TestPaperItemOptions>();
+
+            questionOptions.ForEach(item =>
+            {
+                var testPaperItemOption = _mapper.Map<TestPaperItemOptions>(item);
+                var testPaperItem = testPaperItems.FirstOrDefault(x => x.QuestionId == item.QuestionId);
+                testPaperItemOption.QuestionOptionId = item.Id;
+                testPaperItemOption.TestPaperItemId = testPaperItem?.Id??string.Empty;
+                testPaperItemOptions.Add(testPaperItemOption);
+            });
+
+            testPaperItemOptions.ToInsert();
+
+            await _testPaperItemOptionsRepository.AddWithValidateAsync(testPaperItemOptions, cancellationToken);
+        }
+
+        private async Task<List<TestPaperItem>> SyncQuestion(GenerateTestPaperRequest generateTestPaperRequest, CancellationToken cancellationToken)
+        {
+            var questionRepository = new ExamRepository<Question>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider);
+            var questionTagRepository = new ExamRepository<QuestionTag>(_repository.UOW, _dataPermissionFilterBuilder, _serviceProvider);
+            var testPaperRuleTable = _testPaperRuleRepository.Queryable().Where(x => x.TestPaperId == generateTestPaperRequest.TestPaperId);
+            var testPaperTagTable = _testPaperRuleTagRepository.Queryable();
+            var questionTagTable = questionTagRepository.Queryable();
+            var questionTable = questionRepository.Queryable();
+
+            var count = await testPaperRuleTable.Select(x => x.Count).FirstAsync();
+
+            var questions = await questionTable.InnerJoin(questionTagTable, (q, qt) => q.Id == qt.QuestionId)
+                .InnerJoin(testPaperTagTable, (q, qt, tt) => qt.TagId == tt.TagId)
+                .InnerJoin(testPaperRuleTable, (q, qt, tt, tpr) => tt.TestPaperRuleId == tpr.Id)
+                .Where((q, qt, tt, tpr)=>q.QuestionType == tpr.QuestionType)
+                .Select((q, qt, tt, tpr) => q).Take(count).OrderBy(SqlFunc.GetRandom()).ToListAsync();
+
+            var testPaperItems = new List<TestPaperItem>();
+
+            questions.ForEach(item =>
+            {
+                var testPaperItem = _mapper.Map<TestPaperItem>(item);
+                testPaperItem.TestPaperId = generateTestPaperRequest.TestPaperId;
+                testPaperItem.QuestionId = item.Id;
+                testPaperItems.Add(testPaperItem);
+            });
+
+            testPaperItems.ToInsert();
+
+            await _testPaperItemRepository.AddWithValidateAsync(testPaperItems,cancellationToken);
+
+            return testPaperItems;
+        }
         #endregion
 
         #region private method
@@ -347,7 +492,9 @@ namespace Hotline.Application.Exam.Service.TestPapers
 
             var testPaperRuleDtos = actionRequest.TestPaperRuleDtos.Where(x => x.OperationStatus == EEOperationStatus.Add);
 
-            var testPaperRuleTagDtos = testPaperRuleDtos.Select(x => x.TestPaperRuleTagDto);
+            var testPaperRuleTagDtos = testPaperRuleDtos.SelectMany(x => x.TestPaperRuleTagDtos).ToList();
+
+            testPaperRuleTagDtos.ResolveOperationStatus();
 
             var testPaperRuleTags = _mapper.Map<List<TestPaperRuleTag>>(testPaperRuleTagDtos);
 
@@ -445,13 +592,11 @@ namespace Hotline.Application.Exam.Service.TestPapers
 
             var entities = new List<TestPaperRuleTag>();
 
-            foreach (var testPaperRuleDto in actionRequest.TestPaperRuleDtos)
+            foreach (var testPaperRuleTagDto in actionRequest.TestPaperRuleDtos.SelectMany(x=>x.TestPaperRuleTagDtos))
             {
-                var entity = testPaperRuleTags.FirstOrDefault(x => x.TestPaperRuleId == testPaperRuleDto.Id);
+                var entity = testPaperRuleTags.FirstOrDefault(x => x.Id == testPaperRuleTagDto.Id);
 
-                entity.TagId = testPaperRuleDto.TagId;
-
-                entity.TestPaperRuleId = testPaperRuleDto.Id;
+                entity = _mapper.Map<UpdateTestPaperRuleTagDto, TestPaperRuleTag>(testPaperRuleTagDto,entity);
             }
 
             entities.ToUpdate();
@@ -532,7 +677,7 @@ namespace Hotline.Application.Exam.Service.TestPapers
 
             var testPaperRuleTagDtos = _mapper.Map<List<TestPaperRuleTagDto>>(testPaperRuleTags);
             var testPaperRuleList = await queryable.ToListAsync();
-            testPaperRuleList.ForEach(x => x.TestPaperRuleTagDto = testPaperRuleTagDtos.FirstOrDefault(n => n.TestPaperRuleId == x.Id));
+            testPaperRuleList.ForEach(x => x.TestPaperRuleTagDtos = testPaperRuleTagDtos.Where(n => n.TestPaperRuleId == x.Id).ToList());
 
             return testPaperRuleList;
 
@@ -562,10 +707,6 @@ namespace Hotline.Application.Exam.Service.TestPapers
             actionRequest.TestPaperRuleDtos.ForEach(x =>
             {
                 x.TestPaperId = id;
-                x.TestPaperRuleTagDto = new TestPaperRuleTagDto
-                {
-                    TagId = x.TagId,
-                };
             });
 
             actionRequest.TestPaperItemDtos.ForEach(x => x.TestPaperId = id);

+ 1 - 1
src/Hotline.Repository.SqlSugar/Exam/Core/Constants/InfrastructureContant.cs → src/Hotline.Repository.SqlSugar/Exam/Core/Constants/InfrastructureConstant.cs

@@ -1,6 +1,6 @@
 namespace Exam.Infrastructure.Validation.Constants
 {
-    public class InfrastructureContant
+    public class InfrastructureConstant
     {
         public const string SystemError = "SystemError";
         public const string SystemErrorDescription = "系统错误";

+ 11 - 0
src/Hotline.Repository.SqlSugar/Exam/Core/Constants/OperationConstant.cs

@@ -0,0 +1,11 @@
+namespace Hotline.Repository.SqlSugar.Exam.Core.Constants
+{
+    public class OperationConstant
+    {
+        public const string Create = "Create";
+
+        public const string Update = "Update";
+
+        public const string Delete = "Delete";
+    }
+}

+ 1 - 1
src/Hotline.Repository.SqlSugar/Exam/Core/Validation/IValidationErrors.cs

@@ -38,7 +38,7 @@ namespace Exam.Infrastructure.Validation.Validation
         /// <param name="errorMessage">系统错误</param>
         /// <param name="attemptedValue">试图用于描述异常信息的值</param>
         /// <returns></returns>
-        IValidationErrors AddSystemError(string propertyName = InfrastructureContant.SystemError, string errorMessage = InfrastructureContant.SystemErrorDescription,
+        IValidationErrors AddSystemError(string propertyName = InfrastructureConstant.SystemError, string errorMessage = InfrastructureConstant.SystemErrorDescription,
             object attemptedValue = null, bool customState = false);
 
         /// <summary>

+ 1 - 1
src/Hotline.Repository.SqlSugar/Exam/Core/Validation/ValidationErrors.cs

@@ -32,7 +32,7 @@ namespace Exam.Infrastructure.Validation.Validation
         /// <param name="attemptedValue"></param>
         /// <param name="customState"></param>
         /// <returns></returns>
-        public IValidationErrors AddSystemError(string propertyName = InfrastructureContant.SystemError, string errorMessage = InfrastructureContant.SystemErrorDescription,
+        public IValidationErrors AddSystemError(string propertyName = InfrastructureConstant.SystemError, string errorMessage = InfrastructureConstant.SystemErrorDescription,
             object attemptedValue = null, bool customState = false)
         {
             return AddError(propertyName, errorMessage, attemptedValue, customState);

+ 0 - 113
src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarExtensions.cs

@@ -1,113 +0,0 @@
-using System.Collections;
-using System.Data;
-using System.Reflection;
-using SqlSugar;
-
-namespace Hotline.Repository.SqlSugar.Exam.Extensions
-{
-    public static class SqlSugarExtensions
-    {
-        /// <summary>
-        /// List转Dictionary
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="list"></param>
-        /// <returns></returns>
-        public static List<Dictionary<string, object?>> ToDictionary<T>(this List<T> list)
-        {
-            var result = new List<Dictionary<string, object?>>();
-            if (list.Any())
-            {
-                foreach (var item in list)
-                {
-                    Dictionary<string, object?> dc = new();
-                    var properties = item.GetType().GetProperties();
-                    foreach (var property in properties)
-                    {
-                        if (IsIgnoreColumn(property)) continue;
-                        if (IsNavigateColumn(property)) continue;
-                        dc.Add(property.Name, property.GetValue(item));
-                    }
-                    result.Add(dc);
-                }
-            }
-
-            return result;
-        }
-
-        public static DataTable ToDataTable<T>(this List<T> list, string tableName)
-        {
-            var dt = new DataTable();
-            dt.TableName = tableName; //设置表名
-
-            if (list.Any())
-            {
-                PropertyInfo[] properties = list[0].GetType().GetProperties();
-                foreach (PropertyInfo property in properties)
-                {
-                    if (IsIgnoreColumn(property)) continue;
-                    if (IsNavigateColumn(property)) continue;
-                    Type colType = property.PropertyType;
-                    if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>))
-                    {
-                        colType = colType.GetGenericArguments()[0];
-                    }
-                    dt.Columns.Add(property.Name, colType);
-                }
-
-                foreach (var item in list)
-                {
-                    ArrayList tempList = new();
-                    //var properties = item.GetType().GetProperties();
-                    foreach (var property in properties)
-                    {
-                        if (IsIgnoreColumn(property)) continue;
-                        if (IsNavigateColumn(property)) continue;
-                        var obj = property.GetValue(item, null);
-                        tempList.Add(obj);
-                    }
-                    dt.LoadDataRow(tempList.ToArray(), true);
-                }
-
-                //for (int i = 0; i < list.Count; i++)
-                //{
-                //    ArrayList tempList = new();
-                //    foreach (PropertyInfo pi in propertys)
-                //    {
-                //        if (IsIgnoreColumn(pi))
-                //            continue;
-                //        object obj = pi.GetValue(list[i], null);
-                //        tempList.Add(obj);
-                //    }
-                //    object[] array = tempList.ToArray();
-                //    result.LoadDataRow(array, true);
-                //}
-            }
-
-            //var addRow = dt.NewRow();
-            //addRow["id"] = 0;
-            //addRow["price"] = 1;
-            //addRow["Name"] = "a";
-            //dt.Rows.Add(addRow);//添加数据
-
-            //var x = db.Storageable(dt).WhereColumns("id").ToStorage();//id作为主键
-            //x.AsInsertable.IgnoreColumns("id").ExecuteCommand();//如果是自增要添加IgnoreColumns
-            //x.AsUpdateable.ExecuteCommand();
-            return dt;
-        }
-
-        /// <summary>
-        /// 排除SqlSugar忽略的列
-        /// </summary>
-        /// <param name="pi"></param>
-        /// <returns></returns>
-        private static bool IsIgnoreColumn(PropertyInfo property)
-        {
-            var sc = property.GetCustomAttributes<SugarColumn>(false).FirstOrDefault(u => u.IsIgnore);
-            return sc != null;
-        }
-
-        private static bool IsNavigateColumn(PropertyInfo property) =>
-            property.GetCustomAttributes<Navigate>(false).Any();
-    }
-}

+ 0 - 22
src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarRepositoryExtensions.cs

@@ -1,22 +0,0 @@
-using SqlSugar;
-
-namespace Hotline.Repository.SqlSugar.Exam.Extensions
-{
-    public static class SqlSugarRepositoryExtensions
-    {
-        public static async Task<(int Total, List<TEntity> Items)> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> query, int pageIndex, int pageSize, CancellationToken cancellationToken = default)
-            where TEntity : class, new()
-        {
-            RefAsync<int> total = 0;
-            var items = await query.ToPageListAsync(pageIndex, pageSize, total);
-            return (total.Value, items);
-        }
-
-        public static Task<List<TEntity>> ToFixedListAsync<TEntity>(this ISugarQueryable<TEntity> query, int queryIndex, int? queryCount = null, CancellationToken cancellationToken = default)
-          where TEntity : class, new()
-        {
-            if (queryCount is null or 0) queryCount = 50;
-            return query.Skip(queryIndex * queryCount.Value).Take(queryCount.Value).ToListAsync(cancellationToken);
-        }
-    }
-}

+ 0 - 285
src/Hotline.Repository.SqlSugar/Exam/Extensions/SqlSugarStartupExtensions.cs

@@ -1,285 +0,0 @@
-using System.Collections;
-using System.ComponentModel;
-using System.ComponentModel.DataAnnotations;
-using System.Linq.Dynamic.Core;
-using System.Linq.Expressions;
-using System.Reflection;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Serilog;
-using SqlSugar;
-using XF.Domain.Entities;
-using XF.Domain.Extensions;
-using XF.Domain.Options;
-using XF.Domain.Repository;
-using XF.Utility.SequentialId;
-
-namespace Hotline.Repository.SqlSugar.Exam.Extensions
-{
-    public static class SqlSugarStartupExtensions
-    {
-        public static void AddSqlSugar(this IServiceCollection services, IConfiguration configuration, string dbName = "Exam")
-        {
-            //多租户 new SqlSugarScope(List<ConnectionConfig>,db=>{});
-
-            SqlSugarScope sqlSugar = new SqlSugarScope(new ConnectionConfig()
-            {
-                DbType = DbType.PostgreSQL,
-                ConnectionString = configuration.GetConnectionString(dbName),
-                IsAutoCloseConnection = true,
-                ConfigureExternalServices = new ConfigureExternalServices
-                {
-                    EntityService = (property, column) =>
-                    {
-                        var attributes = property.GetCustomAttributes(true); //get all attributes 
-
-                        if (column.PropertyName.ToLower() == "id" ||
-                            attributes.Any(it => it is KeyAttribute)) //是id的设为主键
-                        {
-                            column.IsPrimarykey = true;
-                            column.Length = 36;
-                        }
-
-                        //if (!column.DbColumnName.Contains("_"))
-                        //    column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName);//ToUnderLine驼峰转下划线
-
-                        //column.ColumnDescription = (attributes.FirstOrDefault(d => d is DescriptionAttribute) as DescriptionAttribute)?.Description ?? string.Empty;
-
-                        //统一设置 nullable等于isnullable=true
-                        if (!column.IsPrimarykey && new NullabilityInfoContext().Create(property).WriteState is NullabilityState.Nullable)
-                        {
-                            column.IsNullable = true;
-                        }
-                    },
-                    EntityNameService = (type, entity) =>
-                    {
-                        var attributes = type.GetCustomAttributes(true);
-                        //if (attributes.Any(it => it is TableAttribute))
-                        //{
-                        //    entity.DbTableName = (attributes.First(it => it is TableAttribute) as TableAttribute).UserName;
-                        //}
-
-                        entity.DbTableName = entity.DbTableName.ToSnakeCase();
-
-                        //if (!entity.DbTableName.Contains("_"))
-                        //    entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);//ToUnderLine驼峰转下划线方法
-                        if (attributes.Any(d => d is DescriptionAttribute))
-                        {
-                            entity.TableDescription =
-                                (attributes.First(d => d is DescriptionAttribute) as DescriptionAttribute)
-                                .Description;
-                        }
-                    }
-                },
-                MoreSettings = new ConnMoreSettings
-                {
-                    PgSqlIsAutoToLower = false,//增删查改支持驼峰表
-                    PgSqlIsAutoToLowerCodeFirst = false, // 建表建驼峰表。5.1.3.30 
-                }
-            },
-                SetDbAop
-            );
-
-            ISugarUnitOfWork<HotlineDbContext> context = new SugarUnitOfWork<HotlineDbContext>(sqlSugar);
-            services.AddSingleton(context);
-
-            InitDatabase(context, configuration);
-        }
-
-        private static void InitDatabase(ISugarUnitOfWork<HotlineDbContext> context, IConfiguration configuration)
-        {
-            var dbOptions = configuration.GetSection("DatabaseConfiguration").Get<DatabaseOptions>() ?? new DatabaseOptions();
-            if (dbOptions.ApplyDbMigrations)
-            {
-                context.Db.DbMaintenance.CreateDatabase();
-                
-                var types = AppDomain.CurrentDomain.GetAssemblies()
-                    .SelectMany(d => d.GetTypes())
-                    .Where(d => !d.IsInterface
-                                && !d.IsAbstract
-                                && d.IsClass
-                                && d.GetInterfaces().Any(x => x == typeof(ITable)))
-                    .ToArray();
-
-                context.Db.CodeFirst.InitTables(types);//根据types创建表
-            }
-
-            if (dbOptions.ApplySeed)
-            {
-                var allTypes = AppDomain.CurrentDomain.GetAssemblies()
-                    .SelectMany(d => d.GetTypes());
-
-                var seedDataTypes = allTypes.Where(d => !d.IsInterface && !d.IsAbstract && d.IsClass
-                                                        && d.HasImplementedOf(typeof(ISeedData<>)));
-
-                foreach (var seedType in seedDataTypes)
-                {
-                    var instance = Activator.CreateInstance(seedType);
-
-                    var hasDataMethod = seedType.GetMethod("HasData");
-                    var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
-                    if (seedData == null) continue;
-
-                    var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
-
-                    var entityInfo = context.Db.EntityMaintenance.GetEntityInfo(entityType);
-                    if (entityInfo.Columns.Any(d => d.IsPrimarykey))
-                    {
-                        var storage = context.Db.StorageableByObject(seedData.ToList()).ToStorage();
-                        storage.AsInsertable.ExecuteCommand();
-                    }
-                    else
-                    {
-                        // 无主键则只进行插入
-                        if (!context.Db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
-                            context.Db.InsertableByObject(seedData.ToList()).ExecuteCommand();
-                    }
-                }
-
-            }
-        }
-
-        #region private
-
-        private static void SetDbAop(SqlSugarClient db)
-        {
-            /***写AOP等方法***/
-            db.Aop.OnLogExecuting = (sql, pars) =>
-            {
-                //Log.Information("Sql: {0}", sql);
-                //Log.Information("SqlParameters: {0}", string.Join(',', pars.Select(d => d.Value)));
-            };
-            db.Aop.OnError = (exp) =>//SQL报错
-            {
-                //exp.sql 这样可以拿到错误SQL,性能无影响拿到ORM带参数使用的SQL
-                Log.Error("SqlError: {0}", exp.Sql);
-
-                //5.0.8.2 获取无参数化 SQL  对性能有影响,特别大的SQL参数多的,调试使用
-                //UtilMethods.GetSqlString(DbType.SqlServer,exp.sql,exp.parameters)           
-            };
-            //db.Aop.OnExecutingChangeSql = (sql, pars) => //可以修改SQL和参数的值
-            //{
-            //    //sql=newsql
-            //    //foreach (var p in pars) //修改
-            //    //{
-
-            //    //}
-
-            //    return new KeyValuePair<string, SugarParameter[]>(sql, pars);
-            //};
-
-            db.Aop.OnLogExecuted = (sql, p) =>
-            {
-                //执行时间超过1秒
-                if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
-                {
-                    //代码CS文件名
-                    var fileName = db.Ado.SqlStackTrace.FirstFileName;
-                    //代码行数
-                    var fileLine = db.Ado.SqlStackTrace.FirstLine;
-                    //方法名
-                    var FirstMethodName = db.Ado.SqlStackTrace.FirstMethodName;
-                    //db.Ado.SqlStackTrace.MyStackTraceList[1].xxx 获取上层方法的信息
-
-                    Log.Warning("slow query ==> fileName: {fileName}, fileLine: {fileLine}, FirstMethodName: {FirstMethodName}",
-                        fileName, fileLine, FirstMethodName);
-                    Log.Warning(UtilMethods.GetNativeSql(sql, p));
-                    Log.Warning("slow query totalSeconds: {sec}", db.Ado.SqlExecutionTime.TotalSeconds);
-                }
-                //相当于EF的 PrintToMiniProfiler
-            };
-
-            db.Aop.DataExecuting = (oldValue, entityInfo) =>
-            {
-                //inset生效
-                if (entityInfo.PropertyName == "CreationTime" && entityInfo.OperationType == DataFilterType.InsertByObject)
-                {
-                    if (oldValue is DateTime createTime)
-                    {
-                        if (createTime == DateTime.MinValue)
-                        {
-                            entityInfo.SetValue(DateTime.Now);//修改CreateTime字段
-                                                              //entityInfo有字段所有参数
-                        }
-                    }
-                }
-                //update生效        
-                else if (entityInfo.PropertyName == "LastModificationTime" && entityInfo.OperationType == DataFilterType.UpdateByObject)
-                {
-                    entityInfo.SetValue(DateTime.Now);//修改UpdateTime字段
-                }
-
-                //根据当前列修改另一列 可以么写
-                //if(当前列逻辑==XXX)
-                //var properyDate = entityInfo.EntityValue.GetType().GetProperty("Date");
-                //if(properyDate!=null)
-                //properyDate.SetValue(entityInfo.EntityValue,1);
-
-                else if (entityInfo.EntityColumnInfo.IsPrimarykey
-                         && entityInfo.EntityColumnInfo.PropertyName.ToLower() == "id"
-                         && entityInfo.OperationType == DataFilterType.InsertByObject
-                         && oldValue is null) //通过主键保证只进一次事件
-                {
-                    //var propertyId = entityInfo.EntityValue.GetType().GetProperty("Id");
-                    //if (propertyId is not null)
-                    //{
-                    //    var idValue = propertyId.GetValue(entityInfo.EntityValue);
-                    //    if (idValue is null)
-                    //        //这样每条记录就只执行一次 
-                    //        entityInfo.SetValue(SequentialStringGenerator.Create());
-                    //}
-                    entityInfo.SetValue(SequentialStringGenerator.Create(EStringFormat.Ulid));
-
-                }
-            };
-
-            SetDeletedEntityFilter(db);
-        }
-
-        private static void SetDeletedEntityFilter(SqlSugarClient db)
-        {
-            var cacheKey = $"DbFilter:{db.CurrentConnectionConfig.ConfigId}:IsDeleted";
-            var tableFilterItemList = db.DataCache.Get<List<TableFilterItem<object>>>(cacheKey);
-            if (tableFilterItemList == null)
-            {
-                // 获取基类实体数据表
-                var entityTypes = AppDomain.CurrentDomain.GetAssemblies()
-                    .SelectMany(d => d.GetTypes())
-                    .Where(d => !d.IsInterface
-                                && !d.IsAbstract
-                                && d.IsClass
-                                && d.GetInterfaces().Any(x => x == typeof(ISoftDelete)));
-                if (!entityTypes.Any()) return;
-
-                var tableFilterItems = new List<TableFilterItem<object>>();
-                foreach (var entityType in entityTypes)
-                {
-                    if (entityType.GetProperty("IsDeleted") is null) continue;
-                    //// 排除非当前数据库实体
-                    //var tAtt = entityType.GetCustomAttribute<TenantAttribute>();
-                    //if ((tAtt != null && (string)db.CurrentConnectionConfig.ConfigId != tAtt.configId.ToString()) ||
-                    //    (tAtt == null && (string)db.CurrentConnectionConfig.ConfigId != SqlSugarConst.ConfigId))
-                    //    continue;
-
-                    var lambda = DynamicExpressionParser.ParseLambda(new[] {
-                        Expression.Parameter(entityType, "d") },
-                    typeof(bool),
-                    $"{nameof(SoftDeleteEntity.IsDeleted)} == @0", false);
-                    var tableFilterItem = new TableFilterItem<object>(entityType, lambda);
-                    tableFilterItems.Add(tableFilterItem);
-                    db.QueryFilter.Add(tableFilterItem);
-                }
-                db.DataCache.Add(cacheKey, tableFilterItems);
-            }
-            else
-            {
-                tableFilterItemList.ForEach(u =>
-                {
-                    db.QueryFilter.Add(u);
-                });
-            }
-        }
-
-        #endregion
-    }
-}

+ 52 - 0
src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamRepository.cs

@@ -48,6 +48,43 @@ namespace Exam.Repository.Sqlsugar.Repositories
             await base.AddRangeAsync(entities, cancellationToken);
         }
 
+        public async Task ValidateAddAsync(List<TEntity> entities, CancellationToken cancellationToken)
+        {
+            await DoValidationAsync(entities, ValidatorTypeConstants.Create);
+        }
+
+        public async Task ValidateUpdateAsync(List<TEntity> entities, CancellationToken cancellationToken)
+        {
+            await DoValidationAsync(entities, ValidatorTypeConstants.Modify);
+        }
+
+        public async Task ValidateDeleteAsync(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken)
+        {
+            if (entityQueryRequest.Ids != null && entityQueryRequest.Ids.Any())
+            {
+                var entities = await base.QueryAsync(x => entityQueryRequest.Ids.Contains(x.Id));
+
+                await DoValidationAsync(entities, ValidatorTypeConstants.Remove);
+
+            }
+            else if (entityQueryRequest.Id.IsNotNullOrEmpty())
+            {
+                var entity = new TEntity();
+                entity.Id = entityQueryRequest.Id;
+
+                await DoValidationAsync(entity, ValidatorTypeConstants.Remove);
+            }
+            else
+            {
+                if (entityQueryRequest.Expression == null) return;
+
+                var entities = await base.QueryAsync((Expression<Func<TEntity, bool>>)entityQueryRequest.Expression);
+
+                await DoValidationAsync(entities, ValidatorTypeConstants.Remove);
+
+            }
+        }
+
         public async Task DeleteWithValidateAsync(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken)
         {          
 
@@ -139,6 +176,21 @@ namespace Exam.Repository.Sqlsugar.Repositories
                 throw new UserFriendlyException(ErrorMessage.RequestFail, string.Join(";", errorMessages));
             }
         }
+
+        public async Task ValidateAddAsync(TEntity entity, CancellationToken cancellationToken)
+        {
+            await DoValidationAsync(entity, ValidatorTypeConstants.Create);
+        }
+
+        public async Task ValidateUpdateAsync(TEntity entity, CancellationToken cancellationToken)
+        {
+            await DoValidationAsync(entity, ValidatorTypeConstants.Modify);
+        }
+
+        public async Task ValidateDeleteAsync(TEntity entity, CancellationToken cancellationToken)
+        {
+            await DoValidationAsync(entity, ValidatorTypeConstants.Remove);
+        }
         #endregion
     }
 }

+ 3 - 3
src/Hotline.Repository.SqlSugar/Exam/Validators/Questions/QuestionValidator.cs

@@ -35,9 +35,9 @@ namespace Exam.Repository.Sqlsugar.Validators.Questions
         {
             base.BaseValidateRule();
             RuleFor(m => m.Title).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(Question.Title))));
-            RuleFor(m => m.DifficultyLevel).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(Question.DifficultyLevel))));
-            RuleFor(m => m.FormalEnable).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(Question.FormalEnable))));
-            RuleFor(m => m.SimulateEnable).NotEmpty().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(Question.SimulateEnable))));
+            RuleFor(m => m.DifficultyLevel).NotNull().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(Question.DifficultyLevel))));
+            RuleFor(m => m.FormalEnable).NotNull().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(Question.FormalEnable))));
+            RuleFor(m => m.SimulateEnable).NotNull().WithMessage(x => string.Format(ErrorMessage.IsRequired, x.GetType().GetDescription(nameof(Question.SimulateEnable))));
 
         }
 

+ 42 - 1
src/Hotline.Repository.SqlSugar/Interface/IExamRepository.cs

@@ -1,4 +1,5 @@
 using Exam.Infrastructure.Data.Entity;
+using Exam.Infrastructure.Validation.Validation;
 using FluentValidation;
 using SqlSugar;
 using XF.Domain.Entities;
@@ -53,6 +54,46 @@ namespace Hotline.Repository.SqlSugar.Interface
         /// <returns></returns>
         public Task UpdateWithValidateAsync(List<TEntity> entities, CancellationToken cancellationToken);
 
-        
+        /// <summary>
+        /// 用于事务(验证新增实体)
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public  Task ValidateAddAsync(List<TEntity> entities, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 用于事务(验证修改实体)
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public  Task ValidateUpdateAsync(List<TEntity> entities, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 用于事务(验证删除实体)
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public  Task ValidateDeleteAsync(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 用于事务(验证新增实体)
+        /// </summary>
+        /// <param name="entity"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task ValidateAddAsync(TEntity entity, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 用于事务(验证修改实体)
+        /// </summary>
+        /// <param name="entity"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task ValidateUpdateAsync(TEntity entity, CancellationToken cancellationToken);
+
+
     }
 }

+ 199 - 7
src/Hotline.Repository.SqlSugar/Service/ApiService.cs

@@ -2,12 +2,16 @@
 using Exam.Infrastructure.Data.Interface;
 using FluentValidation;
 using Hotline.Repository.SqlSugar.Entitys;
+using Hotline.Repository.SqlSugar.Exam.Core.Constants;
 using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Repository.SqlSugar.Interface;
 using Mapster;
 using MapsterMapper;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 using SqlSugar;
+using System.Reflection;
 using XF.Domain.Entities;
+using XF.Domain.Repository;
 
 namespace Exam.Insfrastructure.Service.Service
 {
@@ -22,7 +26,12 @@ namespace Exam.Insfrastructure.Service.Service
 
         private AbstractValidator<T> _validator;
 
+        private bool _isStartTrain = false;
+
         private IMapper _mapper;
+
+        public T Entity { get; set; }
+
         public ApiService(IExamRepository<T, TDBContext> repository,IMapper mapper)
         {
             _repository = repository;
@@ -32,6 +41,144 @@ namespace Exam.Insfrastructure.Service.Service
         #endregion
 
         #region public method
+
+        public void StartTran()
+        {
+            _isStartTrain = true;
+        }
+
+        public InsertNavTaskInit<T,T> AddNav(T entity)
+        {
+            return _repository.AddNav(entity);
+        }
+
+        public UpdateNavTaskInit<T, T> UpdateNav(T entity)
+        {
+            return _repository.UpdateNav(entity);
+        }
+
+        public DeleteNavTaskInit<T, T> RemoveNav(T entity)
+        {
+            return _repository.RemoveNav(entity);
+        }
+
+        /// <summary>
+        /// TODO:暂时未使用
+        /// </summary>
+        /// <param name="entity"></param>
+        /// <param name="operationConstant"></param>
+        /// <returns></returns>
+        public async Task Complete(T entity,string operationConstant)
+        {
+            if (_isStartTrain)
+            {
+                switch (operationConstant)
+                {
+                    case OperationConstant.Create:
+                        await CompleteAdd(entity);
+                        break;
+                    case OperationConstant.Update:
+                        await CompleteUpdate(entity);
+                        break;
+                    case OperationConstant.Delete:
+                        await CompleteDelete(entity);
+                        break;
+                    default:
+                        await CompleteAdd(entity);
+                        break;
+                }
+             
+            }
+        }
+
+        protected virtual async Task CompleteDelete(T entity)
+        {
+            var result = _repository.RemoveNav(entity);
+            var memberInfos = typeof(T).GetMembers().Where(x => x.GetCustomAttribute<Navigate>() != null);
+
+            DeleteNavMethodInfo deleteNavMethodInfo = null;
+
+            foreach (var item in memberInfos)
+            {
+                var attribute = item.GetCustomAttribute<Navigate>();
+
+                if (attribute == null) continue;
+
+                if (deleteNavMethodInfo == null)
+                {
+                    deleteNavMethodInfo = result.IncludeByNameString(item.Name);
+                }
+                else
+                {
+                    deleteNavMethodInfo.IncludeByNameString(item.Name);
+                }
+
+            }
+
+            if (deleteNavMethodInfo == null) return;
+
+            await deleteNavMethodInfo.ExecuteCommandAsync();
+        }
+
+        protected virtual async Task CompleteUpdate(T entity)
+        {
+            var result = _repository.UpdateNav(entity);
+            var memberInfos = typeof(T).GetMembers().Where(x => x.GetCustomAttribute<Navigate>() != null);
+
+            UpdateNavMethodInfo updateNavMethodInfo = null;
+
+            foreach (var item in memberInfos)
+            {
+                var attribute = item.GetCustomAttribute<Navigate>();
+
+                if (attribute == null) continue;
+
+                if (updateNavMethodInfo == null)
+                {
+                    updateNavMethodInfo = result.IncludeByNameString(item.Name);
+                }
+                else
+                {
+                    updateNavMethodInfo.IncludeByNameString(item.Name);
+                }
+
+            }
+
+            if (updateNavMethodInfo == null) return;
+
+            await updateNavMethodInfo.ExecuteCommandAsync();
+
+        }
+
+        protected virtual async Task CompleteAdd(T entity)
+        {
+            var result = _repository.AddNav(entity);
+            var memberInfos = typeof(T).GetMembers().Where(x => x.GetCustomAttribute<Navigate>() != null);
+
+            InsertNavMethodInfo insertNavMethodInfo = null;
+
+            foreach (var item in memberInfos)
+            {
+                var attribute = item.GetCustomAttribute<Navigate>();
+
+                if (attribute == null) continue;
+
+                if (insertNavMethodInfo == null)
+                {
+                    insertNavMethodInfo = result.IncludeByNameString(item.Name);
+                }
+                else
+                {
+                    insertNavMethodInfo.IncludeByNameString(item.Name);
+                }
+
+            }
+
+            if (insertNavMethodInfo == null) return;
+
+            await insertNavMethodInfo.ExecuteCommandAsync();
+        }
+
         /// <summary>
         /// 单表新增
         /// </summary>
@@ -44,6 +191,15 @@ namespace Exam.Insfrastructure.Service.Service
 
             entity.ToInsert();
 
+            if (_isStartTrain)
+            {
+                await _repository.ValidateAddAsync(entity, cancellationToken);
+
+                Entity = entity;
+
+                return entity.Id;
+            }
+
             return await _repository.AddWithValidateAsync(entity, cancellationToken);
         }
 
@@ -59,7 +215,14 @@ namespace Exam.Insfrastructure.Service.Service
 
             entities.ToInsert();
 
-            await _repository.AddWithValidateAsync(entities, cancellationToken);
+            if (_isStartTrain)
+            {
+                await _repository.ValidateAddAsync(entities, cancellationToken);
+            }
+            else
+            {
+                await _repository.AddWithValidateAsync(entities, cancellationToken);
+            }
         }
 
         /// <summary>
@@ -69,8 +232,15 @@ namespace Exam.Insfrastructure.Service.Service
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
         public virtual async Task DeleteAsync(EntityQueryRequest entityQueryRequest, CancellationToken cancellationToken)
-        {           
-            await _repository.DeleteWithValidateAsync(entityQueryRequest,cancellationToken);
+        {
+            if (_isStartTrain)
+            {
+                await _repository.ValidateDeleteAsync(entityQueryRequest, cancellationToken);
+            }
+            else
+            {
+                await _repository.DeleteWithValidateAsync(entityQueryRequest, cancellationToken);
+            }
         }
 
         /// <summary>
@@ -87,7 +257,16 @@ namespace Exam.Insfrastructure.Service.Service
 
             entity.ToUpdate();
 
-            await _repository.UpdateWithValidateAsync(entity, cancellationToken);
+            if (_isStartTrain)
+            {
+                await _repository.ValidateUpdateAsync(entity, cancellationToken);
+
+                Entity = entity;
+            }
+            else
+            {
+                await _repository.UpdateWithValidateAsync(entity, cancellationToken);
+            }
         }
 
         /// <summary>
@@ -104,8 +283,14 @@ namespace Exam.Insfrastructure.Service.Service
             entities = _mapper.Map<List<TUpdate>, List<T>>(actionRequests,entities);
 
             entities.ToUpdate();
-
-            await _repository.UpdateWithValidateAsync(entities, cancellationToken);
+            if (_isStartTrain)
+            {
+                await _repository.ValidateUpdateAsync(entities, cancellationToken);
+            }
+            else
+            {
+                await _repository.UpdateWithValidateAsync(entities, cancellationToken);
+            }
         }
 
         /// <summary>
@@ -123,7 +308,14 @@ namespace Exam.Insfrastructure.Service.Service
 
             entities.ToUpdate();
 
-            await _repository.UpdateWithValidateAsync(entities, cancellationToken);
+            if (_isStartTrain)
+            {
+                await _repository.ValidateUpdateAsync(entities, cancellationToken);
+            }
+            else
+            {
+                await _repository.UpdateWithValidateAsync(entities, cancellationToken);
+            }
         }
         #endregion
     }

+ 12 - 9
src/Hotline.Share/Dtos/TestPapers/TestPaperRuleDto.cs

@@ -15,7 +15,11 @@ namespace Hotline.Share.Dtos.TestPapers
     [Description("试卷规则")]
     public class TestPaperRuleDto : UpdateTestPaperRuleDto
     {
-       
+        /// <summary>
+        /// 组卷规则标签
+        /// </summary>
+        [Description("组卷规则标签")]
+        public new List<TestPaperRuleTagDto> TestPaperRuleTagDtos { get; set; }
 
     }
 
@@ -45,12 +49,6 @@ namespace Hotline.Share.Dtos.TestPapers
         [Description("试题数量")]
         public int Count { get; set; }
 
-        /// <summary>
-        /// 标签Id
-        /// </summary>
-        [Description("标签Id")]
-        public string TagId { get; set; }
-
         /// <summary>
         /// 排序
         /// </summary>
@@ -61,8 +59,7 @@ namespace Hotline.Share.Dtos.TestPapers
         /// 组卷规则标签
         /// </summary>
         [Description("组卷规则标签")]
-        [JsonIgnore]
-        public TestPaperRuleTagDto TestPaperRuleTagDto { get; set; }
+        public List<AddTestPaperRuleTagDto> TestPaperRuleTagDtos { get; set; }
 
         /// <summary>
         /// 操作状态        
@@ -78,5 +75,11 @@ namespace Hotline.Share.Dtos.TestPapers
         /// </summary>
         [Description("主键")]
         public string Id { get; set; }
+
+        /// <summary>
+        /// 组卷规则标签
+        /// </summary>
+        [Description("组卷规则标签")]
+        public new List<UpdateTestPaperRuleTagDto> TestPaperRuleTagDtos { get; set; }
     }
 }

+ 2 - 0
src/Hotline.Share/Dtos/TestPapers/TestPaperRuleTagDto.cs

@@ -3,6 +3,7 @@ using Exam.Infrastructure.Data.Interface;
 using Exam.Infrastructure.Enums;
 using Hotline.Share.Exams.Interface;
 using System.ComponentModel;
+using System.Text.Json.Serialization;
 
 namespace Hotline.Share.Dtos.TestPapers
 {
@@ -37,6 +38,7 @@ namespace Hotline.Share.Dtos.TestPapers
         /// 操作状态        
         /// </summary>
         [Description("操作状态")]
+        [JsonIgnore]
         public EEOperationStatus OperationStatus { get; set; }
     }
 

+ 13 - 0
src/Hotline.Share/Requests/Exam/GenerateTestPaperRequest.cs

@@ -0,0 +1,13 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Requests.Exam
+{
+    public class GenerateTestPaperRequest
+    {
+        /// <summary>
+        /// 试卷Id
+        /// </summary>
+        [Description("试卷Id")]
+        public string TestPaperId { get; set; }
+    }
+}

+ 15 - 0
src/Hotline/Exams/Questions/Question.cs

@@ -60,5 +60,20 @@ namespace Exam.Questions
         [SugarColumn(ColumnDescription ="权重")]
         [Description("权重")]
         public int Weight { get; set; }
+
+        [Navigate(NavigateType.OneToMany,nameof(QuestionOptions.QuestionId))]
+        public List<QuestionOptions> QuestionOptionses { get; set; }
+
+        [Navigate(NavigateType.OneToMany,nameof(QuestionKnowladge.QuestionId))]
+        public List<QuestionKnowladge> QuestionKnowladges { get; set; }
+
+        [Navigate(NavigateType.OneToMany,nameof(QuestionSourceware.QuestionId))]
+        public List<QuestionSourceware> QuestionSourcewares { get; set; }
+
+        [Navigate(NavigateType.OneToMany, nameof(QuestionAnswer.QuestionId))]
+        public QuestionAnswer QuestionAnswerE { get; set; }
+
+        [Navigate(NavigateType.OneToMany, nameof(QuestionTag.QuestionId))]
+        public List<QuestionTag> QuestionTags { get; set; }
     }
 }

+ 9 - 0
src/Hotline/Exams/TestPapers/TestPaperItemAnswer.cs

@@ -1,4 +1,5 @@
 using Hotline.Exams.Base;
+using Hotline.Share.Dtos.Order;
 using SqlSugar;
 using System;
 using System.Collections.Generic;
@@ -26,6 +27,7 @@ namespace Hotline.Exams.TestPapers
         /// 题库Id
         /// </summary>
         [Description("题库Id")]
+        [SugarColumn(ColumnDescription = "题库Id")]
         public string QuestionId { get; set; }
 
         /// <summary>
@@ -34,5 +36,12 @@ namespace Hotline.Exams.TestPapers
         [SugarColumn(ColumnDescription = "答案")]
         [Description("答案")]
         public string? Answer { get; set; }
+
+        /// <summary>
+        /// 题库答案Id
+        /// </summary>
+        [SugarColumn(ColumnDescription = "题库答案Id")]
+        [Description("题库答案Id")]
+        public string QueswerAnswerId { get; set; }
     }
 }

+ 7 - 0
src/Hotline/Exams/TestPapers/TestPaperItemKnowladge.cs

@@ -30,5 +30,12 @@ namespace Exam.Questions
         [SugarColumn(ColumnDescription = "标题")]
         [Description("标题")]
         public string Title { get; set; }
+
+        /// <summary>
+        /// 试卷明细Id
+        /// </summary>
+        [SugarColumn(ColumnDescription = "试卷明细Id")]
+        [Description("试卷明细Id")]
+        public string TestPaperItemId { get; set; }
     }
 }

+ 7 - 0
src/Hotline/Exams/TestPapers/TestPaperItemSourceware.cs

@@ -23,5 +23,12 @@ namespace Exam.Questions
         [SugarColumn(ColumnDescription = "课件Id")]
         [Description("课件Id")]
         public string SourcewareId { get; set; }
+
+        /// <summary>
+        /// 试卷明细Id
+        /// </summary>
+        [SugarColumn(ColumnDescription = "试卷明细Id")]
+        [Description("试卷明细Id")]
+        public string TestPaperItemId { get; set; }
     }
 }

+ 1 - 1
src/Hotline/Snapshot/CommunityInfo.cs

@@ -33,7 +33,7 @@ public class CommunityInfo : CreationSoftDeleteEntity
     /// 父社区Code
     /// </summary>
     [SugarColumn(ColumnDescription = "父社区Code")]
-    public string ParentCode { get; set; }
+    public string? ParentCode { get; set; }
 
     /// <summary>
     /// 社区唯一