无法让实体框架生成DbUpdateConcurrencyException(数据库优先)

本文关键字:数据库 DbUpdateConcurrencyException 实体 框架 | 更新日期: 2023-09-27 17:52:55

我正在尝试为EF实现乐观锁定模型。我有一个列的时间戳数据类型的表上,我试图这样做。

我已经尝试通过修改T4脚本在成员上添加Timestamp属性。运行我的测试并看到它不工作,然后我尝试以相同的方式(通过T4)在同一成员上添加ConcurrencyCheck属性。但是,在这两种情况下,生成的SQL实际上都不会在更新时使用该成员(如下所示)。我还在EDMX设计器的属性上设置了Concurrency ModeFixed

我可以在更新后的单独选择中看到时间戳列,但不能在更新本身上看到。我试过打破SaveChanges(),改变数据库记录,看到时间戳值在我手动更新后改变,执行SaveChanges()行,它执行得很好,没有抛出异常。是否有我错过的东西,我需要滚动我自己的时间戳比较代码,或者我需要进入EF拦截器的世界来改变SQL输出?谢谢。

生成的SQL:(通过DbContext.Database.Log记录)

UPDATE [dbo].[Foo]
SET [FooTitle] = @0
WHERE ([FooId] = @1)
SELECT [fooversion]
FROM [dbo].[Foo]
WHERE @@ROWCOUNT > 0 AND [FooId] = @1 

成员在生成T4之后的最终版本:

[Timestamp]
[ConcurrencyCheck]
public byte[] fooversion { get; set; }

我想让EF做什么:

UPDATE [dbo].[Foo]
SET [FooTitle] = @0
WHERE ([FooId] = @1 AND [fooversion] = @2)

无法让实体框架生成DbUpdateConcurrencyException(数据库优先)

我决定自己编写检查代码。这是非常有效的,尽管它需要两次访问数据库。

我写了一个接口,它附加到一系列我已经用来将东西附加到实体的部分类上。它定义了一个查找选择器,我的实体基类服务可以使用它来获取特定的版本。没有结果?返回消息。如果有结果,则实体按照您的期望进行更新。

接口:

public interface IVersionableDbObject<TEntity>
    where TEntity : class, IValidatableObject
{
    byte[] Version { get; }
    Expression<Func<TEntity, bool>> LookupSelector { get; }
}

实现示例:

public partial class Foo : IVersionableDbObject<Foo>
{
    public byte[] Version { get { return fooversion; } }
    public Expression<Func<Foo, bool>> LookupSelector
    {
        get { return foo => foo.FooId == FooId && foo.fooversion == Version; }
    }
}

基本服务中的检查代码:

IVersionableDbObject<TEntity> versionableSource = entity as IVersionableDbObject<TEntity>;
if (versionableSource != null)
{
    bool versionExists = innerContext.Set<TEntity>().Where(versionableSource.LookupSelector).Any();
    if (!versionExists)
        return new ValidationResult(string.Format(FooResources.EntityUpdateVersionConflictError,
                    BitConverter.ToInt32(versionableSource.Version, 0)));
}

验证结果只是一个消息,它是一个格式化程序,接受要查找的版本。ValidationResult的返回特定于我自己的实现。如果你要用,就随你的便。

下面是.Any()上生成的SQL:

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Foo] AS [Extent1]
    WHERE ([Extent1].[FooId] = @p__linq__0) AND ([Extent1].[fooversion] = @p__linq__1)
)) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

有趣的是,当更新实际发生时,EF仍然刷新没有TimestampConcurrencyCheck属性的行版本,类似于我在op中发布的SQL。