DncZeus实战开源项目(五)CRUD操作演示

CRUD接口实例---NvrServer

  • 新建实体类
// NvrServer.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static DncZeus.Api.Entities.Enums.CommonEnum;

namespace DncZeus.Api.Entities
{
    public class NvrServer
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [Required]
        [StringLength(50)]
        public string Name { get; set; }

        [Required]
        [StringLength(50)]
        public string IP { get; set; }

       
        [StringLength(100)]
        public string Notice { get; set; }

        public IsDeleted IsDeleted { get; set; }

        public DateTime CreateTime { get; set; }
        public DateTime? UpdateTime { get; set; }

        // 数据库导航属性:使用 virtual 以支持延迟加载
        public virtual ICollection<Camera> Cameras { get; set; }


    }
}

  • dbContext中注册并映射到数据库
// DncAuthDbContext.cs



using Microsoft.EntityFrameworkCore;

namespace DncZeus.Api.Entities
{
    
    public class DncZeusDbContext : DbContext
    {
        
       ......
        public DbSet<NvrServer> NvrServer { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
           ......
            modelBuilder.Entity<NvrServer>(entity =>
            {
                entity.HasKey(e => e.Id);
                entity.Property(e => e.Name).IsRequired();
                entity.Property(e => e.IP).IsRequired();
            });
            ......
            base.OnModelCreating(modelBuilder);
        }
    }
}

  • 新建视图模型ViewModel
    • 本质就是模型的序列化
      • 定义字段的校验规则,序列化字段并返回给前端
      • 意义: 当前端传过来的字段不符合要求时,会报错对应的异常
// ViewModels.Equitments.NvrServer.NvrServerCreateViewModel.cs

using System.ComponentModel.DataAnnotations;

namespace DncZeus.Api.ViewModels.Equitments.NvrServer
{
    public class NvrServerCreateViewModel
    {
        [Required(ErrorMessage = "请输入服务器名称")]
        [StringLength(50, ErrorMessage = "服务器名称长度不能超过50个字符")]
        public string Name { get; set; }

        [Required(ErrorMessage = "请输入IP地址")]
        [StringLength(50, ErrorMessage = "IP地址长度不能超过50个字符")]
        public string IP { get; set; }

        [StringLength(100, ErrorMessage = "备注长度不能超过100个字符")]
        public string Notice { get; set; }
    }
}

// NvrServerEditViewModel.cs

using System.ComponentModel.DataAnnotations;

namespace DncZeus.Api.ViewModels.Equitments.NvrServer
{
    public class NvrServerEditViewModel
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "请输入服务器名称")]
        [StringLength(50, ErrorMessage = "服务器名称长度不能超过50个字符")]
        public string Name { get; set; }

        [Required(ErrorMessage = "请输入IP地址")]
        [StringLength(50, ErrorMessage = "IP地址长度不能超过50个字符")]
        public string IP { get; set; }

        [StringLength(100, ErrorMessage = "备注长度不能超过100个字符")]
        public string Notice { get; set; }

        public bool IsDeleted { get; set; }
    }
}

// NvrServerJsonModel.cs

namespace DncZeus.Api.ViewModels.Equitments.NvrServer
{
    public class NvrServerJsonModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string IP { get; set; }
        public string Notice { get; set; }
        public bool IsDeleted { get; set; }
        public string CreateTime { get; set; }
        public string UpdateTime { get; set; }
        public int CameraCount { get; set; }
    }
}

  • 新建请求模型
    • 本质就是分页,排序关键字搜索
      • 基类RequestPayload已自带分页排序
// RequestPayload.Equitments.NvrServer.cs

using DncZeus.Api.RequestPayload;

namespace DncZeus.Api.RequestPayload.Equitments.NvrServer
{
    public class NvrServerRequestPayload:RequestPayload
    {
        public string Kw { get; set; }
    }
}

  • AutoMapper配置映射

    • 作用

      • 在不同类型之间自动转换,避免手动赋值
      // 没有 AutoMapper 时
      var jsonModel = new UserJsonModel 
      {
          Id = user.Id,
          Name = user.Name,
          // ... 需要手动赋值每个属性
      };
      
      // 使用 AutoMapper
      var jsonModel = _mapper.Map<DncUser, UserJsonModel>(user);  // 自动转换
      
      • 配置映射规则: 定义实体ViewModel之间的关系
      // 基本映射
      CreateMap<DncUser, UserJsonModel>();
      
      // 复杂映射(自定义字段映射)
      CreateMap<DncPermission, PermissionJsonModel>()
          .ForMember(d => d.MenuName, s => s.MapFrom(x => x.Menu.Name))  // 关联表字段
          .ForMember(d => d.PermissionTypeText, s => s.MapFrom(x => x.Type.ToString()));  // 类型转文本
      
      • 支持实体ViewModel的双向转换:
      // 实体 → ViewModel
      CreateMap<DncUser, UserEditViewModel>();
      
      // ViewModel → 实体
      CreateMap<UserEditViewModel, DncUser>();
      
      • 实际应用
      // 查询列表
      var data = list.Select(_mapper.Map<DncUser, UserJsonModel>);
      
      // 创建时映射
      var entity = _mapper.Map<UserCreateViewModel, DncUser>(model);
      
      // 编辑时映射
      entity.DisplayName = model.DisplayName;  // 手动赋值
      // 或者
      entity = _mapper.Map<UserEditViewModel, DncUser>(model);  // 自动映射
      
  • CRUD接口

