EF Core下利用Mysql进行数据存储在并发访问下的数据同步问题

小故事

在开始讲这篇文章之前,我们来说一个小故事,纯素虚构(真实的存钱逻辑并非如此)

小刘发工资后,赶忙拿着现金去银行,准备把钱存起来,而与此同时,小刘的老婆刘嫂知道小刘的品性,知道他发工资的日子,也知道他喜欢一发工资就去银行存起来,担心小刘卡里存的钱太多拿去“大宝剑”,于是,也去了银行,想趁着小刘把钱存进去后就把钱给取出来,省的夜长梦多。

 

小刘与刘嫂取得是两家不同的银行的ATM,所以两人没有碰面。

小刘插入银行卡存钱之前查询了自己的余额,ATM这样显示的:

 

与次同时,刘嫂也通过卡号和密码查询该卡内的余额,也是这么显示的:

 

刘嫂,很生气,没想到小刘偷偷藏了5000块钱的私房钱,就把5000块钱全部取出来了。所以把账户6217****888888的金额更新成0.(查询结果5000基础上减5000)

在这之后,小刘把自己发的3000块钱也存到了银行卡里,所以这边的这台ATM把账户6217****888888的金额更新成了8000.(在查询的5000基础上加3000)

最终的结果是,小刘的银行卡金额8000块钱,刘嫂也拿到了5000块钱。

反思?

故事结束了,很多同学肯定会说,要真有这样的银行不早就倒闭了?确实,真是的银行不可能是这样来计算的,可是我们的同学在设计程序的时候,却经常是这样的一个思路,先从数据库中取值,然后在取到的值的基础上对该值进行修改。可是,却有可能在取到值之后,另外一个客户也取了值,并在你保存之前对数据进行了更新。那么如何解决?

解决办法—乐观锁

常用的办法是,使用客观锁,那么什么是乐观锁?

下面是来自百度百科关于乐观锁的解释:

乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

 

通俗地讲,就是在我们设计数据库的时候,给实体添加一个Version的属性,对实体进行修改前,比较该实体现在的Version和自己当年取出来的Version是否一致,如果一致,对该实体修改,同时,对Version属性+1;如果不一致,则不修改并触发异常。

 

作为强大的EF(Entiry FrameWork)当然对这种操作进行了封装,不用我们自己独立地去实现,但是在查询微软官方文档时,我们发现,官方文档是利用给Sql Server数据库添加timestamp标签实现的,Sql Server在数据发生更改时,能自动地对timestamp进行更新,但是Mysql没有这样的功能的,我是通过并发令牌(ConcurrencyToken)实现的。

 

什么是并发令牌(ConcurrencyToken)?

所谓的并发令牌,就是在实体的属性中添加一块令牌,当对数据执行修改操作时,系统会在Sql语句后加一个Where条件,筛选被标记成令牌的字段是否与取出来一致,如果不一致了,返回的肯定是影响0行,那么此时,就会对抛出异常。

具体怎么用?

首先,新建一个WebApi项目,然后在该项目的Model目录(如果没有就手动创建)新建一个student实体。其代码如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;


namespace Bingfa.Model

{

    public class Student

    {

        public int id { get; set; }

        public string Name { get; set; }

        public string Pwd { get; set; }

        public int Age { get; set; }

        public DateTime LastChanged { get; set; }

    }

}

然后创建一个数据库上下文,其代码如下:

using System;

using System.Collections.Generic;

using System.ComponentModel.DataAnnotations.Schema;

using System.Linq;

using System.Threading.Tasks;

using Microsoft.EntityFrameworkCore;


namespace Bingfa.Model

{

    public class SchoolContext : DbContext

    {

        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)

        {


        }


        public DbSet<Student> students { get; set; }


        protected override void OnModelCreating(ModelBuilder modelBuilder)

        {

            modelBuilder.Entity<Student>().Property(p => p.LastChanged).IsConcurrencyToken() ;

        }

    }

}

