首页 > 其他分享 >Entity Framework Core中的并发处理

Entity Framework Core中的并发处理

时间:2024-05-13 14:30:35浏览次数:24  
标签:Core 令牌 Name 并发 数据库 Entity Framework var public

1.常见的并发处理策略

要了解如何处理并发,就要知道并发的一般处理策略

悲观并发策略

悲观并发策略,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守悲观的态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观并发策略大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的巨大开销,特别是对长事务而言,这样的开销在大量的并发情况下往往无法承受。

乐观并发策略

乐观并发策略,一般是基于数据版本 Version记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现.读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。需要注意的是,乐观并发策略机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性.

本篇就是讲解,如何在我们的Entity Framework Core中来使用和自定义我们的并发策略

2.Entity Framework Core并发令牌

要使用Entity Framework Core中的并发策略,就需要使用我们的并发令牌(ConcurrencyCheck)

在Entity Framework Core中,并发的默认处理方式是无视并发冲突的,任何修改语句在条件符合的情况下,都可以修改成功.

在高并发的情况下这种处理方式,肯定会给我们的数据库带来很多脏数据,所以,Entity Framework Core提供了并发令牌(ConcurrencyCheck)这个特性.

如果一个属性被配置为并发令牌,则EF将在保存这条记录时,会检查没有其他用户修改过数据库中的这个属性的值。EF使用了乐观并发策略,这意味着它将假定值没有改变,并尝试保存数据,但如果发现值已更改,则抛出异常。

举个例子,我们有一个用户类(User),我们配置 User中的 Name为并发令牌。这意味着,如果一个用户试图保存一个有些变化的 User,但另一个用户已经改变了 Name那么将抛出一个异常。这在应用中一般是可取的,以便我们的应用程序可以提示用户,在保存他们的改变之前,以确保此记录仍然代表同一个姓名的人。

2.1并发令牌在EF中工作的原理

当我们配置User中的Name为令牌的时候,EF会将并发令牌包含在Where、Update或delete命令的子句中并检查受影响的行数来实现验证。如果并发令牌仍然匹配,则一行将被更新。如果数据库中的值已更改,则不会更新任何行。

比如,当我们设置Name为并发令牌,然后通过ID来修改User的PassWord的时候,EF会生成如下的修改语句:

UPDATE [User] SET [PassWord] = @p1
WHERE [ID] = @p0 AND [Name] = @p2;

当然,这时候,Name不匹配了,受影响的行数返回为0.

2.2并发令牌的使用约定

    属性默认不被配置为并发令牌。

2.3并发令牌的使用方式

1.直接使用特性,如下配置UserName为并发令牌:

public partial class UserTable
    {
        public int Id { get; set; }
        [ConcurrencyCheck]
        public string UserName { get; set; }
        public string PassWord { get; set; }
        public int? ClassId { get; set; }
}

 2.使用FluentAPI配置属性为并发令牌

class MyContext : DbContext
{
    public DbSet<UserTable> People { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<UserTable>()
            .Property(p => p.UserName)
            .IsConcurrencyToken();
    }
}

以上2种方式,效果是一样的.

2.4使用时间戳和行级版本号

我们知道,SQL Server给我们提供了时间戳的属性(当然,几乎所有的关系数据库都有这个).下面举个SQL Server的例子

我们加一个时间戳字段为TimestampV,加上特性Timestamp,实体代码如下:

  public partial class UserTable
    {
        public int Id { get; set; }

        public string UserName { get; set; }
        public string PassWord { get; set; }
        public int? ClassId { get; set; }

        public ClassTable Class { get; set; }

        [Timestamp]
        public byte[] TimestampV { get; set; }
    }

CodeFrist生成的表如下:

自动帮我们生成的Timestamp类型的一个字段.

配置时间戳属性的方式也有2种,上面已经说了一种..特性的..

同样我们也可以使用Fluent API配置属性为时间戳,代码如下:

class MyContext : DbContext
{
    public DbSet<UserTable> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<UserTable>()
            .Property(p => p.TimestampV)
            .ValueGeneratedOnAddOrUpdate()
            .IsConcurrencyToken();
    }
}