// V1.Equitments.NvrServer.cs

using AutoMapper;
using DncZeus.Api.Entities;
using DncZeus.Api.Entities.Enums;
using DncZeus.Api.Extensions;
using DncZeus.Api.Extensions.AuthContext;
using DncZeus.Api.Extensions.DataAccess;
using DncZeus.Api.Models.Response;
using DncZeus.Api.RequestPayload.Equitments.NvrServer;
using DncZeus.Api.ViewModels.Equitments.NvrServer;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using static DncZeus.Api.Entities.Enums.CommonEnum;


namespace DncZeus.Api.Controllers.Api.V1.Equitments.NvrServer
{
    [Route("api/v1/nvrserver/[action]")]
    [ApiController]
    public class NvrServerController : ControllerBase
    {
        private readonly DncZeusDbContext _dbContext;
        private readonly IMapper _mapper;
		
		// 注入dbContext和mapper
        public NvrServerController(DncZeusDbContext dbContext, IMapper mapper)
        {
            _dbContext = dbContext;
            _mapper = mapper;
        }

        [HttpPost]
        public IActionResult List(NvrServerRequestPayload payload)
        /* payload是个对象
        {
          "pageSize": 6,
          "currentPage": 1,
          "sort": [
            {
              "field": "Name",
              "direct": "DESC"
            }
          ],
          "kw": "服务器"
        }
        */
        {
            using (_dbContext)
            {
            	// 获取数据库所有的记录集
                var query = _dbContext.Set<Entities.NvrServer>().AsQueryable();

                // 筛选未删除的记录
                query = query.Where(x => x.IsDeleted == IsDeleted.No);
				
               // 当Kw不为空值时,搜索查询 
                if (!string.IsNullOrEmpty(payload.Kw))
                {
                    query = query.Where(x => x.Name.Contains(payload.Kw.Trim()) ||
                                           x.IP.Contains(payload.Kw.Trim()) ||
                                           (x.Notice != null && x.Notice.Contains(payload.Kw.Trim())));
                }
				
                // 排序
                if (payload.FirstSort != null)
                {
                    // 验证排序字段是否有效
                    var validFields = new[] { "Id", "Name", "IP", "Notice", "IsDeleted", "CreateTime", "UpdateTime" };
                    if (!string.IsNullOrEmpty(payload.FirstSort.Field) && validFields.Contains(payload.FirstSort.Field))
                    {
                        query = query.OrderBy(payload.FirstSort.Field, payload.FirstSort.Direct == "DESC");
                    }
                }

                // 先获取总数
                var totalCount = query.Count();

                // 获取分页数据
                var list = query.Paged(payload.CurrentPage, payload.PageSize).ToList();

                // 获取所有相关ID
                var nvrIds = list.Select(x => x.Id).ToList();

                // 批量查询摄像头数量
                var cameraCounts = _dbContext.Camera
                    .Where(c => nvrIds.Contains(c.NvrServerId) && c.IsDeleted == IsDeleted.No)
                    .GroupBy(c => c.NvrServerId)
                    .ToDictionary(g => g.Key, g => g.Count());

                // 在 using 块内完成所有数据映射
                var data = list.Select(x =>
                {
                    var model = _mapper.Map<DncZeus.Api.Entities.NvrServer, NvrServerJsonModel>(x);
                    model.CreateTime = x.CreateTime.ToString("yyyy-MM-dd HH:mm:ss");
                    model.UpdateTime = x.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss");
                    model.CameraCount = cameraCounts.GetValueOrDefault(x.Id, 0);
                    return model;
                }).ToList();

                var response = ResponseModelFactory.CreateResultInstance;
                response.SetData(data, totalCount);
                return Ok(response);
            }
        }


		
        // 测试接口,返回所有的数据(不分页)
        [HttpGet("debug")]
        public IActionResult GetAllDebug()
        {
            using (_dbContext)
            {
                var allRecords = _dbContext.NvrServer
                    .Select(x => new { x.Id, x.Name, x.IsDeleted, x.CreateTime })
                    .ToList();

                var response = ResponseModelFactory.CreateInstance;
                response.SetData(new { AllRecords = allRecords, TotalCount = allRecords.Count });
                return Ok(response);
            }
        }

		
        // create接口
        [HttpPost]
        public IActionResult Create(NvrServerCreateViewModel model)
        {
            var response = ResponseModelFactory.CreateInstance;

            using (_dbContext)
            {
                if (_dbContext.NvrServer.Any(x => x.Name == model.Name.Trim()))
                {
                    response.SetFailed("服务器名称已存在");
                    return Ok(response);
                }

                if (_dbContext.NvrServer.Any(x => x.IP == model.IP.Trim()))
                {
                    response.SetFailed("IP地址已存在");
                    return Ok(response);
                }
				
                /*利用mapper快速生成entity(若校验不通过会自动触发异常,无需再try)
                {
                  "errors": {
                    "IP": [
                      "请输入IP地址"
                    ]
                  },
                  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
                  "title": "One or more validation errors occurred.",
                  "status": 400,
                  "traceId": "00-b80c95c5799d733934eb6ac53c69025c-6787641c69bfb97c-00"
                }         
                */
                var entity = _mapper.Map<NvrServerCreateViewModel, Entities.NvrServer>(model);
                entity.CreateTime = DateTime.Now;
                entity.IsDeleted = IsDeleted.No;

                _dbContext.NvrServer.Add(entity);
                _dbContext.SaveChanges();

                response.SetSuccess();
                return Ok(response);
            }
        }

		
        // 获取单个实体
        [HttpGet("{id}")]
        public IActionResult Edit(int id) { 
        
            var entity = _dbContext.NvrServer.FirstOrDefault(x => x.Id == id);

            if (entity == null) {
                return NotFound();
            }

            var response = ResponseModelFactory.CreateInstance;
            response.SetData(_mapper.Map<Entities.NvrServer,NvrServerEditViewModel>(entity));
            return Ok(response);
        }

		
        [HttpPost]
        public IActionResult Edit(NvrServerEditViewModel model)
        {

            var response = ResponseModelFactory.CreateInstance;

            using (_dbContext) {

                var entity = _dbContext.NvrServer.FirstOrDefault(x => x.Id == model.Id);
                if (entity == null)
                {
                    response.SetFailed("服务器不存在");
                    return Ok(response);
                }

                if (_dbContext.NvrServer.Any(x => x.Name == model.Name.Trim() && x.Id != model.Id))
                {
                    response.SetFailed("服务器名称已存在");
                    return Ok(response);
                }

                if (_dbContext.NvrServer.Any(x => x.IP == model.IP.Trim() && x.Id != model.Id))
                {
                    response.SetFailed("IP地址已存在");
                    return Ok(response);
                }

                entity.Name = model.Name;
                entity.IP = model.IP;
                entity.Notice = model.Notice;
                // entity.IsDeleted = model.IsDeleted;
                entity.IsDeleted = model.IsDeleted ? CommonEnum.IsDeleted.Yes : CommonEnum.IsDeleted.No;
                entity.UpdateTime = DateTime.Now;

                _dbContext.SaveChanges();
                response.SetSuccess();
                return Ok(response);


            }
           
        }


