ASP.NET Core MVC 使用 EF Core 实现字段自动填充(如:添加时间 CreatedTime、更新时间 UpdatedTime)

参考

  • DeepSeek
  • 豆包
  • 其他(由于文章是后编写的,相关参考文章未存储连接)

环境

软件/系统 版本 说明
Windows windows 10 专业版 22H2 64 位操作系统, 基于 x64 的处理器
Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.14.9
Visual Studio Code 1.102.2
Docker Engine v28.3.2 Docker 桌面工具
Docker 28.3.2
pgAdmin4 9.0 PostgreSQL 数据库管理软件
PostgreSQL 15
.NET 8
ASP.NET Core MVC ASP.NET Core MVC in .NET 8.0
Microsoft.EntityFrameworkCore.Design 8.0.18 nuget 依赖
Microsoft.EntityFrameworkCore.Tools 8.0.18 nuget 依赖
Microsoft.VisualStudio.Web.CodeGeneration.Design 8.0.7 nuget 依赖 (Microsoft Visual Studio在通过模型生成控制器与视图时自动安装的依赖)
Npgsql.EntityFrameworkCore.PostgreSQL 8.0.11 nuget 依赖
EFCore.NamingConventions 8.0.3 nuget 依赖
X.PagedList.EF 10.5.7 nuget 依赖
X.PagedList.Mvc.Core 10.5.7 nuget 依赖(安装 X.PagedList.EF 时自动安装)

本项目入口文件为 Program.cs ,创建项目时为不使用顶级语句

注意

如果遇到时间存储出现Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone', only UTC is supported. 问题时,请参考《ASP.NET Core MVC 解决 EF Core 连接 PostgreSQL 失败与数据库存储 DateTime 字段出现 Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone', only UTC is supported. 问题

