EF Core并发性未被执行

4

我已经按照此处的文档进行操作,并且使用的是SQL Server 2016。(https://ef.readthedocs.io/en/latest/modeling/concurrency.html

我已经在我的表中添加了一个名为VersionCol的时间戳列,当我运行Scaffold-DbContext时,它会在我的DbContext中的表实体上放置以下属性。

entity.Property(e => e.VersionCol)
    .IsRequired()
    .HasColumnType("timestamp")
    .ValueGeneratedOnAddOrUpdate()

这段代码缺少 .IsConcurrencyToken(),我已经自己加上了,但在应该出现并发问题的情况下没有抛出任何异常,反而直接覆盖了数据。

是否有什么地方我忽略了?

编辑:

我使用基于数据库的方法(因此没有[Timestamp]或其他注释),并将我的DbContext注入到服务中,在Startup.cs中使用services.AddScoped<IPoRepository,PoRepository>()进行配置。

它在我的模型中生成一个public byte[] VersionCol { get; set; }字段,我认为这是正确的。

在我的PoRepository中,我试图使用以下代码更新我的Po:

public void SavePo(PoListing poListing) {
    Po po;

    try {
        po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
    } catch ( ArgumentNullException ) {
        throw new ArgumentNullException( "The PO does not exist." );
    }

    po.AssignedUserId = poListing.AssignedUserId;
    po.VersionCol     = poListing.VersionCol;

    _context.Entry( po ).State = EntityState.Modified;

    _context.SaveChanges();
}

PoListing本质上只是Po的一部分,因此它仅具有其某些列(它不是数据库中的表),并且在生成时具有Po的VersionCol。如果PoListing的VersionCol早于它所基于的Po,则应该会产生异常。

编辑2:

这个方法可行,但我无法弄清楚如何使它在不需要创建第二个上下文的情况下工作,并且只使用注入的上下文。

public void SavePo(PoListing poListing) {
    DbContextOptionsBuilder<TMS_1000Context> options = new DbContextOptionsBuilder<TMS_1000Context>();
    options.UseSqlServer( "Server=DEVSQL16;Database=TMS_1000_Dev;Trusted_Connection=True;MultipleActiveResultSets=true" );

    TMS_1000Context context1;

    try {
        po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
    } catch ( ArgumentNullException ) {
        throw new ArgumentNullException( "The PO does not exist." );
    }

    using ( context1 = new TMS_1000Context( options.Options ) ) {
        po.AssignedUserId = poListing.AssignedUserId;
        po.VersionCol = poListing.VersionCol;

        context1.Update( po );

        context1.SaveChanges();
    }
}

编辑3:

目前这种方法可行。还有其他方式吗?

public void SavePo(PoListing poListing) {
    Po po;

    try {
        po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
    } catch ( ArgumentNullException ) {
        throw new ArgumentNullException( "The PO does not exist." );
    }

    po.AssignedUserId = poListing.AssignedUserId;

    _context.Entry( po ).Property( u => u.VersionCol ).OriginalValue = poListing.VersionCol;

    _context.Update( po );
    _context.SaveChanges();
}

1
请问您能展示一下您传递给数据上下文的内容吗?我的意思是,您是如何更新实体的? - Mahdi Farhani
1
你能添加更多信息吗?你是在代码优先模型中添加[Timestamp]属性还是在DbContext的OnModelCreating方法中添加,或者你正在尝试从现有数据库脚手架的数据库优先方法?请至少发布你的模型和DbContext代码以及相关的OnModelCreating - Tseng
我已经添加了更多细节,请告诉我是否还有其他您想要看到的内容。 - GlacialFlames
1个回答

2
我认为这种情况发生的原因是EF Core跟踪只关心原始值是否与当前数据库中的值相同,如果不同,则会引发并发异常。
以下是我找到的3种解决方法。
  1. Change the original value so that it is now different than what exists in the database.

    public void SavePo(PoListing poListing) {
        Po po;
    
        try {
            po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
        } catch ( ArgumentNullException ) {
            throw new ArgumentNullException( "The PO does not exist." );
        }
    
        po.AssignedUserId = poListing.AssignedUserId;
    
        _context.Entry( po ).Property( u => u.VersionCol ).OriginalValue = poListing.VersionCol;
    
        _context.Update( po );
        _context.SaveChanges();
    }
    
  2. Get the entity with AsNoTracking(). Now EF Core won't just compare the original values.

    public void SavePo(PoListing poListing) {
        Po po;
    
        try {
            po = _context.Po.AsNoTracking().Where( p => p.Poid == poListing.PoId ).First();
        } catch ( ArgumentNullException ) {
            throw new ArgumentNullException( "The PO does not exist." );
        }
    
        po.AssignedUserId = poListing.AssignedUserId;
        po.VersionCol     = poListing.VersionCol;
    
        _context.Update( po );
        _context.SaveChanges();
    }
    
  3. Detach the entity from the context. Similar function as fix #2

    public void SavePo(PoListing poListing) {
        Po po;
    
        try {
            po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
        } catch ( ArgumentNullException ) {
            throw new ArgumentNullException( "The PO does not exist." );
        }
    
        po.AssignedUserId = poListing.AssignedUserId;
        po.VersionCol     = poListing.VersionCol;
    
        _context.Entry( po ).State = EntityState.Detached;
    
        _context.Update( po );
        _context.SaveChanges();
    }
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接