        [HttpGet("{ids}")]
        public IActionResult Delete(string ids)
        {

            var response = UpdateIsDelete(IsDeleted.Yes, ids);
            return Ok(response);
        }

        [HttpGet("{ids}")]
        public IActionResult Recover(string ids)
        {
            var response = UpdateIsDelete(IsDeleted.No, ids);
            return Ok(response);
        }

        [HttpGet]
        public IActionResult GetAll()
        {
            using (_dbContext)
            {
                var list = _dbContext.NvrServer
                    .Where(x => x.IsDeleted == IsDeleted.No)
                    .Select(x => new { x.Id, x.Name })
                    .ToList();

                var response = ResponseModelFactory.CreateInstance;
                response.SetData(list);
                return Ok(response);
            }
        }

		
        // 批量删除
        private ResponseModel UpdateIsDelete(IsDeleted isDeleted, string ids)
        {
            var response = ResponseModelFactory.CreateInstance;
            var idList = ids.Split(",").Select(x => int.Parse(x)).ToList();

            using (_dbContext)
            {
                var servers = _dbContext.NvrServer.Where(x => idList.Contains(x.Id)).ToList();
                foreach (var server in servers)
                {
                    server.IsDeleted = isDeleted;
                    server.UpdateTime = DateTime.Now;
                }

                _dbContext.SaveChanges();
                return response;
            }
        }

    }
}

Edit接口代码优化

  • 针对 public IActionResult Edit(NvrServerEditViewModel model)接口里面的手动赋值,这里可以利用mapper实现更简洁的代码
// MappingProfile.cs
......
// 新增
 CreateMap<NvrServerEditViewModel, NvrServer>()
     .ForMember(dest => dest.CreateTime, opt => opt.Ignore())
     .ForMember(dest => dest.UpdateTime, opt => opt.Ignore())
     .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => src.IsDeleted ? IsDeleted.Yes : IsDeleted.No));