正文

  1. 本文以 Admin 表作为示例,定义 Admin模型
    using Microsoft.EntityFrameworkCore;
    using NightMarketPlatformWeb.Enums;
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace NightMarketPlatformWeb.Models
    {
    	/// <summary>
    	/// 管理员管理模型
    	/// </summary>
    	[Table("admins")]
    	// 表备注
    	[Comment("管理员管理表")]
    	// 普通索引
    	//[Index(nameof(AreaName))]
    	// 普通索引并命名
    	//[Index(nameof(AreaName), Name = "AreaName")]
    	// 唯一索引
    	[Index(nameof(AdminName, IsUnique = true))]
    	// 复合唯一索引
    	[Index(nameof(AdminName), nameof(AdminPhoneNumber), IsUnique = true)]
    	public class AdminModel : BaseModel
    	{
    		/// <summary>
    		/// 主键
    		/// </summary>
    		[Key]
    		public int Id { get; set; }
    
    		/// <summary>
    		/// 管理员名称
    		/// </summary>
    		[Required]
    		// 字段备注
    		[Comment("管理员名称")]
    		// 显示名称
    		[Display(Name = "管理员名称")]
    		public string AdminName { get; set; } = string.Empty;
    
    		/// <summary>
    		/// 管理员账号/管理员联系方式
    		/// </summary>
    		[Required]
    		// 字段备注
    		[Comment("管理员账号/联系方式")]
    		// 显示名称
    		[Display(Name = "管理员账号/联系方式")]
    		public string AdminPhoneNumber { get; set; } = string.Empty;
    
    		/// <summary>
    		/// 管理员密码
    		/// </summary>
    		[Required]
    		// 长度验证 (如果这里设置了就会导致数据表出问题,把校验放到修改dto中)
    		//[StringLength(16, MinimumLength = 8, ErrorMessage = " {0} 区间为 {1} - {2}.")]
    		// 字段备注
    		[Comment("管理员密码")]
    		// 显示名称
    		[Display(Name = "管理员密码")]
    		public string AdminPassword { get; set; } = string.Empty;
    
    		/// <summary>
    		/// 是否启用
    		/// </summary>
    		// 字段备注
    		[Comment("是否启用")]
    		// 显示名称
    		[Display(Name = "是否启用")]
    		public bool Enabled { get; set; } = true;
    
    	}
    
    	public class BaseModel
    	{
    		/// <summary>
    		/// 创建时间
    		/// </summary>
    		[DataType(DataType.DateTime)]
    		// 字段备注
    		[Comment("创建时间")]
    		// 显示名称
    		[Display(Name = "创建时间")]
    		// 只在插入时填充
    		//[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    		public DateTime CreatedTime { get; set; }
    		/// <summary>
    		/// 更新时间
    		/// </summary>
    		[DataType(DataType.DateTime)]
    		// 显示名称
    		[Display(Name = "更新时间")]
    		// 字段备注
    		[Comment("更新时间")]
    		// 每次更新时填充
    		//[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    		public DateTime UpdatedTime { get; set; }
    		/// <summary>
    		/// 删除时间
    		/// </summary>
    		[DataType(DataType.DateTime)]
    		// 显示名称
    		[Display(Name = "删除时间")]
    		// 字段备注
    		[Comment("删除时间")]
    		public DateTime? DeletedTime { get; set; }
    	}
    }
    
  2. 重写项目 DbContext 类的 SaveChangesSaveChangesAsync方法。(相关不需要的内容已注释)
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;
    using NightMarketPlatformWeb.Models;
    using System;
    using System.Diagnostics;
    using System.Reflection.Metadata;
    using System.Xml.Linq;
    
    namespace NightMarketPlatformWeb.Data
    {
    	public class ApplicationDbContext : IdentityDbContext
    	{
    
    		public DbSet<AdminModel> Admins { get; set; }
    		//public DbSet<AreaModel> Areas { get; set; }
    		//public DbSet<LesseeModel> Lessees { get; set; }
    		//public DbSet<LogModel> Logs { get; set; }
    		//public DbSet<SettingModel> Settings { get; set; }
    		//public DbSet<StallModel> Stalls { get; set; }
    
    
    		public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
    			: base(options)
    		{
    		}
    
    		protected override void OnModelCreating(ModelBuilder modelBuilder)
    		{
    			base.OnModelCreating(modelBuilder);
    			// 配置摊位-用户关系
    			//modelBuilder.Entity<StallModel>()
    			//    .HasOne(s => s.Lessee)          // 一个摊位属于一个用户
    			//    .WithMany(l => l.Stalls)        // 一个用户有多个摊位
    			//    .HasForeignKey(s => s.LesseeId) // 明确指定外键属性
    			//    .OnDelete(DeleteBehavior.Restrict); // 禁止级联删除
    
    			//// 配置摊位-区域关系
    			//modelBuilder.Entity<StallModel>()
    			//    .HasOne(s => s.Area)            // 一个摊位属于一个区域
    			//    .WithMany(a => a.Stalls)        // 一个区域有多个摊位
    			//    .HasForeignKey(s => s.AreaId)   // 明确指定外键属性
    			//    .OnDelete(DeleteBehavior.Restrict); // 禁止级联删除
    		}
    		/// <summary>
    		/// 重写savechangs
    		/// </summary>
    		/// <returns></returns>
    		public override int SaveChanges()
    		{
    			SetSystemField();
    			return base.SaveChanges();
    		}
    		/// <summary>
    		/// 重写SaveChangesAsync
    		/// </summary>
    		/// <param name="cancellationToken"></param>
    		/// <returns></returns>
    		public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    		{
    			SetSystemField();
    			return await base.SaveChangesAsync();
    		}
    
    		/// <summary>
    		/// 重写 SaveChanges 
    		/// 时间填充 https://blog.csdn.net/qq_37214567/article/details/120288608 https://www.codeleading.com/article/79544088425/
    		/// 忽略null https://developer.aliyun.com/article/676711
    		/// </summary>
    		/// <returns></returns>
    		public void SetSystemField()
    		{
    			//自动修改 CreateTime,UpdateTime
    			var entityEntries = ChangeTracker.Entries().ToList();
    			foreach (var entry in entityEntries)
    			{
    				// 新增自动填充时间字段
    				if (entry.State == EntityState.Added)
    				{
    					if (entry.Metadata.FindProperty(nameof(BaseModel.CreatedTime)) != null)
    					{
    						Entry(entry.Entity).Property(nameof(BaseModel.CreatedTime)).CurrentValue = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
    					}
    					if (entry.Metadata.FindProperty(nameof(BaseModel.UpdatedTime)) != null)
    					{
    						Entry(entry.Entity).Property(nameof(BaseModel.UpdatedTime)).CurrentValue = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
    					}
    				}
    				// 更新自动填充时间字段
    				if (entry.State == EntityState.Modified)
    				{
    					if (entry.Metadata.FindProperty(nameof(BaseModel.CreatedTime)) != null)
    					{
    						Entry(entry.Entity).Property(nameof(BaseModel.CreatedTime)).IsModified = false;
    					}
    					if (entry.Metadata.FindProperty(nameof(BaseModel.UpdatedTime)) != null)
    					{
    						Entry(entry.Entity).Property(nameof(BaseModel.UpdatedTime)).CurrentValue = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
    					}
    
    				}
    			}
    		}
    	}
    }
    
  3. 重启项目,在数据新增、更新就会自动更新时间戳了

预览

  • 数据参考
    image
posted @ 2025-07-27 18:18  夏秋初  阅读(23)  评论(0)    收藏  举报