EF Core中怎么实现自动更新实体的属性值到数据库
Posted opencoder
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EF Core中怎么实现自动更新实体的属性值到数据库相关的知识,希望对你有一定的参考价值。
我们在开发系统的时候,经常会遇到这种需求数据库表中的行被更新时需要自动更新某些列。
数据库
比如下面的Person表有一列UpdateTime,这列数据要求在行被更新后自动更新为系统的当前时间。
Person表:
CREATE TABLE [dbo].[Person]( [ID] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](50) NULL, [Age] [int] NULL, [CreateTime] [datetime] NULL, [UpdateTime] [datetime] NULL, CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO ALTER TABLE [dbo].[Person] ADD CONSTRAINT [DF_Person_CreateTime] DEFAULT (getdate()) FOR [CreateTime] GO
我们还有一个Book表,它没有UpdateTime列,那么这个表的数据在行更新时不要求自动更新任何列
Book表:
CREATE TABLE [dbo].[Book]( [ID] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](50) NULL, [BookDescription] [nvarchar](100) NULL, [ISBN] [nvarchar](50) NULL, [CreateTime] [datetime] NULL, CONSTRAINT [PK_Book] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO ALTER TABLE [dbo].[Book] ADD CONSTRAINT [DF_Book_CreateTime] DEFAULT (getdate()) FOR [CreateTime] GO
那么Person表的UpdateTime列如果映射到了EF Core的实体上的话,有办法在Person实体被Update的时候自动设置为系统当前时间吗?答案是当然有!
EF Core 实体
首先我们将这两张表映射到EF Core的实体对象上:
Person实体:
public partial class Person { public int Id { get; set; } public string Name { get; set; } public int? Age { get; set; } public DateTime? CreateTime { get; set; } public DateTime? UpdateTime { get; set; } }
Book实体:
public partial class Book { public int Id { get; set; } public string Name { get; set; } public string BookDescription { get; set; } public string Isbn { get; set; } public DateTime? CreateTime { get; set; } }
EF Core的DB First生成的DbContext类EFDemoContext
public partial class EFDemoContext : DbContext { public EFDemoContext() { } public EFDemoContext(DbContextOptions<EFDemoContext> options) : base(options) { } public virtual DbSet<Book> Book { get; set; } public virtual DbSet<Person> Person { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=1qaz!QAZ;Database=EFDemo"); } } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Book>(entity => { entity.Property(e => e.Id).HasColumnName("ID"); entity.Property(e => e.BookDescription).HasMaxLength(100); entity.Property(e => e.CreateTime) .HasColumnType("datetime") .HasDefaultValueSql("(getdate())"); entity.Property(e => e.Isbn) .HasColumnName("ISBN") .HasMaxLength(50); entity.Property(e => e.Name).HasMaxLength(50); }); modelBuilder.Entity<Person>(entity => { entity.Property(e => e.Id).HasColumnName("ID"); entity.Property(e => e.CreateTime) .HasColumnType("datetime") .HasDefaultValueSql("(getdate())"); entity.Property(e => e.Name).HasMaxLength(50); entity.Property(e => e.UpdateTime).HasColumnType("datetime"); }); } }
DbContext.ChangeTracker.StateChanged事件
之后最关键的一点到了,我们需要用到DbContext.ChangeTracker.StateChanged这个事件,这个事件会在DbContext中被Track的实体对象的EntityState状态发生变化时被触发,有多少个实体的EntityState状态变化了,它就会被触发多少次。
为此,我们需要再定义一个自定义的DbContext类EFDbContext,来继承DB First自动生成的EFDemoContext类:
//EFDbContext继承自EFDemoContext,EFDemoContext又继承自DbContext public class EFDbContext: EFDemoContext { public EFDbContext() { //设置数据库Command永不超时 this.Database.SetCommandTimeout(0); //DbContext.ChangeTracker.StateChanged事件,会在DbContext中被Track的实体其EntityState状态值发生变化时被触发 this.ChangeTracker.StateChanged += (sender, entityStateChangedEventArgs) => { //如果实体状态变为了EntityState.Modified,那么就尝试设置其UpdateTime属性为当前系统时间DateTime.Now,如果实体没有UpdateTime属性,会抛出InvalidOperationException异常,所以下面要用try catch来捕获异常避免系统报错 if (entityStateChangedEventArgs.NewState == EntityState.Modified) { try { //如果是Person表的实体那么下面的Entry.Property("UpdateTime")就不会抛出异常 entityStateChangedEventArgs.Entry.Property("UpdateTime").CurrentValue = DateTime.Now; } catch(InvalidOperationException) { //如果上面try中抛出InvalidOperationException,就是实体没有属性UpdateTime,应该是表Book的实体 } } //如果要自动更新多列,比如还要自动更新实体的UpdateUser属性值到数据库,可以像下面这样再加一个try catch来更新UpdateUser属性 //if (entityStateChangedEventArgs.NewState == EntityState.Modified) //{ // try // { // entityStateChangedEventArgs.Entry.Property("UpdateUser").CurrentValue = currentUser; // } // catch (InvalidOperationException) // { // } //} }; } }
然后我们在Program.cs的Main方法中(我在本例建立的是一个.Net Core控制台程序)先初始化Person表和Book表的数据,然后再修改Person表和Book表的数据,看看被修改的Person表数据其列UpdateTime的值是否设置为了系统当前时间:
class Program { //初始化Person表和Book表的数据 static void InitializeDataToDB() { var personJim = new Person() { Name="Jim", Age=20 }; var personTom= new Person() { Name = "Tom", Age = 30 }; var personSam = new Person() { Name = "Sam", Age = 25 }; var personJerry = new Person() { Name = "Jerry", Age = 35 }; var personHenry = new Person() { Name = "Henry ", Age = 26 }; var bookScience = new Book() { Name = "Science", BookDescription= "Science", Isbn="0001" }; var bookMath = new Book() { Name = "Math", BookDescription = "Math", Isbn = "0002" }; var bookPhysics = new Book() { Name = "Physics", BookDescription = "Physics", Isbn = "0003" }; var bookComputer = new Book() { Name = "Computer", BookDescription = "Computer", Isbn = "0004" }; var bookEnglish = new Book() { Name = "English", BookDescription = "English", Isbn = "0005" }; using (var efDbContext = new EFDbContext()) { efDbContext.Person.Add(personJim); efDbContext.Person.Add(personTom); efDbContext.Person.Add(personSam); efDbContext.Person.Add(personJerry); efDbContext.Person.Add(personHenry); efDbContext.Book.Add(bookScience); efDbContext.Book.Add(bookMath); efDbContext.Book.Add(bookPhysics); efDbContext.Book.Add(bookComputer); efDbContext.Book.Add(bookEnglish); efDbContext.SaveChanges(); } } static void Main(string[] args) { Console.WriteLine("Testing start!"); //初始化Person表和Book表的数据 InitializeDataToDB(); //修改Person表和Book表的数据 using (var efDbContext = new EFDbContext()) { //更改Person.Name为Tom的实体的Age属性值,这会导致personTom这个Person实体的EntityState变为Modified Expression<Func<Person, bool>> expressionTom = p => p.Name == "Tom"; var personTom = efDbContext.Person.First(expressionTom); personTom.Age = 50; //更改Book.Name为Computer的实体的Isbn属性值,这会导致bookComputer这个Book实体的EntityState变为Modified Expression<Func<Book, bool>> expressionComputer = b => b.Name == "Computer"; var bookComputer = efDbContext.Book.First(expressionComputer); bookComputer.Isbn = "1000"; //由于上面DbContext中有两个实体的EntityState改变了,下面的SaveChanges方法会触发两次DbContext.ChangeTracker.StateChanged事件,在实体数据保存到数据库之前,自动更新personTom这个Person实体的UpdateTime属性值为系统当前时间 efDbContext.SaveChanges(); } Console.WriteLine("Testing end!"); Console.ReadLine(); } }
当执行完InitializeDataToDB方法后,数据库两张表的值:
Person表:
Book表:
当Program.cs的Main方法运行完毕后,数据库两张表的值:
Person表:
Book表:
我们可以看到Person表中列Name为Tom的行,其UpdateTime也被自动更新为了系统当前时间。这样数据库中所有带UpdateTime列的表,其UpdateTime列的值都会在EF Core中自动被更新,省去了很多冗余的代码。
以上是关于EF Core中怎么实现自动更新实体的属性值到数据库的主要内容,如果未能解决你的问题,请参考以下文章
在 EF Core/自动生成 ID 中保存实体时将 IT 留空
为啥 EF Core 5 不使用我的实体“地址”的“Id”属性上的“[Key]”属性创建“主键自动增量”?