我们在开发系统的时候,经常会遇到这种需求数据库表中的行被更新时需要自动更新某些列。

数据库


比如下面的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(); entity.Property(e => e.CreateTime)
.HasColumnType("datetime")
.HasDefaultValueSql("(getdate())"); entity.Property(e => e.Isbn)
.HasColumnName("ISBN")
.HasMaxLength(); entity.Property(e => e.Name).HasMaxLength();
}); 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(); 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(); //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= };
var personTom= new Person() { Name = "Tom", Age = };
var personSam = new Person() { Name = "Sam", Age = };
var personJerry = new Person() { Name = "Jerry", Age = };
var personHenry = new Person() { Name = "Henry ", Age = }; var bookScience = new Book() { Name = "Science", BookDescription= "Science", Isbn="" };
var bookMath = new Book() { Name = "Math", BookDescription = "Math", Isbn = "" };
var bookPhysics = new Book() { Name = "Physics", BookDescription = "Physics", Isbn = "" };
var bookComputer = new Book() { Name = "Computer", BookDescription = "Computer", Isbn = "" };
var bookEnglish = new Book() { Name = "English", BookDescription = "English", Isbn = "" }; 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 = ; //更改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 = ""; //由于上面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中自动被更新,省去了很多冗余的代码。

源代码下载

最新文章

  1. Hihocoder 1063 缩地
  2. Windows Azure Cloud Service (11) PaaS之Web Role, Worker Role(上)
  3. LAMP安装各种问题解决方案
  4. MS对WCF配置中security节点的解释
  5. [原]OpenGL基础教程(五)缓冲区数据更新方式
  6. html字符实体对照表
  7. SQLServer乱码问题的分析及解决方法(中文字符被存入数据库后,显示为乱码)
  8. springMVC+Hibernate4+spring整合实例二(实例代码部分)
  9. 在ubuntu16.04中初次体验.net core 2.0
  10. 转 docker创建私有仓库和k8s中使用私有镜像
  11. java 代码实现使用Druid 链接池获取数据库链接
  12. dict 知识汇总
  13. arcgis desktop 地理编码服务发布
  14. php基础-3
  15. Oracle exp/imp 导出/导入
  16. [日志log] 常用log日志记录方式对比和详解
  17. 在VMwear 11中的Mac OS X 10.11+ 进入恢复模式(Recovery HD)
  18. C++:sprintf()的用法(转)
  19. elk系列7之通过grok分析apache日志【转】
  20. Linux Shell 文本处理工具集锦--Awk―sed―cut(row-based, column-based),find、grep、xargs、sort、uniq、tr、cut、paste、wc

热门文章

  1. Java温故而知新(2)多线程详解
  2. SZU1
  3. The method setItems(String) in the type ForTokensTag is not applicable for the arguments (Object)
  4. Java反射机制(带应用)
  5. klee源码阅读笔记1--STPBuilder类
  6. tdd:(react + mocha)环境配置
  7. 64位Navicat Premium-11.2.7(64bit)访问64位Oracle服务器
  8. java自增(自减)运算符
  9. 使用GDI技术创建ASP.NET验证码
  10. windows系统的错误码