// 原先
 CreateMap<NvrServer, NvrServerEditViewModel>()
     .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => src.IsDeleted==IsDeleted.Yes));
     



     
// 接口.cs

public IActionResult Edit(NvrServerEditViewModel model)
{
    ......
    using (_dbContext) {

        var entity = _dbContext.NvrServer.FirstOrDefault(x => x.Id == model.Id);
        ......
        //entity.Name = model.Name;
        //entity.IP = model.IP;
        //entity.Notice = model.Notice;
        ////entity.IsDeleted = model.IsDeleted;
        //entity.IsDeleted = model.IsDeleted ? CommonEnum.IsDeleted.Yes : CommonEnum.IsDeleted.No;
        //entity.UpdateTime = DateTime.Now;

        // 使用AutoMapper映射到现有实体
        _mapper.Map(model, entity);

 		// 手动赋值(不想赋值也可以,mapper再做一个配置即可)
        entity.UpdateTime = DateTime.Now;

        _dbContext.SaveChanges();
        response.SetSuccess();
        return Ok(response);

    }
   
}

Camera模型CRUD演示

// 实体类.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static DncZeus.Api.Entities.Enums.CommonEnum;

namespace DncZeus.Api.Entities
{
    public class Camera
    {

        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [Required]
        [StringLength(100)]
        public string Name { get; set; }

        
        [Required]
        [StringLength(45)] // IPv6最大长度
        public string IP { get; set; }

        
        [StringLength(100)]
        public string Notice { get; set; }

       
        [Range(1, 16, ErrorMessage = "通道号必须在1-16之间")]
        public int Channel { get; set; }

        
        [Required]
        public int NvrServerId { get; set; }

        [ForeignKey("NvrServerId")]
        public virtual NvrServer NvrServer { get; set; }


        public IsDeleted IsDeleted { get; set; }

        
        public DateTime CreateTime { get; set; }

      
        public DateTime? UpdateTime { get; set; }

    }
}

// dbContext.cs
......
public DbSet<Camera> Camera { get; set; }
......
modelBuilder.Entity<Camera>(entity =>
{
    entity.HasKey(e => e.Id);
    entity.Property(e => e.Name).IsRequired();
    entity.Property(e => e.IP).IsRequired();
    entity.Property(e => e.Channel).IsRequired();

    entity.HasOne(c => c.NvrServer) // 设置一端
          .WithMany(n => n.Cameras) // 设置多端
          .HasForeignKey(c => c.NvrServerId) // 外键以及级联关系
          .OnDelete(DeleteBehavior.Cascade);

});

自定义校验特性ValidaCameraAttribute

  • 作用: 校验实体类CRUD操作[这里只写新增编辑校验]中,字段是否合法
// ViewModels.Equitments.Camera.ValidationAttributes.ValidaCameraAttribute.cs

using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using DncZeus.Api.Entities;
using DncZeus.Api.Entities.Enums;

namespace DncZeus.Api.ViewModels.Equitments.Camera.ValidationAttributes
{
    /// <summary>
    /// 摄像头名称唯一性验证特性
    /// </summary>
    public class ValidaCameraAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var dbContext = (DncZeusDbContext)validationContext.GetService(typeof(DncZeusDbContext));
            var model = validationContext.ObjectInstance as CameraCreateViewModel;

            // 检查value是否为null或空字符串
            if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
            {
                return ValidationResult.Success; // 校验假装成功,让后续Required验证特性处理这个错误
            }

            if (dbContext?.Camera.Any(x => x.Name == value.ToString().Trim() &&
                                         x.NvrServerId == model.NvrServerId &&
                                         x.IsDeleted == CommonEnum.IsDeleted.No) == true)
            {
                return new ValidationResult("同一NVR服务器下摄像头名称已存在");
            }

