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中自動被更新,省去了很多冗余的代碼。

 

源代碼下載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM