1、事务管理 在 C# .NET ASP.NET Core 中,确保事务一致性主要通过数据库事务机制实现,确保多个操作要么全部成功,要么全部失败。以下是常见的实现方式及其示例:
1. Entity Framework Core 事务(推荐) 方式 1:使用 DbContext.Database.BeginTransaction()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using (var transaction = context.Database.BeginTransaction()){ try { context.Orders.Add(order); await context.SaveChangesAsync(); product.Stock -= order.Quantity; context.Products.Update(product); await context.SaveChangesAsync(); transaction.Commit(); } catch { transaction.Rollback(); throw ; } }
方式 2:使用 ExecutionStrategy(自动重试)
适用于分布式数据库(如 Azure SQL),支持瞬态错误重试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var strategy = context.Database.CreateExecutionStrategy();await strategy.ExecuteAsync(async () =>{ using (var transaction = context.Database.BeginTransaction()) { await context.Orders.AddAsync(order); await context.SaveChangesAsync(); product.Stock -= order.Quantity; context.Products.Update(product); await context.SaveChangesAsync(); transaction.Commit(); } });
2. 使用 TransactionScope(跨资源事务) 适用于跨多个数据库或外部服务的分布式事务(需启用 MSDTC 服务):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)){ try { await dbContextA.SaveChangesAsync(); await dbContextB.SaveChangesAsync(); await httpClient.PostAsync("api/payment" , ...); scope.Complete(); } catch { throw ; } }
注意 :SQLite 不支持分布式事务,SQL Server 需配置 MSDTC。
3. 基于仓储模式的 Unit of Work(UoW) 通过封装事务逻辑,统一管理多个仓储操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class UnitOfWork : IUnitOfWork { private readonly AppDbContext _context; public UnitOfWork (AppDbContext context ) => _context = context; public async Task CommitAsync () { using (var transaction = await _context.Database.BeginTransactionAsync()) { try { await _context.SaveChangesAsync(); await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw ; } } } } public class OrderService { private readonly IUnitOfWork _uow; public OrderService (IUnitOfWork uow ) => _uow = uow; public async Task CreateOrderAsync (Order order ) { await _orderRepository.AddAsync(order); await _inventoryRepository.UpdateStockAsync(...); await _uow.CommitAsync(); } }
4. 显式 ADO.NET 事务 直接使用 SqlTransaction 控制事务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 using (var connection = new SqlConnection(connectionString)){ await connection.OpenAsync(); using (var transaction = connection.BeginTransaction()) { try { using (var cmd1 = new SqlCommand("INSERT INTO Orders ..." , connection, transaction)) { await cmd1.ExecuteNonQueryAsync(); } using (var cmd2 = new SqlCommand("UPDATE Products SET Stock ..." , connection, transaction)) { await cmd2.ExecuteNonQueryAsync(); } await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw ; } } }
5.事务一致性形式总结
形式
适用场景
优点
缺点
EF Core 事务(BeginTransaction)
单个数据库操作
简单、与 EF Core 深度集成
仅限单个 DbContext
TransactionScope
跨数据库/外部服务
支持分布式事务
需 MSDTC、性能开销大
Unit of Work (UoW)
领域驱动设计(DDD)、复杂业务逻辑
高内聚、易测试
需额外抽象层
ADO.NET 显式事务
原生 SQL 操作
完全控制、高性能
代码冗余、需手动管理连接
2、SetmealController ProgramBackEnd\SkyServer\controller\admin\SetmealController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 using Microsoft.AspNetCore.Mvc;using ProgramBackEnd.SkyCommon.result;using ProgramBackEnd.SkyPojo.dto;using ProgramBackEnd.SkyPojo.vo;using ProgramBackEnd.SkyServer.service;using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;namespace ProgramBackEnd.SkyServer.controller.admin { [Route("/admin/setmeal" ) ] [ApiController ] [ApiExplorerSettings(GroupName = "套餐相关接口" ) ] public class SetmealController : ControllerBase { private readonly ISetmealService _setmealService; private readonly ILogger<SetmealController> _logger; public SetmealController (ISetmealService setmealService, ILogger<SetmealController> logger ) { _setmealService = setmealService ?? throw new ArgumentNullException(nameof (setmealService)); _logger = logger ?? throw new ArgumentNullException(nameof (logger)); } [HttpPost ] public async Task<Result<string >> Save([FromBody] SetmealDTO setmealDTO) { _logger.LogInformation("开始新增套餐: {@SetmealDTO}" , new { setmealDTO.Id, setmealDTO.Name, setmealDTO.Price, DishCount = setmealDTO.SetmealDishes?.Count ?? 0 }); await _setmealService.SaveWithDish(setmealDTO); _logger.LogInformation("套餐新增成功: {SetmealName}" , setmealDTO.Name); return Result<string >.Success("套餐添加成功" ); } [HttpGet("page" ) ] public async Task<Result<PageResult>> Page([FromQuery] SetmealPageQueryDTO setmealPageQueryDTO) { _logger.LogInformation("分页查询套餐: 第{Page}页,每页{PageSize}条" , setmealPageQueryDTO.Page, setmealPageQueryDTO.PageSize); PageResult pageResult = await _setmealService.PageQuery(setmealPageQueryDTO); _logger.LogInformation("分页查询套餐完成: 共{Total}条记录" , pageResult.Total); return Result<PageResult>.Success(pageResult); } [HttpDelete ] public async Task<Result<string >> Delete([FromQuery] string ids) { List<long > idList = ids.Split(',' ) .Where(s => !string .IsNullOrWhiteSpace(s)) .Select(s => long .Parse(s.Trim())) .ToList(); _logger.LogInformation("开始删除套餐,ID列表: {IDs}" , string .Join("," , idList)); await _setmealService.DeleteBatch(idList); _logger.LogInformation("套餐删除成功,ID列表: {IDs}" , ids); return Result<string >.Success("套餐删除成功" ); } [HttpGet("{id}" ) ] public async Task<Result<SetmealVO>> GetById([FromRoute] long id) { _logger.LogInformation("根据ID查询套餐: {ID}" , id); SetmealVO setmealVO = await _setmealService.GetByIdWithDish(id); _logger.LogInformation("查询套餐成功: {ID}, 名称: {Name}" , id, setmealVO.Name); return Result<SetmealVO>.Success(setmealVO); } [HttpPut ] public async Task<Result<string >> Update([FromBody] SetmealDTO setmealDTO) { _logger.LogInformation("开始修改套餐,ID: {ID}, 名称: {Name}" , setmealDTO.Id, setmealDTO.Name); await _setmealService.Update(setmealDTO); _logger.LogInformation("套餐修改成功,ID: {ID}" , setmealDTO.Id); return Result<string >.Success("套餐修改成功" ); } [HttpPost("status/{status}" ) ] public async Task<Result<string >> StartOrStop([FromRoute] int status, [FromQuery] long id) { _logger.LogInformation("开始更新套餐状态,ID: {ID}, 状态: {Status}" , id, status == 1 ? "起售" : "停售" ); await _setmealService.StartOrStop(status, id); string message = status == 1 ? "套餐起售成功" : "套餐停售成功" ; _logger.LogInformation("套餐状态更新成功,ID: {ID}, 状态: {Status}" , id, status == 1 ? "起售" : "停售" ); return Result<string >.Success(message); } } }
3、SetmealService 1、ISetmealService ProgramBackEnd\SkyServer\service\ISetmealService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 using ProgramBackEnd.SkyCommon.result;using ProgramBackEnd.SkyPojo.dto;using ProgramBackEnd.SkyPojo.vo;using System;using System.Collections.Generic;using System.Threading.Tasks;namespace ProgramBackEnd.SkyServer.service { public interface ISetmealService { Task DeleteBatch (List<long > ids ) ; Task<SetmealVO> GetByIdWithDish (long id ) ; Task<PageResult> PageQuery (SetmealPageQueryDTO setmealPageQueryDTO ) ; Task SaveWithDish (SetmealDTO setmealDTO ) ; Task StartOrStop (int status, long id ) ; Task Update (SetmealDTO setmealDTO ) ; } }
2、SetmealServiceImpl ProgramBackEnd\SkyServer\service\Impl\SetmealServiceImpl.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 using Microsoft.EntityFrameworkCore;using ProgramBackEnd.SkyCommon.context;using ProgramBackEnd.SkyCommon.exception;using ProgramBackEnd.SkyCommon.result;using ProgramBackEnd.SkyCommon.utils;using ProgramBackEnd.SkyPojo.dto;using ProgramBackEnd.SkyPojo.entity;using ProgramBackEnd.SkyPojo.vo;using ProgramBackEnd.SkyServer.config;using ProgramBackEnd.SkyServer.mapper;using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;namespace ProgramBackEnd.SkyServer.service.Impl { public class SetmealServiceImpl : ISetmealService { private readonly ISetmealMapper _setmealMapper; private readonly ISetmealDishMapper _setmealDishMapper; private readonly IDishMapper _dishMapper; private readonly SkyDbContext _skyDbContext; public SetmealServiceImpl ( ISetmealMapper setmealMapper, ISetmealDishMapper setmealDishMapper, IDishMapper dishMapper, SkyDbContext skyDbContext ) { _setmealMapper = setmealMapper ?? throw new ArgumentNullException(nameof (setmealMapper)); _setmealDishMapper = setmealDishMapper ?? throw new ArgumentNullException(nameof (setmealDishMapper)); _dishMapper = dishMapper ?? throw new ArgumentNullException(nameof (dishMapper)); _skyDbContext = skyDbContext ?? throw new ArgumentNullException(nameof (skyDbContext)); } public async Task DeleteBatch (List<long > ids ) { if (ids == null || ids.Count == 0 ) { return ; } using var transaction = await _skyDbContext.Database.BeginTransactionAsync(); try { foreach (long id in ids) { Setmeal setmeal = await _setmealMapper.GetById(id); if (setmeal != null && setmeal.Status == 1 ) { throw new BaseException("起售中的套餐不能删除" ); } } foreach (long setmealId in ids) { await _setmealMapper.DeleteById(setmealId); await _setmealDishMapper.DeleteBySetmealId(setmealId); } await transaction.CommitAsync(); } catch (Exception ex) { await transaction.RollbackAsync(); if (ex is BaseException) { throw ; } else { throw new Exception($"批量删除套餐失败: {ex.Message} " , ex); } } } public async Task<SetmealVO> GetByIdWithDish (long id ) { try { var setmeal = await _setmealMapper.GetById(id); if (setmeal == null ) { throw new BaseException($"套餐不存在:ID {id} " ); } SetmealVO setmealVO = new SetmealVO(); PropertyUtil.CopyProperties(setmeal, setmealVO); var setmealDishes = await _skyDbContext.SetmealDishes .Where(sd => sd.SetmealId == id) .ToListAsync(); setmealVO.SetmealDishes = setmealDishes ?? new List<SetmealDish>(); if (setmeal.CategoryId > 0 ) { var category = await _skyDbContext.Categories.FindAsync(setmeal.CategoryId); if (category != null ) { setmealVO.CategoryName = category.Name; } } return setmealVO; } catch (BaseException) { throw ; } catch (Exception ex) { throw new Exception($"查询套餐详情失败: {ex.Message} " , ex); } } public async Task<PageResult> PageQuery (SetmealPageQueryDTO setmealPageQueryDTO ) { if (setmealPageQueryDTO == null ) { throw new ArgumentNullException(nameof (setmealPageQueryDTO), "分页查询参数不能为空" ); } try { int page = setmealPageQueryDTO.Page; int pageSize = setmealPageQueryDTO.PageSize; if (page <= 0 ) page = 1 ; if (pageSize <= 0 ) pageSize = 10 ; (long total, List<SetmealVO> setmealVOList) = await _setmealMapper.PageQuery(setmealPageQueryDTO); return new PageResult(total, setmealVOList); } catch (Exception ex) { throw new Exception($"分页查询套餐数据失败: {ex.Message} " , ex); } } public async Task SaveWithDish (SetmealDTO setmealDTO ) { if (setmealDTO == null ) { throw new ArgumentNullException(nameof (setmealDTO), "套餐数据不能为空" ); } using var transaction = await _skyDbContext.Database.BeginTransactionAsync(); try { Setmeal setmeal = new Setmeal(); PropertyUtil.CopyProperties(setmealDTO, setmeal); var now = DateTime.Now; long userId = BaseContext.GetCurrentId() ?? 0 ; setmeal.CreateTime = now; setmeal.UpdateTime = now; setmeal.CreateUser = userId; setmeal.UpdateUser = userId; await _setmealMapper.Insert(setmeal); long setmealId = setmeal.Id; if (setmealDTO.SetmealDishes != null && setmealDTO.SetmealDishes.Count > 0 ) { foreach (var dish in setmealDTO.SetmealDishes) { dish.SetmealId = setmealId; } await _setmealDishMapper.InsertBatch(setmealDTO.SetmealDishes); } await transaction.CommitAsync(); } catch (Exception ex) { await transaction.RollbackAsync(); throw new Exception($"保存套餐失败: {ex.Message} " , ex); } } public async Task StartOrStop (int status, long id ) { if (status != 0 && status != 1 ) { throw new ArgumentException("状态值必须为0(停售)或1(起售)" , nameof (status)); } using var transaction = await _skyDbContext.Database.BeginTransactionAsync(); try { if (status == 1 ) { var setmealDishes = await _skyDbContext.SetmealDishes .Where(sd => sd.SetmealId == id) .ToListAsync(); if (setmealDishes != null && setmealDishes.Count > 0 ) { var dishIds = setmealDishes.Select(sd => sd.DishId).ToList(); var dishes = await _skyDbContext.Dishes .Where(d => dishIds.Contains(d.Id)) .ToListAsync(); foreach (var dish in dishes) { if (dish.Status == 0 ) { throw new BaseException("套餐内包含未启售菜品,无法启售" ); } } } } Setmeal setmeal = new Setmeal { Id = id, Status = status, UpdateTime = DateTime.Now, UpdateUser = BaseContext.GetCurrentId() ?? 0 }; await _setmealMapper.Update(setmeal); await transaction.CommitAsync(); } catch (BaseException) { await transaction.RollbackAsync(); throw ; } catch (Exception ex) { await transaction.RollbackAsync(); string action = status == 1 ? "起售" : "停售" ; throw new Exception($"套餐{action} 失败: {ex.Message} " , ex); } } public async Task Update (SetmealDTO setmealDTO ) { if (setmealDTO == null ) { throw new ArgumentNullException(nameof (setmealDTO), "套餐数据不能为空" ); } if (setmealDTO.Id <= 0 ) { throw new ArgumentException("套餐ID无效" , nameof (setmealDTO)); } using var transaction = await _skyDbContext.Database.BeginTransactionAsync(); try { Setmeal setmeal = new Setmeal(); PropertyUtil.CopyProperties(setmealDTO, setmeal); setmeal.UpdateTime = DateTime.Now; setmeal.UpdateUser = BaseContext.GetCurrentId() ?? 0 ; await _setmealMapper.Update(setmeal); long setmealId = setmealDTO.Id; await _setmealDishMapper.DeleteBySetmealId(setmealId); if (setmealDTO.SetmealDishes != null && setmealDTO.SetmealDishes.Count > 0 ) { foreach (var dish in setmealDTO.SetmealDishes) { dish.SetmealId = setmealId; } await _setmealDishMapper.InsertBatch(setmealDTO.SetmealDishes); } await transaction.CommitAsync(); } catch (Exception ex) { await transaction.RollbackAsync(); if (ex is ArgumentException || ex is ArgumentNullException) { throw ; } else { throw new Exception($"更新套餐失败: {ex.Message} " , ex); } } } } }
4、SetmealMapper 1、ISetmealMapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 using ProgramBackEnd.SkyPojo.dto;using ProgramBackEnd.SkyPojo.entity;using ProgramBackEnd.SkyPojo.vo;using System;using System.Collections.Generic;using System.Threading.Tasks;namespace ProgramBackEnd.SkyServer.mapper { public interface ISetmealMapper { Task<int > CountByCategoryId (long id ) ; Task DeleteById (long setmealId ) ; Task<Setmeal> GetById (long id ) ; Task Insert (Setmeal setmeal ) ; Task<(long total, List<SetmealVO> setmealVOList)> PageQuery(SetmealPageQueryDTO setmealPageQueryDTO); Task Update (Setmeal setmeal ) ; } }
2、SetmealMapperImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 using Microsoft.EntityFrameworkCore;using ProgramBackEnd.SkyPojo.dto;using ProgramBackEnd.SkyPojo.entity;using ProgramBackEnd.SkyPojo.vo;using ProgramBackEnd.SkyServer.config;using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;namespace ProgramBackEnd.SkyServer.mapper.Impl { public class SetmealMapperImpl : ISetmealMapper { private readonly SkyDbContext _skyDbContext; public SetmealMapperImpl (SkyDbContext skyDbContext ) { _skyDbContext = skyDbContext ?? throw new ArgumentNullException(nameof (skyDbContext)); } public async Task<int > CountByCategoryId (long id ) { try { int total = await _skyDbContext.Setmeals .Where(s => s.CategoryId == id) .CountAsync(); return total; } catch (Exception ex) { throw new InvalidOperationException( $"统计分类ID为{id} 的套餐数量时发生错误: {ex.Message} " , ex); } } public async Task DeleteById (long setmealId ) { try { var setmeal = await _skyDbContext.Setmeals.FindAsync(setmealId); if (setmeal != null ) { _skyDbContext.Setmeals.Remove(setmeal); await _skyDbContext.SaveChangesAsync(); } } catch (Exception ex) { throw new InvalidOperationException($"删除套餐(ID:{setmealId} )时发生错误: {ex.Message} " , ex); } } public async Task<Setmeal> GetById (long id ) { try { var setmeal = await _skyDbContext.Setmeals.FindAsync(id); if (setmeal == null ) { throw new InvalidOperationException($"未找到ID为{id} 的套餐" ); } return setmeal; } catch (Exception ex) when (!(ex is InvalidOperationException && ex.Message.StartsWith("未找到ID" ))) { throw new InvalidOperationException($"查询套餐(ID:{id} )时发生错误: {ex.Message} " , ex); } } public async Task Insert (Setmeal setmeal ) { if (setmeal == null ) { throw new ArgumentNullException(nameof (setmeal), "套餐对象不能为null" ); } try { await _skyDbContext.Setmeals.AddAsync(setmeal); await _skyDbContext.SaveChangesAsync(); } catch (Exception ex) { throw new InvalidOperationException($"添加套餐记录时发生错误: {ex.Message} " , ex); } } public async Task<(long total, List<SetmealVO> setmealVOList)> PageQuery(SetmealPageQueryDTO setmealPageQueryDTO) { if (setmealPageQueryDTO == null ) { throw new ArgumentNullException(nameof (setmealPageQueryDTO), "查询参数不能为null" ); } try { var query = from s in _skyDbContext.Setmeals join c in _skyDbContext.Categories on s.CategoryId equals c.Id into categoryJoin from category in categoryJoin.DefaultIfEmpty() select new SetmealVO { Id = s.Id, CategoryId = s.CategoryId, Name = s.Name, Price = s.Price, Status = s.Status, Description = s.Description, Image = s.Image, UpdateTime = s.UpdateTime, CategoryName = category.Name }; if (!string .IsNullOrEmpty(setmealPageQueryDTO.Name)) { query = query.Where(s => s.Name != null && s.Name.Contains(setmealPageQueryDTO.Name)); } if (setmealPageQueryDTO.Status.HasValue) { query = query.Where(s => s.Status == setmealPageQueryDTO.Status.Value); } if (setmealPageQueryDTO.CategoryId.HasValue && setmealPageQueryDTO.CategoryId.Value > 0 ) { query = query.Where(s => s.CategoryId == setmealPageQueryDTO.CategoryId.Value); } query = query.OrderByDescending(s => s.UpdateTime); long total = await query.CountAsync(); int page = Math.Max(1 , setmealPageQueryDTO.Page); int pageSize = Math.Max(1 , setmealPageQueryDTO.PageSize); List<SetmealVO> setmealVOList = await query .Skip((page - 1 ) * pageSize) .Take(pageSize) .ToListAsync(); return (total, setmealVOList); } catch (Exception ex) { throw new InvalidOperationException($"分页查询套餐信息失败: {ex.Message} " , ex); } } public async Task Update (Setmeal setmeal ) { if (setmeal == null ) { throw new ArgumentNullException(nameof (setmeal), "套餐对象不能为null" ); } try { var existingSetmeal = await _skyDbContext.Setmeals.FindAsync(setmeal.Id); if (existingSetmeal == null ) { throw new InvalidOperationException($"未找到ID为{setmeal.Id} 的套餐" ); } if (setmeal.Name != null ) existingSetmeal.Name = setmeal.Name; if (setmeal.Description != null ) existingSetmeal.Description = setmeal.Description; if (setmeal.Image != null ) existingSetmeal.Image = setmeal.Image; if (setmeal.CategoryId != 0 ) existingSetmeal.CategoryId = setmeal.CategoryId; if (setmeal.Price != 0 ) existingSetmeal.Price = setmeal.Price; if (setmeal.Status.HasValue) existingSetmeal.Status = setmeal.Status; if (setmeal.UpdateTime != default ) existingSetmeal.UpdateTime = setmeal.UpdateTime; if (setmeal.UpdateUser != 0 ) existingSetmeal.UpdateUser = setmeal.UpdateUser; await _skyDbContext.SaveChangesAsync(); } catch (Exception ex) when (!(ex is InvalidOperationException && ex.Message.StartsWith("未找到ID" ))) { throw new InvalidOperationException( $"更新套餐(ID:{setmeal.Id} )时发生错误: {ex.Message} " , ex); } } } }
5、SetmealDishMapper 1、ISetmealDishMapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 using ProgramBackEnd.SkyPojo.entity;using System;using System.Collections.Generic;using System.Threading.Tasks;namespace ProgramBackEnd.SkyServer.mapper { public interface ISetmealDishMapper { Task DeleteBySetmealId (long setmealId ) ; Task<List<long >> GetSetmealIdsByDishIds(List<long > ids); Task InsertBatch (List<SetmealDish> setmealDishes ) ; } }
2、SetmealDishMapperImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 using Microsoft.EntityFrameworkCore;using ProgramBackEnd.SkyPojo.entity;using ProgramBackEnd.SkyServer.config;using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;namespace ProgramBackEnd.SkyServer.mapper.Impl { public class SetmealDishMapperImpl : ISetmealDishMapper { private readonly SkyDbContext _skyDbContext; public SetmealDishMapperImpl (SkyDbContext skyDbContext ) { _skyDbContext = skyDbContext ?? throw new ArgumentNullException(nameof (skyDbContext)); } public async Task DeleteBySetmealId (long setmealId ) { try { var relationsToDelete = await _skyDbContext.SetmealDishes .Where(sd => sd.SetmealId == setmealId) .ToListAsync(); if (relationsToDelete.Any()) { _skyDbContext.SetmealDishes.RemoveRange(relationsToDelete); await _skyDbContext.SaveChangesAsync(); } } catch (DbUpdateException ex) { throw new InvalidOperationException( $"删除套餐(ID:{setmealId} )的关联菜品时发生数据库错误: {ex.Message} " , ex); } catch (Exception ex) { throw new InvalidOperationException( $"删除套餐(ID:{setmealId} )和菜品的关联关系时发生错误: {ex.Message} " , ex); } } public async Task<List<long >> GetSetmealIdsByDishIds(List<long > ids) { if (ids == null || ids.Count == 0 ) { return new List<long >(); } try { var query = _skyDbContext.SetmealDishes .Where(sd => ids.Contains(sd.DishId)) .Select(sd => sd.SetmealId) .Distinct(); var setmealIds = await query.ToListAsync(); return setmealIds; } catch (Exception ex) { string idList = string .Join("," , ids); throw new InvalidOperationException( $"查询包含指定菜品(IDs:{idList} )的套餐时发生错误: {ex.Message} " , ex); } } public async Task InsertBatch (List<SetmealDish> setmealDishes ) { if (setmealDishes == null ) { throw new ArgumentNullException(nameof (setmealDishes), "套餐菜品关系集合不能为null" ); } if (setmealDishes.Count == 0 ) { return ; } try { await _skyDbContext.SetmealDishes.AddRangeAsync(setmealDishes); int affectedRows = await _skyDbContext.SaveChangesAsync(); if (affectedRows != setmealDishes.Count) { } } catch (DbUpdateException ex) { throw new InvalidOperationException( $"批量插入套餐菜品关系时发生数据库错误: {ex.Message} " , ex); } catch (Exception ex) { throw new InvalidOperationException( $"批量插入{setmealDishes.Count} 条套餐菜品关系数据失败: {ex.Message} " , ex); } } } }
6、结果展示 1、增
2、改
3、查
4、删
6、店铺状态设定 设定店铺状态通过Redis实现
1、ShopController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 using Microsoft.AspNetCore.Mvc;using Microsoft.Extensions.Logging;using Microsoft.Extensions.Caching.Distributed;using ProgramBackEnd.SkyCommon.result;using System;using System.Threading.Tasks;using System.Text;using System.Text.Json;namespace ProgramBackEnd.SkyServer.controller.admin { [Route("/admin/shop" ) ] [ApiController ] [ApiExplorerSettings(GroupName = "店铺相关接口" ) ] public class ShopController : ControllerBase { private const string KEY = "SHOP_STATUS" ; private readonly IDistributedCache _cache; private readonly ILogger<ShopController> _logger; public ShopController (IDistributedCache cache, ILogger<ShopController> logger ) { _cache = cache ?? throw new ArgumentNullException(nameof (cache)); _logger = logger ?? throw new ArgumentNullException(nameof (logger)); } [HttpPut("{status}" ) ] public async Task<Result<string >> SetStatus([FromRoute] int status) { _logger.LogInformation("设置店铺的营业状态为:{Status}" , status == 1 ? "营业中" : "打烊中" ); await _cache.SetStringAsync(KEY, status.ToString()); return Result<string >.Success("设置店铺营业状态成功" ); } [HttpGet("status" ) ] public async Task<Result<int >> GetStatus() { string value = await _cache.GetStringAsync(KEY); int status = string .IsNullOrEmpty(value ) ? 0 : int .Parse(value ); _logger.LogInformation("获取到店铺的营业状态为:{Status}" , status == 1 ? "营业中" : "打烊中" ); return Result<int >.Success(status); } } }