            return ValidationResult.Success;
        }
    }

    /// <summary>
    /// IP地址唯一性验证特性
    /// </summary>
    public class UniqueIPAddressAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var dbContext = (DncZeusDbContext)validationContext.GetService(typeof(DncZeusDbContext));

            
            if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
            {
                return ValidationResult.Success;
            }

            if (dbContext?.Camera.Any(x => x.IP == value.ToString().Trim() &&
                                         x.IsDeleted == CommonEnum.IsDeleted.No) == true)
            {
                return new ValidationResult("IP地址已存在");
            }

            return ValidationResult.Success;
        }
    }

    /// <summary>
    /// 通道号唯一性验证特性
    /// </summary>
    public class UniqueChannelAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var dbContext = (DncZeusDbContext)validationContext.GetService(typeof(DncZeusDbContext));
            var model = validationContext.ObjectInstance as CameraCreateViewModel;

           
            if (value == null)
            {
                return ValidationResult.Success; 
            }

            if (dbContext?.Camera.Any(x => x.Channel == (int)value &&
                                         x.NvrServerId == model.NvrServerId &&
                                         x.IsDeleted == CommonEnum.IsDeleted.No) == true)
            {
                return new ValidationResult($"通道号{value}已被占用");
            }

            return ValidationResult.Success;
        }
    }

    /// <summary>
    /// NVR服务器有效性验证特性
    /// </summary>
    public class ValidNvrServerAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var dbContext = (DncZeusDbContext)validationContext.GetService(typeof(DncZeusDbContext));
            var nvrServerId = (int)value;

            if (dbContext?.NvrServer.Any(x => x.Id == nvrServerId &&
                                             x.IsDeleted == CommonEnum.IsDeleted.No) != true)
            {
                return new ValidationResult("所选的NVR服务器不存在");
            }

            return ValidationResult.Success;
        }
    }

    /// <summary>
    /// 摄像头ID有效性验证特性(编辑时使用,优先验证)
    /// </summary>
    public class ValidCameraIdAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var cameraId = (int)value;

            if (cameraId <= 0)
            {
                return new ValidationResult("请选择要编辑的摄像头");
            }

            var dbContext = (DncZeusDbContext)validationContext.GetService(typeof(DncZeusDbContext));

            if (dbContext?.Camera.Any(x => x.Id == cameraId &&
                                         x.IsDeleted == CommonEnum.IsDeleted.No) != true)
            {
                return new ValidationResult("所选的摄像头不存在");
            }

            return ValidationResult.Success;
        }
    }

    /// <summary>
    /// 摄像头名称唯一性验证特性(编辑时使用,排除当前记录)
    /// </summary>
    public class UniqueCameraNameEditAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var dbContext = (DncZeusDbContext)validationContext.GetService(typeof(DncZeusDbContext));
            var model = validationContext.ObjectInstance as CameraEditViewModel;

          
            if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
            {
                return ValidationResult.Success; 
            }

            // 首先验证摄像头ID是否存在
            if (model.Id <= 0 || !dbContext.Camera.Any(x => x.Id == model.Id && x.IsDeleted == CommonEnum.IsDeleted.No))
            {
                return ValidationResult.Success; // 让ValidCameraIdAttribute处理这个错误
            }

            if (dbContext?.Camera.Any(x => x.Name == value.ToString().Trim() &&
                                         x.NvrServerId == model.NvrServerId &&
                                         x.Id != model.Id &&
                                         x.IsDeleted == CommonEnum.IsDeleted.No) == true)
            {
                return new ValidationResult("同一NVR服务器下摄像头名称已存在");
            }

            return ValidationResult.Success;
        }
    }

    /// <summary>
    /// IP地址唯一性验证特性(编辑时使用,排除当前记录)
    /// </summary>
    public class UniqueIPAddressEditAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var dbContext = (DncZeusDbContext)validationContext.GetService(typeof(DncZeusDbContext));
            var model = validationContext.ObjectInstance as CameraEditViewModel;

            // 检查value是否为null或空字符串
            if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
            {
                return ValidationResult.Success; 
            }

            // 首先验证摄像头ID是否存在
            if (model.Id <= 0 || !dbContext.Camera.Any(x => x.Id == model.Id && x.IsDeleted == CommonEnum.IsDeleted.No))
            {
                return ValidationResult.Success;
            }

            if (dbContext?.Camera.Any(x => x.IP == value.ToString().Trim() &&
                                         x.Id != model.Id &&
                                         x.IsDeleted == CommonEnum.IsDeleted.No) == true)
            {
                return new ValidationResult("IP地址已存在");
            }

            return ValidationResult.Success;
        }
    }

    /// <summary>
    /// 通道号唯一性验证特性(编辑时使用,排除当前记录)
    /// </summary>
    public class UniqueChannelEditAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var dbContext = (DncZeusDbContext)validationContext.GetService(typeof(DncZeusDbContext));
            var model = validationContext.ObjectInstance as CameraEditViewModel;

            // 检查value是否为null
            if (value == null)
            {
                return ValidationResult.Success; 
            }

            // 首先验证摄像头ID是否存在
            if (model.Id <= 0 || !dbContext.Camera.Any(x => x.Id == model.Id && x.IsDeleted == CommonEnum.IsDeleted.No))
            {
                return ValidationResult.Success; 
            }

            if (dbContext?.Camera.Any(x => x.Channel == (int)value &&
                                         x.NvrServerId == model.NvrServerId &&
                                         x.Id != model.Id &&
                                         x.IsDeleted == CommonEnum.IsDeleted.No) == true)
            {
                return new ValidationResult($"通道号{value}已被占用");
            }

            return ValidationResult.Success;
        }
    }
}
  • 这里对这个.net框架特性要有一个理解
- 无法控制验证特性的执行顺序!
	- 因为增加框架复杂度,并且需要维护验证优先级机制

