导读:本期聚焦于小伙伴创作的《C#怎么处理EFCore并发更新_C# RowVersion时间戳冲突解决【进阶】》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C#怎么处理EFCore并发更新_C# RowVersion时间戳冲突解决【进阶】》有用,将其分享出去将是对创作者最好的鼓励。

在C#项目中使用EFCore操作数据库时,多用户同时修改同一条数据会触发并发更新问题,若没有合理的控制机制,后提交的更新会直接覆盖先提交的更新,造成数据丢失或者业务逻辑错误。RowVersion是数据库层面提供的并发控制方案,能有效解决这类冲突,下面详细介绍其实现方式。

C#怎么处理EFCore并发更新_C# RowVersion时间戳冲突解决【进阶】

并发更新冲突的产生场景

假设数据库中存在一条用户积分记录,用户A和用户B同时查询到该记录的积分值为100,用户A将其修改为120并提交,用户B随后也将自己查询到的100修改为150并提交。如果没有并发控制,用户B的更新会直接覆盖用户A的修改,最终积分值为150,用户A的20积分修改就丢失了。

EFCore默认不会主动检测这类冲突,需要我们手动配置并发令牌来实现冲突检测。

RowVersion的工作原理

RowVersion是SQL Server等数据库提供的自动递增的时间戳类型字段,每次对记录进行更新时,数据库会自动将该字段的值加1。当EFCore执行更新操作时,会将查询时获取的RowVersion值作为更新条件的一部分,如果当前数据库中的RowVersion值和查询时的值不一致,说明记录已经被其他操作修改过,更新语句会影响0行数据,EFCore就会抛出并发冲突异常。

EFCore中配置RowVersion

1. 实体类定义

首先在实体类中添加RowVersion属性,使用byte[]类型,并且添加Timestamp特性或者Fluent API配置将其标记为并发令牌。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

public class UserScore
{
    [Key]
    public int Id { get; set; }

    public int UserId { get; set; }

    public int Score { get; set; }

    // RowVersion字段,标记为时间戳并发令牌
    [Timestamp]
    public byte[] RowVersion { get; set; }
}

2. Fluent API配置方式

如果不想使用数据注解,也可以在DbContext的OnModelCreating方法中通过Fluent API配置RowVersion:

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public DbSet<UserScore> UserScores { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 配置RowVersion为并发令牌
        modelBuilder.Entity<UserScore>()
            .Property(u => u.RowVersion)
            .IsRowVersion();
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // 替换为实际的数据库连接字符串
        optionsBuilder.UseSqlServer("Server=127.0.0.1;Database=TestDb;Trusted_Connection=True;");
    }
}

3. 数据库迁移

配置完成后,执行EFCore迁移命令会在数据库中生成对应的RowVersion字段,SQL Server中该字段类型为timestamp或者rowversion

处理并发冲突的完整流程

当并发冲突发生时,EFCore会抛出DbUpdateConcurrencyException异常,我们需要捕获该异常并根据业务需求处理,常见的处理方式有重试更新、提示用户数据已变更等。

基础处理示例

using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;

public class ScoreService
{
    private readonly AppDbContext _dbContext;

    public ScoreService(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void UpdateUserScore(int userId, int newScore)
    {
        try
        {
            var userScore = _dbContext.UserScores.FirstOrDefault(u => u.UserId == userId);
            if (userScore == null)
            {
                Console.WriteLine("用户积分记录不存在");
                return;
            }

            // 修改积分
            userScore.Score = newScore;
            _dbContext.SaveChanges();
            Console.WriteLine("更新成功");
        }
        catch (DbUpdateConcurrencyException ex)
        {
            // 捕获并发冲突异常
            Console.WriteLine("数据已被其他用户修改,请重新查询后再操作");
            // 可以获取当前数据库中的最新值
            var entry = ex.Entries.Single();
            var databaseValues = entry.GetDatabaseValues();
            if (databaseValues != null)
            {
                Console.WriteLine($"当前数据库最新积分值为:{databaseValues["Score"]}");
            }
        }
    }
}

进阶重试处理示例

如果业务允许,可以在捕获到并发异常后重新查询最新数据,合并修改后再次尝试更新,最多重试指定次数:

using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;

public class ScoreService
{
    private readonly AppDbContext _dbContext;
    private const int MaxRetryCount = 3; // 最大重试次数

    public ScoreService(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void UpdateUserScoreWithRetry(int userId, int newScore)
    {
        int retryCount = 0;
        while (retryCount < MaxRetryCount)
        {
            try
            {
                var userScore = _dbContext.UserScores.FirstOrDefault(u => u.UserId == userId);
                if (userScore == null)
                {
                    Console.WriteLine("用户积分记录不存在");
                    return;
                }

                // 保留本地修改的字段,这里示例为直接覆盖为新的积分值
                userScore.Score = newScore;
                _dbContext.SaveChanges();
                Console.WriteLine("更新成功");
                return;
            }
            catch (DbUpdateConcurrencyException)
            {
                retryCount++;
                if (retryCount >= MaxRetryCount)
                {
                    Console.WriteLine("多次重试后仍然发生冲突,请稍后重试");
                    return;
                }
                Console.WriteLine($"发生并发冲突,第{retryCount}次重试");
                // 重新加载最新数据到当前上下文
                _dbContext.ChangeTracker.Entries<UserScore>().First().Reload();
            }
        }
    }
}

进阶注意事项

  • RowVersion字段仅对标记了该令牌的实体生效,如果实体没有配置并发令牌,EFCore不会检测并发冲突。
  • 如果项目使用的是MySQL数据库,可以使用IsConcurrencyToken()配置DateTime或者long类型的版本字段,配合触发器或者应用层逻辑实现类似RowVersion的效果。
  • 并发冲突处理需要结合具体业务场景,比如涉及金额、库存等核心数据,建议优先提示用户手动确认,避免自动重试导致业务逻辑错误。
  • 不要将RowVersion字段暴露给前端,避免前端篡改该值导致并发控制失效。
并发控制的核心是平衡数据一致性和系统性能,RowVersion是轻量级的数据库层面方案,适合大多数单库场景的并发冲突处理,如果是分布式场景,可能需要结合分布式锁等机制实现更复杂的控制。

EFCoreRowVersion并发更新C#时间戳修改时间:2026-06-11 03:45:42

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。