zoukankan      html  css  js  c++  java
  • 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中自动被更新,省去了很多冗余的代码。

    源代码下载

  • 相关阅读:
    docker swarm使用keepalived+haproxy搭建基于percona-xtradb-cluster方案的高可用mysql集群
    docker搭建基于percona-xtradb-cluster方案的mysql集群
    flask实现基于elasticsearch的关键词搜索建议
    tcp === udp
    re 模块===正则表达式
    模块===包
    析构方法====
    python===属性--类方法
    python====接口类 抽象类===19
    python==继承===18
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/9772965.html
Copyright © 2011-2022 走看看