红色部分,我们把Student的LastChange属性标记成并发令牌。

然后在依赖项中选择Nuget包管理器,安装  Pomelo.EntityFrameworkCore.MySql 改引用,该引用可以理解为Mysql的EF Core驱动。

安装成功后,在appsettings.json文件中写入Mysql数据库的连接字符串。写入后,该文件如下:其中红色部分为连接字符串

{

  "Logging": {

    "IncludeScopes": false,

    "Debug": {

      "LogLevel": {

        "Default": "Warning"

      }

    },

    "Console": {

      "LogLevel": {

        "Default": "Warning"

      }

    }

  },

  "ConnectionStrings": { "Connection": "Data Source=127.0.0.1;Database=school;User ID=root;Password=123456;pooling=true;CharSet=utf8;port=3306;" }

}


然后,在Stutup.cs中对Mysql进行依赖注入:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

using Bingfa.Model;

using Microsoft.AspNetCore.Builder;

using Microsoft.AspNetCore.Hosting;

using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.Extensions.Logging;

using Microsoft.Extensions.Options;


namespace Bingfa

{

    public class Startup

    {

        public Startup(IConfiguration configuration)

        {

            Configuration = configuration;

        }


        public IConfiguration Configuration { get; }


        // This method gets called by the runtime. Use this method to add services to the container.

        public void ConfigureServices(IServiceCollection services)

        {

            var connection = Configuration.GetConnectionString("Connection");

            services.AddDbContext<SchoolContext>(options =>

            {

                options.UseMySql(connection);

                options.UseLoggerFactory(new LoggerFactory().AddConsole());

            });

            services.AddMvc();

        }


        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)

        {

            if (env.IsDevelopment())

            {

                app.UseDeveloperExceptionPage();

            }


            app.UseMvc();

        }

    }

}

其中,红色字体部分即为对Mysql数据库上下文进行注入,蓝色背景部分,为将sql语句在控制台中输出,便于我们查看运行过程中的sql语句。

以上操作完成后,即可在数据库中生成表了。打开程序包管理控制台,打开方式如下:

打开后分别输入以下两条命令:、

add-migration init

update-database

是分别输入哦,不是一次输入两条,语句执行效果如图:

执行完成后即可在Mysql数据库中看到生成的数据表了,如图。

最后,我们就要进行实际的业务处理过程的编码了。打开ValuesController.cs的代码,我修改后代码如下

using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

using Bingfa.Model;

using Microsoft.AspNetCore.Mvc;


namespace Bingfa.Controllers

{

    [Route("api/[controller]")]

    public class ValuesController : Controller

    {

        private SchoolContext schoolContext;


        public ValuesController(SchoolContext _schoolContext)//控制反转,依赖注入

        {

            schoolContext = _schoolContext;

        }

        

        // GET api/values/5

        [HttpGet("{id}")]

        public Student Get(int id)

        {

            return schoolContext.students.Where(p => p.id == id).FirstOrDefault();  //通过Id获取学生数据

        }

        [HttpGet]

        public List<Student> Get()

        {

            return schoolContext.students.ToList();   //获取所有的学生数据

        }


        // POST api/values

        [HttpPost]

        public string Post(Student student)   //更新学生数据

        {

            if (student.id != 0)

            {

                try

                {

                    Student studentDataBase = schoolContext.students.Where(p => p.id == student.id).FirstOrDefault();   //首先通过Id找到该学生


                    //如果查找到的学生的LastChanged与Post过来的数据的LastChanged的时间相同,则表示数据没有修改过

                    //为了控制时间精度,对时间进行秒后取三位小数

                    if (studentDataBase.LastChanged.ToString("yyyy-MM-dd HH:mm:ss.fff").Equals(student.LastChanged.ToString("yyyy-MM-dd HH:mm:ss.fff")))

                    {

                        studentDataBase.LastChanged=DateTime.Now;//把数据的LastChanged更改成现在的时间

                        studentDataBase.Age = student.Age;

                        studentDataBase.Name = student.Name;

                        studentDataBase.Pwd = student.Pwd;

                        schoolContext.SaveChanges();  //保存数据

                    }

                    else

                    {

                        throw new Exception("数据已经修改,请刷新查看");

                        //return "";

                    }

                }

                catch (Exception e)

                {

                    return e.Message;

                }

                return "success";

            }

            return "没有找到该Student";

        }


