• 【译】EF/EFCore 中RowVersion与ConcurrencyToken的比较


    原文链接:传送门

    最近我被问到了一个相当好的关于EFCore的问题(虽然一般来说它并不是一个数据库的概念):我应该使用RowVersion 还是ConcurrencyToken作为乐观并发?

    我觉得答案在于,更明确的说,你知道它们两者之间的区别及不足之处吗?

    让我们往回倒一点,以准确的来说,什么是Concurrency Tokens来作为开始,然后是RowVersion,最后我们来看看它们是如何比较的。

    什么是Concurrency Token

    一个concurrency token 是一个每次一条数据库记录被更新时候都会进行检查的一个值。通过检查,我的意思更明确的说,就是已存在的值会被用作SQL语句的一部分,因此如果其在你这儿发生了变化,UPDATE语句便会失败。如果两个用户同时更新同一条记录,那么这种情况便会发生。

    以非常粗糙的流程图形式表现如下:

     用户B和用户A同时更新同一条记录时,最糟糕的情形他会将用户A的更新稀里糊涂的覆盖掉,就算是最好的情况,用户BY将用自己读取到的知识来对数据库所做的更新也会被用户A覆盖掉。

    concurrency token 通过简单检查包含在最初读取中的信息是否还在(写入时)来对付这种情况。我们假设有一个叫做“User”的数据库表,其看起来像是这样:

    Id		int
    FirstName	nvarchar(max)
    LastName	nvarchar(max)
    Version		int
    

    通常一个没有带concurrency token的SQL Update语句看起来像是这样:

    UPDATE User
    SET FirstName = 'Wade'
    WHERE Id = 1
    

    但是如果我们用Version列作为concurrency token,它或许看起来像是这样:

    UPDATE User
    SET FirstName = 'Wade', Version = Version + 1
    WHERE Id = 1 AND Version = 1
    

    在我们的Where语句中Version的值是我们读取最初的数据时获取到的值。在这种方式下,如果有个人在我们读取并更新数据的时候更新了这个记录,Version值便不会匹配,从而导致我们的更新失败。

    在EF/EF Core中,我们有两种方式来指示一个属性是一个ConcurrencyToken,如果你更倾向于使用DataAnnotations你可以简单的在你的模型上应用一个特性【Attribute】:

    [ConcurrencyCheck]
    public int Version { get; set; }
    

    或者你更倾向于流式配置(应该使用这种方式),那么它会变得更加容易:

    modelBuilder.Entity<People>()
    	.Property(p => p.Version)
    	.IsConcurrencyToken();
    

    但是这有一个问题

    因此所有的事情都听起来很棒。但是这有一个问题,一个小但却相当烦人的问题。

    问题在于其缺少某种类型的数据库触发器,或者某种数据库自增字段,它取决于你,由开发者来保证每次进行Update操作时来增加Version。现在你显然可以编写一些EntityFramework扩展来解决这个问题,并在C#中自动增加内容,但它可以非常快速地复杂化。

    RowVersion是什么

    让我们以纯粹的SQL Server概念来开始什么是RowVersion。RowVersion(也被称为Timestamp,它们其实是一样的东西),是一个SQL 列类型,其使用在整个数据库内唯一的自增二进制数来标记记录。每次一个带有RowVersion的记录被插入或者更新到一个表中时,都会生成一个新的唯一的数字(以二进制的形式),并将其赋给那个记录。再一次的,RowVersions是整个数据库唯一的,而并不局限于这个表。

    现在在EF/EF Core中这实际上是有一些不同的含义,这是由于SQL RowVersion用来实现的东西有所不同。

    典型的在EF中,当某人说使用一个RowVersion时,他们其实是在说使用一个RowVersion/Timestamp列来用作一个“ConcurrencyToken”。现在如果你记得早些时候只是使用ConcurrencyToken带来的问题便是我们不得不自己update/increment 那个值,但是很显然如果SQL Server使用RowVersion来进行自动更新,那么问题便迎刃而解。

    如果我们来看一看EF是如何计算是否使用一个RowVersion的,那么事情会变得更有意思。实际的代码可以查看这里:https://github.com/dotnet/efcore/blob/master/src/EFCore/Metadata/Builders/PropertyBuilder.cs#L152

    public virtual PropertyBuilder IsRowVersion()
    {
    	Builder.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Explicit);
    	Builder.IsConcurrencyToken(true, ConfigurationSource.Explicit);
    
    	return this;
    }
    

    调用IsRowVersion()实际上是相当于告诉EFCore这个属性是ConcurrencyToken 并且是自动生成的。因此实际上,如果你手动给一个属性添加这两个配置。EF Core会将其看作一个RowVersion,即使你并不显示的说它是。

    我们可以通过检查询问一个列是否是RowVersion的代码来看到这个:https://github.com/dotnet/efcore/blob/master/src/EFCore.Relational/Metadata/IColumn.cs#L56

    bool IsRowVersion => PropertyMappings.First().Property.IsConcurrencyToken
    					&& PropertyMappings.First().Property.ValueGenerated == ValueGenerated.OnAddOrUpdate;
    

    所有实际上它所做的所有的工作就是审核一个列是否 是 concurrency token并且是自动生成的,简单吧!

    我要指出的是,实际上如果你有一个列,你以某种其他的方式来递增(举个例子,数据库触发器),那么其也是一个concurrency token。我很确定EF Core处理这个会有一些问题,但那将是以后的某一天。

    在EF中,你可以在一个属性上建立一个RowVersion,像是这样:

    [TimeStamp]
    public byte[] RowVersion{ get; set; }
    

    对于流式配置来说,其是这样:

    modelBuilder.Entity<People>()
    	.Property(p => p.RowVersion)
    	.IsRowVersion();
    

    即使你指定了一个列是一个RowVersion,其如何工作(数据类型,特定配置以及其如何更新)的实际实现实际上依赖于SQL SERVER。不同的数据库可以以其自己喜欢的方式来实现RowVersion。但是典型的在SQL SERVER中,其是byte[] 类型。

     注意在EF中使用RowVersion,你真的不需要做其他更多的事情来建立和运行了。每次你更新带有RowVersion属性的一个记录时,它会自动将RowVersion属性添加到WHERE子句,为比提供了提供开箱即用的乐观并发性。

    所以ConcurrencyToken还是RowVersion

    所以让我们回过头来看看最初的那个问题:什么时候你应该使用 Concurrency Token,而什么时候你该使用RowVersion?答案其实非常简单。如果你想使用ConcurrencyToken作为一个自动增长的字段,并且你并不在乎其如何增长的以及其数据类型,那么便使用RowVersion。如果你在乎 concurrency token的数据类型应该什么,或者你想明确的控制其如何以及何时更新的,那么使用 Concurrency Token并自己管理其增长。

    我通常发现,当人们建议我使用Concurrency Token时,他们实际上的意思是使用RowVersion。事实上我们很容易可以说:RowVersion(在EF)是一种特定类型的Concurrency Token.。

  • 相关阅读:
    Java 集合(静态导入)
    Java 集合 (Collections、Arrays)
    Java 异常
    Java 多态
    Java 继承

    内网服务器配置访问公网
    替换centos的原生yum源为阿里云yum源
    centos7安装杀毒软件ClamAV
    linux程序名称带devel跟不带的区别
  • 原文地址:https://www.cnblogs.com/qianxingmu/p/13376164.html
Copyright © 2020-2023  润新知