ViewModel模型(序列化模型)

  • 自定义三个模型,分别负责新增,编辑响应的JSON对象
- CameraCreateViewModel
- CameraEditViewModel
- CameraJsonModel
using System;
using System.ComponentModel.DataAnnotations;

using DncZeus.Api.Entities.Enums;
using DncZeus.Api.ViewModels.Equitments.Camera.ValidationAttributes;

namespace DncZeus.Api.ViewModels.Equitments.Camera
{
    public class CameraCreateViewModel
    {
        [ValidaCamera(ErrorMessage = "同一NVR服务器下摄像头名称已存在")]
        [Required(ErrorMessage = "请输入摄像头名称")]
        [StringLength(100, ErrorMessage = "摄像头名称长度不能超过100个字符")]
        public string Name { get; set; }

        [Required(ErrorMessage = "请输入IP地址")]
        [StringLength(45, ErrorMessage = "IP地址长度不能超过45个字符")]
        [UniqueIPAddress(ErrorMessage = "IP地址已存在")]
        public string IP { get; set; }

        [StringLength(100, ErrorMessage = "备注长度不能超过100个字符")]
        public string Notice { get; set; }

        [Required(ErrorMessage = "请输入通道号")]
        [Range(1, 16, ErrorMessage = "通道号必须在1-16之间")]
        [UniqueChannel(ErrorMessage = "通道号已被占用")]
        public int Channel { get; set; }

        [Required(ErrorMessage = "请选择NVR服务器")]
        [ValidNvrServer(ErrorMessage = "所选的NVR服务器不存在")]
        public int NvrServerId { get; set; }

        // 自动赋值字段 - 不需要前端传递
        public DateTime CreateTime { get; set; } = DateTime.Now;
        public DateTime? UpdateTime { get; set; } = DateTime.Now;
        public bool IsDeleted { get; set; } = false;

    }
}

......
    
using System.ComponentModel.DataAnnotations;
using DncZeus.Api.ViewModels.Equitments.Camera.ValidationAttributes;

namespace DncZeus.Api.ViewModels.Equitments.Camera
{
    public class CameraEditViewModel
    {
        [ValidCameraId(ErrorMessage = "所选的摄像头不存在")]
        public int Id { get; set; }

        [Required(ErrorMessage = "请输入摄像头名称")]
        [StringLength(100, ErrorMessage = "摄像头名称长度不能超过100个字符")]
        [UniqueCameraNameEdit(ErrorMessage = "同一NVR服务器下摄像头名称已存在")]
        public string Name { get; set; }

        [Required(ErrorMessage = "请输入IP地址")]
        [StringLength(45, ErrorMessage = "IP地址长度不能超过45个字符")]
        [UniqueIPAddressEdit(ErrorMessage = "IP地址已存在")]
        public string IP { get; set; }

        [StringLength(100, ErrorMessage = "备注长度不能超过100个字符")]
        public string Notice { get; set; }

        [Required(ErrorMessage = "请输入通道号")]
        [Range(1, 16, ErrorMessage = "通道号必须在1-16之间")]
        [UniqueChannelEdit(ErrorMessage = "通道号已被占用")]
        public int Channel { get; set; }

        [Required(ErrorMessage = "请选择NVR服务器")]
        [ValidNvrServer(ErrorMessage = "所选的NVR服务器不存在")]
        public int NvrServerId { get; set; }

        public bool IsDeleted { get; set; }
    }
}
......
namespace DncZeus.Api.ViewModels.Equitments.Camera
{
    public class CameraJsonModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string IP { get; set; }
        public string Notice { get; set; }
        public int Channel { get; set; }
        public int NvrServerId { get; set; }
        public string NvrServerName { get; set; }
        public bool IsDeleted { get; set; }
        public string CreateTime { get; set; }
        public string UpdateTime { get; set; }
    }
}

ViewModel和实体类之间作映射Map

using AutoMapper;
using DncZeus.Api.Entities;
using DncZeus.Api.Entities.QueryModels;
using DncZeus.Api.ViewModels.Equitments.Camera;
using DncZeus.Api.ViewModels.Equitments.NvrServer;
using DncZeus.Api.ViewModels.Rbac.DncIcon;
using DncZeus.Api.ViewModels.Rbac.DncMenu;
using DncZeus.Api.ViewModels.Rbac.DncPermission;
using DncZeus.Api.ViewModels.Rbac.DncRole;
using DncZeus.Api.ViewModels.Rbac.DncUser;
using static DncZeus.Api.Entities.Enums.CommonEnum;

namespace DncZeus.Api.Configurations
{
    /// <summary>
    /// 
    /// </summary>
    public class MappingProfile : Profile
    {
        /// <summary>
        /// 
        /// </summary>
        public MappingProfile()
        {
            ......

            #region NvrServer
        
            CreateMap<NvrServer, NvrServerJsonModel>()
                .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => src.IsDeleted == IsDeleted.Yes));