        // PUT api/values/5

        [HttpPut("{id}")]

        public void Put(int id, [FromBody]string value)

        {


        }


        // DELETE api/values/5

        [HttpDelete("{id}")]

        public void Delete(int id)

        {

        }

    }

}

主要代码在Post方法中。

为了方便看到运行的Sql语句,我们需要把启动程序更改成项目本身而不是IIS。如图

启动后效果如图:

我们先往数据库中插入一条数据

然后,通过访问http://localhost:56295/api/values/1即可获取该条数据,如图:

我们把该数据修改age成2之后,利用postMan把数据post到控制器,进行数据修改,如图,修改成功

那么,我们把age修改成3,LastChange的数据依然用第一次获取到的时间进行Post,那么返回的结果如图:

可以看到,执行了catch内的代码,触发了异常,没有接受新的提交。

最后,我们看看加了并发锁之后的sql语句:

从控制台中输出的sql语句可以看到  对LastChanged属性进行了筛选,只有当LastChanged与取出该实体时一致,该更新才会执行。

这就是乐观锁的实现过程。

并发访问测试程序

为了对该程序进行测试,我特意编写了一个程序,多线程地对数据库的数据进行get和post,模拟一个并发访问的过程,代码如下:

using System;

using System.Net;

using System.Net.Http;

using System.Threading;

using Newtonsoft.Json;


namespace Test

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("输入回车开始测试...");

            Console.ReadKey();

            ServicePointManager.DefaultConnectionLimit = 1000;

            for (int i = 0; i < 10; i++)

            {

                Thread td = new Thread(new ParameterizedThreadStart(PostTest));

                td.Start(i);

                Thread.Sleep(new Random().Next(1,100));//随机休眠时长

            }

            Console.ReadLine();

        }

        public static void PostTest(object i)

        {

            try

            {

                string url = "http://localhost:56295/api/values/1";//获取ID为1的student的信息

                Student student = JsonConvert.DeserializeObject<Student>(RequestHandler.HttpGet(url));

                student.Age++;//对年龄进行修改

                string postData = $"Id={ student.id}&age={student.Age}&Name={student.Name}&Pwd={student.Pwd}&LastChanged={student.LastChanged.ToString("yyyy-MM-dd HH:mm:ss.fff")}";

                Console.WriteLine($"线程{i.ToString()}Post数据{postData}");

                string r = RequestHandler.HttpPost("http://localhost:56295/api/values", postData);

                Console.WriteLine($"线程{i.ToString()}Post结果{r}");

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }


        }

    }

}

测试效果:

可以看到,部分修改成功了,部分没有修改成功,这就是乐观锁的效果。

项目的完整代码我已经提交到github,有兴趣的可以访问以下地址查看:

https://github.com/liuzhenyulive/Bingfa

第一次这么认真地写一篇文章,如果喜欢,请推荐支持,谢谢!


原文:https://www.cnblogs.com/CoderAyu/p/8530798.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321976.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

牛客练习赛50-记录

正题 比赛链接:https://ac.nowcoder.com/acm/contest/1080#question 成绩 本届 升高二届 总结 以后还是不要写太多自己不擅长的写法&#xff0c;空间要多检查&#xff0c;不要像个傻逼一样啥都写错。 尽量不要为了省一点空间和时间写一些不舒服的东西&#xff0c;尽量在能…

物联网框架ServerSuperIO在.NetCore实现跨平台的实践路线