3.如何根据需求自定义处理并发冲突

上面,我们已经配置好了需要并发处理的表,也配置好了相关的特性,下面我们就来讲讲如何使用它.

使用之前,我们先来了解一下,并发过程中所产生的3个值,也是我们需要处理的3个值

       1.当前值是应用程序尝试写入数据库的值。

       2.原始值是在进行任何编辑之前最初从数据库检索的值。

       3.数据库值是当前存储在数据库中的值。

当我们配置好上面的并发令牌时,在EF执行SaveChanges()操作并产生并发的时候,我们会得到DbUpdateConcurrencyException的异常信息,(注意:在不配置并发令牌时,这个异常一般不会触发)

前面,我们已经讲过乐观并发策略是一种性能较高,也比较实用的处理方式,所以我们就通过时间戳来处理这个并发的问题.

示例测试代码如下:

 public void Test()
 {
            //重新创建数据库,并新增一条数据
            using (var context = new School_TestContext())
            {
                context.Database.EnsureDeleted();
                context.Database.EnsureCreated();

                context.UserTable.Add(new UserTable { UserName = "John", PassWord = "Doe" });
                context.SaveChanges();
            }

            using (var context = new School_TestContext())
            {
                // 修改id为1的用户名称
                var person = context.UserTable.Single(p => p.Id == 1);
                person.UserName = "555-555-5555";

                // 直接通过访问数据库来修改同一条数据 (这里是为了模拟并发)
                context.Database.ExecuteSqlCommand("UPDATE dbo.UserTable SET UserName = 'Jane' WHERE ID = 1");

                try
                {
                    //尝试保存修改
                    int a = context.SaveChanges();
                }
                //获取并发异常
                catch (DbUpdateConcurrencyException ex)
                {
                    foreach (var entry in ex.Entries)
                    {
                        if (entry.Entity is UserTable)
                        {
                            var databaseEntity = context.UserTable.AsNoTracking().Single(p => p.Id == ((UserTable)entry.Entity).Id);
                            var databaseEntry = context.Entry(databaseEntity);

                            //当前上下文时间戳
                            var date = ConvertToTimeSpanString(entry.Property("TimestampV").CurrentValue);
                            var dateint = Int32.Parse(date, System.Globalization.NumberStyles.HexNumber);

                            //数据库时间戳
                            var datebase = ConvertToTimeSpanString(databaseEntry.Property("TimestampV").CurrentValue);
                            var dateint2 = Int32.Parse(datebase, System.Globalization.NumberStyles.HexNumber);
                            //如果当前上下文时间戳与数据库相同,或者更加新,则使用当前
                            if (dateint >= dateint2)
                            {
                                foreach (var property in entry.Metadata.GetProperties())
                                {
                                    //当前值
                                    var proposedValue = entry.Property(property.Name).CurrentValue;

                                    //原始值
                                    var originalValue = entry.Property(property.Name).OriginalValue;

                                    //数据库值
                                    var databaseValue = databaseEntry.Property(property.Name).CurrentValue;

                                    //更新当前值
                                    entry.Property(property.Name).CurrentValue = proposedValue;

                                    //更新原始值来保证修改成功
                                    entry.Property(property.Name).OriginalValue = databaseEntry.Property(property.Name).CurrentValue;
                                    // 尝试重新保存数据
                                    int aa = context.SaveChanges();
                                }
                            }
                        }
                        else
                        {
                            throw new NotSupportedException("无法处理并发," + entry.Metadata.Name);
                        }
                    }


                }
            }

        }

执行这段代码,会发现,符合我们乐观并发策略的要求.

值为最后修改的UserName,为Jane,如图:

解释一下,为何最终结果为Jane.

首先,我们添加了一条UserName为John的数据,我们在上下文中修改它为"555-555-5555",

这时候,产生并发,另一个上下文在这个SaveChang之前,就执行完成了,把值修改为了Jane,所以EF通过并发令牌发现匹配失败.则会触发异常.

在异常中,我们将当前上下文的版本号和数据库现有的版本号进行对比,发现当前上下文的版本号为过期数据,则不更新,并返回失败.