            CreateMap<NvrServerCreateViewModel, NvrServer>();
            
            CreateMap<NvrServerEditViewModel, NvrServer>()
                .ForMember(dest => dest.CreateTime, opt => opt.Ignore())
                .ForMember(dest => dest.UpdateTime, opt => opt.Ignore())
                .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => src.IsDeleted ? IsDeleted.Yes : IsDeleted.No));

            CreateMap<NvrServer, NvrServerEditViewModel>()
                .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => src.IsDeleted==IsDeleted.Yes));
            #endregion

            #region Camera
            CreateMap<Camera, CameraJsonModel>()
                .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => src.IsDeleted == IsDeleted.Yes));

            // CreateMap<CameraCreateViewModel, Camera>();
            CreateMap<CameraCreateViewModel, Camera>()
                .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => src.IsDeleted ? IsDeleted.Yes : IsDeleted.No));


            CreateMap<Camera, CameraEditViewModel>()
                .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => src.IsDeleted == IsDeleted.Yes));

            CreateMap<CameraEditViewModel, Camera>()
                .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => src.IsDeleted ? IsDeleted.Yes : IsDeleted.No));
            #endregion
        }
    }
}

接口代码如下

using AutoMapper;
using DncZeus.Api.Entities;
using DncZeus.Api.Entities.Enums;
using DncZeus.Api.Extensions;
using DncZeus.Api.Extensions.AuthContext;
using DncZeus.Api.Extensions.DataAccess;
using DncZeus.Api.Models.Response;
using DncZeus.Api.RequestPayload.Equitments.NvrServer;
using DncZeus.Api.RequestPayload.Equitments.Camera;
using DncZeus.Api.ViewModels.Equitments.NvrServer;
using DncZeus.Api.ViewModels.Equitments.Camera;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Linq;
using static DncZeus.Api.Entities.Enums.CommonEnum;
using Microsoft.EntityFrameworkCore;

namespace DncZeus.Api.Controllers.Api.V1.Equitments.Camera
{
    [Route("api/v1/camera/[action]")]
    [ApiController]
    public class CameraController : ControllerBase
    {
        private readonly DncZeusDbContext _dbContext;
        private readonly IMapper _mapper;

        public CameraController(DncZeusDbContext dbContext, IMapper mapper)
        {
            _dbContext = dbContext;
            _mapper = mapper;
        }


        [HttpPost]
        public IActionResult Create(CameraCreateViewModel model)
        {
            var response = ResponseModelFactory.CreateInstance;

            using (_dbContext)
            {
                //if (_dbContext.Camera.Any(x => x.Name == model.Name.Trim() && x.NvrServerId == model.NvrServerId))
                //{
                //    response.SetFailed("同一NVR服务器下摄像头名称已存在");
                //    return Ok(response);
                //}

                //if (_dbContext.Camera.Any(x => x.IP == model.IP.Trim()))
                //{
                //    response.SetFailed("IP地址已存在");
                //    return Ok(response);
                //}

                //if (_dbContext.Camera.Any(x => x.Channel == model.Channel && x.NvrServerId == model.NvrServerId))
                //{
                //    response.SetFailed($"通道号{model.Channel}已被占用");
                //    return Ok(response);
                //}

                // ASP.NET Core会自动执行模型验证,我们只需要检查ModelState
                if (!ModelState.IsValid)
                {
                    var firstError = ModelState.Values.SelectMany(v => v.Errors).FirstOrDefault()?.ErrorMessage;
                    response.SetFailed(firstError ?? "数据验证失败");
                    return Ok(response);
                }

                //var entity = _mapper.Map<CameraCreateViewModel, Entities.Camera>(model);
                //entity.CreateTime = DateTime.Now;
                //entity.UpdateTime = DateTime.Now;
                //entity.IsDeleted = IsDeleted.No;
                var entity = _mapper.Map<CameraCreateViewModel, Entities.Camera>(model);

                _dbContext.Camera.Add(entity);
                _dbContext.SaveChanges();

                response.SetSuccess();
                return Ok(response);


            }
        }

        [HttpGet("{id}")]
        public IActionResult Edit(int id)
        {
            using (_dbContext) { 
                var entity = _dbContext.Camera.Include("NvrServer").FirstOrDefault(x => x.Id == id);
                if (entity == null)
                {
                    return NotFound();
                }

                var response = ResponseModelFactory.CreateInstance;
                response.SetData(_mapper.Map<Entities.Camera, CameraEditViewModel>(entity));
                return Ok(response);
            }
        }