正所谓天下大势&#xff0c;不跟风不行。你不跨平台&#xff0c;很low嘛。java说&#xff1a;你们能跨嘛&#xff0c;跨给我看看。C#说&#xff1a;不要强人所难嘛。java说&#xff1a;能部署在云上吗&#xff1f;docker&#xff1f;微服务&#xff1f;C#说&#xff1a;不要强人…

使用WebApiClient请求和管理Restful Api

前言本篇文章的内容是WebApiClient应用说明篇&#xff0c;如果你没有了解过WebApiClient&#xff0c;可以先阅读以下相关文章&#xff1a;WebApi client 的面向切面编程我来给.Net设计一款HttpClient.Net45下HttpClient的几个缺陷.net的retrofit--WebApiClient库.net的retrofit…

拥抱.NET Core系列:MemoryCache 缓存选项

MSCache项目MSCache 目前最新的正式版是 2.0.0&#xff0c;预览版是2.1.0&#xff0c;会与 .NETCore 2.1 一起发布。本篇用了2.0.0版本开源在 GitHub 上&#xff0c;仓库地址是&#xff1a;https://github.com/aspnet/CachingNuGet地址为&#xff1a;https://www.nuget.org/pac…

牛客练习赛51-记录

正题 比赛链接:https://ac.nowcoder.com/acm/contest/1083#question 成绩 可怜的zycT3zycT3zycT3被n0n0n0卡了半天&#xff0c;这里感谢一下排雷 总结 比赛状态较好&#xff0c;后面没有T6T6T6的题解 T1:abcT1:abcT1:abc 题目大意 给出一个字符串&#xff0c;求有多少个abc…

Metrics.net + influxdb + grafana 构建WebAPI的自动化监控和预警

前言这次主要分享通过Metrics.net influxdb grafana 构建WebAPI的自动化监控和预警方案。通过执行耗时&#xff0c;定位哪些接口拖累了服务的性能&#xff1b;通过请求频次&#xff0c;设置适当的限流和熔断机制&#xff0c;拦截非法或不合理的请求&#xff0c;保障服务的可用…

EF Core 2.1路线图:视图、GROUP BY和惰性加载

Entity Framework Core一直追随着初始Entity Framework的发展&#xff0c;并不断推陈出新。它首先推出的是对视图的支持&#xff0c;这听起来有些耸人听闻。在即将推出的EF Core 2.1之前&#xff0c;EF Core并未对数据库视图提供官方的支持&#xff0c;也不支持缺少主键的数据库…

计算机网络总结

一、计算机网络体系 &#xff08;1&#xff09;OSI分层 &#xff08;7层&#xff09; 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。 &#xff08;2&#xff09;TCP/IP分层&#xff08;4层&#xff09; 网络接口层、 网际层、运输层、 应用层。 &#xff0…

从技术角度讨论微服务

本文希望从技术角度来探讨下微服务&#xff0c;因此&#xff0c;不会过多地谈及如何根据业务进行微服务划分&#xff0c;更多是介绍微服务的相关技术&#xff0c;微服务的业务划分方法可参考“领域驱动设计“相关方法论。微服务的两个程度一、服务化复杂的单体架构会有以下的挑…

jzoj6344-[NOIP2019模拟2019.9.7]Huge Counting【组合数,状压dp】

正题 题目大意 定义函数f(x)(xf(x)(xf(x)(x为一个序列))) 若任意一个xi1x_i1xi​1那么有f(x)1f(x)1f(x)1 若有一个xi0x_i0xi​0那么有f(x)0f(x)0f(x)0 其他的&#xff0c;有f(x)(∑j1nf(x1...,xj−1,...xn))%2f(x)(\sum_{j1}^nf(x_{1}...,x_j-1,...x_n))\% 2f(x)(j1∑n​f(x1​…

拥抱.NET Core系列:MemoryCache 缓存域

