1.SQL Server里的timestamp数据类型
SQL Server 中的timestamp类型,不需要sql脚本中写入,更新数据时,数据库里会自己更新;该数据类型主要用于处理并发导致的数据覆盖。
参考文档:SQL Server数据库(时间戳timestamp)类型 (转载) - PowerCoder - 博客园 (cnblogs.com)
INSERT People([ID], [Name])
VALUES('2023042413400010', '小贱贱')
2.使用EF Core 写数据到数据库中,如果Timestamp数据类型不加Timestamp标签,会写入数据库失败;在Timestamp数据类型的字段上添加[DatabaseGenerated(DatabaseGeneratedOption.Computed)]标签也能写入成功;
疑问:对于sql server数据库 Timestamp数据类型的字段,是应该加[Timestamp]标签还是[DatabaseGenerated(DatabaseGeneratedOption.Computed)]标签?
对于使用[DatabaseGenerated(DatabaseGeneratedOption.Computed)]标签写入数据库不会失败的原因如下:
言外之意就是使用[DatabaseGenerated(DatabaseGeneratedOption.Computed)]标签的字段,EF Core生成SQL语句的时候Insert时不会生成该字段;
通过第5点的测试得出结论,EF Core 配合 SQL SERVER数据库使用时,Timestamp数据类型的字段应该配合[Timestamp]标签使用。
3.使用原生EF 更新数据时,更新字段里没有VerNo字段,但VerNo字段仍会自动更新;
4.使用EF Core 7.0中的ExecuteUpdate更新数据,更新字段里没有VerNo字段,但VerNo字段仍会自动更新;
5.观察更新数据时的sql语句
SaveChanges()更新数据 | ExecuteUpdate方法更新数据 | |
---|---|---|
使用Timestamp标签 | SET NOCOUNT ON; UPDATE [People] SET [Name] = @p0 OUTPUT INSERTED.[VerNo] WHERE [ID] = @p1 AND [VerNo] IS NULL; ‘,N’@p1 varchar(50),@p0 varchar(50),@p2 varbinary(8)',@p1=‘2023042413400010’,@p0=‘钢铁小贱贱’,@p2=NULL 更新时因为Entity中没有给VerNo赋值,导致更新失败 | exec sp_executesql N’UPDATE [p] SET [p].[Name] = @__name_1 FROM [People] AS [p] WHERE [p].[ID] = @__id_0’,N’@__name_1 varchar(50),@__id_0 varchar(50)',@__name_1=‘AI小贱贱’,@__id_0=‘2023042413400010’ 执行成功,生成的sql脚本中没有VerNo字段,但VerNo字段在数据库中已经自动更新 |
使用[DatabaseGenerated(DatabaseGeneratedOption.Computed)]标签 | exec sp_executesql N’SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [People] SET [Name] = @p0 OUTPUT INSERTED.[VerNo] WHERE [ID] = @p1; ‘,N’@p1 varchar(50),@p0 varchar(50)',@p1=‘2023042413400010’,@p0=‘源计划小贱贱’ 更新数据成功,生成的SQL脚本里VerNo没有在Where条件里,只是更新成功后把VerNo输出出来 | exec sp_executesql N’UPDATE [p] SET [p].[Name] = @__name_1 FROM [People] AS [p] WHERE [p].[ID] = @__id_0’,N’@__name_1 varchar(50),@__id_0 varchar(50)',@__name_1=‘钢铁军团小贱贱’,@__id_0=‘2023042413400010’ 执行成功,生成的SQL语句中没有VerNo字段 |
不使用上述两种标签 | exec sp_executesql N’SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [People] SET [Name] = @p0 OUTPUT 1 WHERE [ID] = @p1; ‘,N’@p1 varchar(50),@p0 varchar(50)',@p1=‘2023042413400010’,@p0=‘幻灵战队小贱贱’ 更新数据成功,生成的SQL脚本里没有VerNo | exec sp_executesql N’UPDATE [p] SET [p].[Name] = @__name_1 FROM [People] AS [p] WHERE [p].[ID] = @__id_0’,N’@__name_1 varchar(50),@__id_0 varchar(50)',@__name_1=‘源计划小贱贱’,@__id_0=‘2023042413400010’ 更新数据成功,生成的SQL脚本里没有VerNo |
在SQL Server中Timestamp主要是用来处理并发冲突,所以在更新的时候最好是把VerNo查出来之后再进行更新。
使用[DatabaseGenerated(DatabaseGeneratedOption.Computed)]标签虽然能够更新成功,但是并不成处理并发冲突的问题。
2.Mysql中自定义一个防止并发冲突的字段
Mysql中的Timestamp数据类型只是单纯的一个时间戳类型,没有防止并发冲突的作用;如果想实现SQL Server里的Timestamp数据类型需要自定义一个并发冲突字段,并通过重写EF Core中的SaveChanges方法进行实现
1、Entity里声明并发冲突字段:
声明自定义并发冲突字段后,在更新时where条件里会带上该字段,与TimeStamp效果类似。
在Entity里使用[ConcurrencyCheck]标签标示或者使用Fluent API中使用IsConcurrencyToken方法。
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ConcurrencyCheck]
public byte[] RowVersion { get; set; }
}
modelBuilder.Entity<Person>().Property(p => p.RowVersion).IsConcurrencyToken();
2、重写savechanges,在SaveChanges方法里自动更新RowVer字段
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges();
var modifiedEntities = this.ChangeTracker
.Entries()
.Where(x => x.State == EntityState.Modified || x.State == EntityState.Added)
.Select(x => x.Entity)
.ToList();
foreach (var entity in modifiedEntities)
{
//给自定义的并发冲突字段赋值
entity?.GetType().GetProperty("RowVer")
?.SetValue(entity, Encoding.Default.GetBytes(Guid.NewGuid().ToString()));
}
return base.SaveChanges();
}