        [HttpPost]
        public IActionResult Edit(CameraEditViewModel model)
        {
            var response = ResponseModelFactory.CreateInstance;

            using (_dbContext)
            {
                //if (_dbContext.Camera.Any(x => x.Name == model.Name.Trim() && x.NvrServerId == model.NvrServerId))
                //{
                //    response.SetFailed("同一NVR服务器下摄像头名称已存在");
                //    return Ok(response);
                //}

                //if (_dbContext.Camera.Any(x => x.IP == model.IP.Trim()))
                //{
                //    response.SetFailed("IP地址已存在");
                //    return Ok(response);
                //}

                //if (_dbContext.Camera.Any(x => x.Channel == model.Channel && x.NvrServerId == model.NvrServerId))
                //{
                //    response.SetFailed($"通道号{model.Channel}已被占用");
                //    return Ok(response);
                //}

                // ASP.NET Core会自动执行模型验证,我们只需要检查ModelState
                if (!ModelState.IsValid)
                {
                    var firstError = ModelState.Values.SelectMany(v => v.Errors).FirstOrDefault()?.ErrorMessage;
                    response.SetFailed(firstError ?? "数据验证失败");
                    return Ok(response);
                }

                var entity = _dbContext.Camera.FirstOrDefault(x => x.Id == model.Id);

                //var entity = _mapper.Map<CameraCreateViewModel, Entities.Camera>(model);
                //entity.CreateTime = DateTime.Now;
                //entity.UpdateTime = DateTime.Now;
                //entity.IsDeleted = IsDeleted.No;

                _mapper.Map(model, entity);
                _dbContext.SaveChanges();

                response.SetSuccess();
                return Ok(response);


            }
        }


        [HttpPost]
        public IActionResult List(CameraRequestPayload payload)
        {
            using (_dbContext)
            {
                var query = _dbContext.Camera.Include("NvrServer").AsQueryable();

                if (!string.IsNullOrEmpty(payload.Kw))
                {
                    query = query.Where(x => x.Name.Contains(payload.Kw.Trim()) ||
                                           x.IP.Contains(payload.Kw.Trim()) ||
                                           (x.Notice != null && x.Notice.Contains(payload.Kw.Trim())));
                }

                if (payload.IsDeleted > IsDeleted.All)
                {
                    query = query.Where(x => x.IsDeleted == payload.IsDeleted);
                }

                if (payload.NvrServerId.HasValue)
                {
                    query = query.Where(x => x.NvrServerId == payload.NvrServerId.Value);
                }

                if (payload.FirstSort != null)
                {
                    query = query.OrderBy(payload.FirstSort.Field, payload.FirstSort.Direct == "DESC");
                }

                var list = query.Paged(payload.CurrentPage, payload.PageSize).ToList();
                var totalCount = query.Count();
                var data = list.Select(x =>
                {
                    var model = _mapper.Map<Entities.Camera, CameraJsonModel>(x);
                    model.CreateTime = x.CreateTime.ToString("yyyy-MM-dd HH:mm:ss");
                    model.UpdateTime = x.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss");
                    model.NvrServerName = x.NvrServer?.Name;
                    return model;
                });

                var response = ResponseModelFactory.CreateResultInstance;
                response.SetData(data, totalCount);
                return Ok(response);
            }
        }




        [HttpGet("{ids}")]
        public IActionResult Delete(string ids)
        {
            var response = UpdateIsDelete(IsDeleted.Yes, ids);
            return Ok(response);
        }


        [HttpGet("{ids}")]
        public IActionResult Recover(string ids)
        {
            var response = UpdateIsDelete(IsDeleted.No, ids);
            return Ok(response);
        }



        private ResponseModel UpdateIsDelete(IsDeleted isDeleted, string ids)
        {
            var response = ResponseModelFactory.CreateInstance;
            
            // 校验ids参数
            if (string.IsNullOrWhiteSpace(ids))
            {
                response.SetFailed("请选择要操作的摄像头");
                return response;
            }

            try
            {
                var idLists = ids.Split(",", StringSplitOptions.RemoveEmptyEntries)
                    .Select(x => int.Parse(x.Trim()))
                    .ToList();

                if (!idLists.Any())
                {
                    response.SetFailed("请选择要操作的摄像头");
                    return response;
                }
            }
            catch (Exception)
            {
                response.SetFailed("摄像头ID格式不正确");
                return response;
            }

            var idList = ids.Split(",", StringSplitOptions.RemoveEmptyEntries)
                .Select(x => int.Parse(x.Trim()))
                .ToList();

            using (_dbContext)
            {
                var cameras = _dbContext.Camera.Where(x => idList.Contains(x.Id)).ToList();
                
                if (!cameras.Any())
                {
                    response.SetFailed("未找到指定的摄像头");
                    return response;
                }
                
                foreach (var camera in cameras)
                {
                    camera.IsDeleted = isDeleted;
                    camera.UpdateTime = DateTime.Now;
                }

                _dbContext.SaveChanges();
                return response;
            }
        }

    }
}
posted @ 2025-12-23 13:16  安_宁  阅读(7)  评论(0)    收藏  举报