MSCache项目MSCache 目前最新的正式版是 2.0.0&#xff0c;预览版是2.1.0&#xff0c;会与 .NETCore 2.1 一起发布。本篇用了2.0.0版本开源在 GitHub 上&#xff0c;仓库地址是&#xff1a;https://github.com/aspnet/CachingNuGet地址为&#xff1a;https://www.nuget.org/pac…

牛客小白月赛17-记录(附题解)

正题 比赛链接:https://ac.nowcoder.com/acm/contest/1085#question 成绩 总结 除了那道积分数学其他还好 后面没有FFF题的题解 T1:小sun的假期T1:小sun的假期T1:小sun的假期 题目大意 长度为nnn的序列&#xff0c;mmm个区间&#xff0c;求最大的没有被任何区间覆盖的区间…

安全、高效的MySQL DDL解决方案

MySQL作为目前应用最广泛的开源关系型数据库&#xff0c;是许多网站、应用和商业产品的主要数据存储。在生产环境&#xff0c;线上数据库常常面临着持续的、不断变化的表结构修改&#xff08;DDL&#xff09;&#xff0c;如增加、更改、删除字段和索引等等。其中一些DDL操作在M…

DNS总结

一、DNS &#xff08;1&#xff09;简介 域名系统&#xff08;英文&#xff1a;Domain Name System&#xff0c;缩写&#xff1a;DNS&#xff09;是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库&#xff0c;能够使人更方便地访问互联网。 DNS 协议也是…

谈谈在.NET Core中使用Redis和Memcached的序列化问题

前言在使用分布式缓存的时候&#xff0c;都不可避免的要做这样一步操作&#xff0c;将数据序列化后再存储到缓存中去。序列化这一操作&#xff0c;或许是显式的&#xff0c;或许是隐式的&#xff0c;这个取决于使用的package是否有帮我们做这样一件事。本文会拿在.NET Core环境…

牛客练习赛52-记录

正题 比赛链接:https://ac.nowcoder.com/acm/contest/1084#question 成绩 T1:T1:T1:数数 题目大意 给出nnn&#xff0c;求∑i1n∑j1n(i∗j)\sum_{i1}^n \sum_{j1}^n (i*j)i1∑n​j1∑n​(i∗j) 和 ∏i1n∏j1n(i∗j)\prod_{i1}^n\prod_{j1}^n(i*j)i1∏n​j1∏n​(i∗j) 解题…

Windows Developer Day - Windows AI Platform

本次 Windows Developer Day&#xff0c;最值得期待的莫过于 Windows AI Platform 了&#xff0c;可以说是千呼万唤始出来。观看直播的开发者们&#xff0c;留言最多的也是 Windows AI Platform。下面结合微软提供的展示过程&#xff0c;文档和 Git Sample 来详细分析一下。基础…

EF Core:一统SQL和NoSQL数据库

推出EF Core的初衷之一&#xff0c;就是开发出一种可在很少甚至不更改代码的情况下使用SQL和NoSQL数据库的模型。Microsoft正向此目标迈出第一步&#xff0c;发布了用于Azure Cosmos DB的实验性EF提供程序&#xff08;provider&#xff09;。据EF 2.1路线图介绍&#xff1a;Cos…

Java 并发总结——进程与线程

一、进程与线程 &#xff08;1&#xff09;线程与进程 进程是程序在一个数据集合上运行的过程&#xff0c;它是系统进行资源分配和调度的一个独立单位。进程实体由程序段&#xff0c; 数据段 PCB&#xff08;进程控制块&#xff09;组成。 线程可以看做轻量级进程&#xff0c;…

.NET Core使用skiasharp文字头像生成方案(基于docker发布)

一、问题背景目前.NET Core下面针对于图像处理的库微软并没有集成&#xff0c;在.NET FrameWork下我们已经习惯使用System.Drawing类库做简单的图像处理&#xff0c;到了.NET Core下一脸懵逼的我&#xff0c;只能百度谷歌看看有没啥解决方案&#xff0c;好在网上资料也多&#…