请仔细看代码中的注释.

注意:这里的例子是根据乐观并发处理策略要进行处理的.你可以根据你的业务,来任意处理当前值,原始值和数据库值,选择你需要的值保存.

标签:Core,令牌,Name,并发,数据库,Entity,Framework,var,public
From: https://www.cnblogs.com/liuqifeng/p/18189146

相关文章

  • Django - Rest Framework 框架
    目录DRF的安装与配置序列化类SerializerDRF的安装与配置为了简化API的开发过程,我们可以使用DjangoRestFramework框架实现API开发。使用框架开发不仅能减少代码冗余,还可以规范代码的编写格式,这对企业级开发来说很有必要,毕竟每个开发人员的编程风格存在一定的差异,开发规范可......
  • .net core 实现注册同一服务类型的多个服务实例
    1.注册服务。给IMyDependency注册两个不同的实现。builder.Services.AddSingleton<IMyDependency,MyDependency>();builder.Services.AddSingleton<IMyDependency,DifferentDependency>();2.依赖注入。通过 IEnumerable<IMyDependency>获取两个不同的实现,这里会按注......
  • 补档 https://github.com/taichi-framework/TaiChi/wiki/FAQ/9eeeef88cdbcee6a2834969
    taichi-framework/TaiChiPublicNotificationsFork 572 Star 5.9kCodePullrequestsActionsWikiSecurityInsightsFAQ weishueditedthispage onNov2,2018 · 17revisions如何使用点击右下角浮动按钮,然后选择“创建应用”......
  • 补档 https://github.com/taichi-framework/TaiChi/wiki/%E5%87%86%E5%A4%87%E4%BA%8B
    taichi-framework/TaiChiPublicNotificationsFork 570 Star 5.9kCodePullrequestsActionsWikiSecurityInsights准备事项 weishueditedthispage onJan22,2019 · 1revision太极·Magisk准备事项数据备份系统数据......
  • 补档 https://github.com/taichi-framework/TaiChi/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE
    taichi-framework/TaiChiPublicNotificationsFork 572 Star 5.9kCodePullrequestsActionsWikiSecurityInsights常见问题 weishueditedthispage onMar1,2019 · 14revisionsQ:无法触发Magisk版?A:建议使用Magisk17.x版......
  • 补档 https://github.com/taichi-framework/TaiChi/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF
    taichi-framework/TaiChiPublicNotificationsFork 569 Star 5.9kCodePullrequestsActionsWikiSecurityInsights如何使用 weishueditedthispage onJan22,2019 · 6revisions如何使用太极·Magisk?说明太极·Magisk......
  • 在 WPF 中集成 ASP.NET Core 和 WebView2 用于集成 SPA 应用
    背景我们有些工具在Web版中已经有了很好的实践,而在WPF中重新开发也是一种费时费力的操作,那么直接集成则是最省事省力的方法了。修改项目文件我们首先修改项目文件,让WPF项目可以包含ASP.NETCore的库,以及引用WebView2控件。<ProjectSdk="Microsoft.NET.Sdk"><Pr......
  • post与.net core接受请求参数
    ///ffff constres=awaitinstancs.post(  "Know/DocList_Insert",  {   Id:1,   Name:"酷酷酷酷酷",   ClientId:1,   EqId:1,  },  {headers:{"Content-Type":"application/form-data"}} ......
  • Core Impact 21.5 (Windows) - 高级渗透测试
    CoreImpact21.5(Windows)-高级渗透测试Fortra|CoreSecurityPenetrationtestingsoftware,ReleaseFeb2024请访问原文链接:CoreImpact21.5(Windows)-高级渗透测试,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgCoreImpact渗透测试软件,安全地发现和......
  • FFMpegCore 对音视频格式转换
    下载Nuget包FFMpegCore FFMpeg的官网下载转码程序点击Dowload 选择对应系统的下载源本次为Windows系统 选择Full标记的压缩包 解压后的文件结构ffmpeg版本 将bin文件夹下的ffmpeg.exe文件放置在程序项目的根目录下  视频格式转换以下是将.mov转.mp4///......