EntityFramework Core进行读写分离最佳实践方式,了解一下(一)?
前言
本来打算写ASP.NET Core MVC基础系列内容,看到有园友提出如何实现读写分离,这个问题提的好,大多数情况下,对于园友在评论中提出的问题,如果是值得深究或者大多数同行比较关注的问题我都会私下去看看,然后进行对应解答,若有叙述不当之处,还请海涵。我们稍微过一下事务,本文略长,请耐心阅读。
事务
什么是事务呢?有关事务详解可参看我写的SQL Server基础系列,我们可归结为一句话:多个提交要么全部成功,要么全部失败即同生共死,没有临阵脱逃者。那么问题来了,用了事务有什么作用或者说有什么优点呢?事务允许我们将相关操作组合打包,以确保应用程序数据的一致性。那么使用事务又有何缺点呢?使用事务虽然确保了数据一致性等等,但是会影响性能,可能会造成死锁。那么问题又来了,既然有其优缺点,那么我们是否可以手写逻辑实现数据一致性呢?当然可以,我们可以模拟事务回滚、提交的效果,但是这也无法百分百保证。
调用SaveChanges是否在一个事务内?
首先我们在控制台中进行如下数据添加,然后添加日志打印。
using (var context = new EFCoreDbContext())
{
var blog = new Blog()
{
IsDeleted = false,
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Name = "demo",
Url = "http://www.cnblogs.com/createmyslef"
};
context.Add(blog);
context.SaveChanges();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var loggerFactory = new LoggerFactory();
loggerFactory.AddConsole(LogLevel.Debug);
optionsBuilder.UseLoggerFactory(loggerFactory);
optionsBuilder.UseSqlServer("data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=Demo1;integrated security=True;");
}

我们通过打印日志得知在调用SaveChanges方法时则包含在事务中进行提交,所以请那些可在项目中用到多表添加担心出现问题就加上了如下开启事务,这很显然是多此一举。
using (var context = new EFCoreDbContext())
{ using (var transaction = context.Database.BeginTransaction())
{ var blog = new Blog()
{
IsDeleted = false,
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Name = "demo",
Url = "http://www.cnblogs.com/createmyslef"
};
context.Add(blog);
context.SaveChanges(); try
{
transaction.Commit();
}
catch (Exception)
{
//TODO
}
}
}
看到如上日志信息还不是更加确定是不是,我们再来看看在上下文中的 context.Database.AutoTransactionsEnabled 方法,详细解释如下:
// 摘要:
// Gets or sets a value indicating whether or not a transaction will be created
// automatically by Microsoft.EntityFrameworkCore.DbContext.SaveChanges if none
// of the 'BeginTransaction' or 'UseTransaction' methods have been called.
// Setting this value to false will also disable the Microsoft.EntityFrameworkCore.Storage.IExecutionStrategy
// for Microsoft.EntityFrameworkCore.DbContext.SaveChanges
// The default value is true, meaning that SaveChanges will always use a transaction
// when saving changes.
// Setting this value to false should only be done with caution since the database
// could be left in a corrupted state if SaveChanges fails.
通过AutoTransactionsEnabled方法解释得知:其默认值为True,也就意味着当调用SaveChanges方法将使用事务性提交。当然我们可以在上下文构造函数中设置是否全局禁用事务,如下:
public class EFCoreDbContext : DbContext
{
public EFCoreDbContext()
{
Database.AutoTransactionsEnabled = false;
}
}
在EF Core中我们什么时候会用到事务呢?如果是单一上下文,单一数据库,那么事务跟我们没啥关系,压根不用管事务。如果是在单一数据库使用多个上下文(跨上下文)或者多个数据库,这个时候事务就闪亮登场了。比如对于电商中的商品、购物车、订单管理、支付、物流,我们完全可以实例化五个不同的上下文,此时将涉及到跨上下文操作使用事务保持数据一致性,当然这是针对在同一关系数据库中。或者是实例化同一上下文多次来使用事务保持数据一致性。可以参看官网的介绍《https://docs.microsoft.com/en-us/ef/core/saving/transactions》,没什么看头,都是针对同一数据库操作,无非还是我所说的跨上下文、使用上下文结合底层DbConnection来使用事务共享连接等等 ,稍微大一点的看点则是在EF Core 2.1中引入了System.Transactions,可指定隔离级别以及使用ambient transactions(查资料作用是存在多个事务,事务之间存在连接,如此一来将显得整个作用域非常冗长,通过使用此事务则在特定范围内,所有连接都将包含在该事务中),在此就不占用篇幅介绍了,和大家一样我们最关心的是分布式事务,也就是使用不同上下文针对多个数据库,但是遗憾的是直到EF Core 2.1还不支持分布式事务,因为.NET Core中相关APi也还不完善,继续等待吧。
读写分离
随着流量的进入,数据库将承受不可抗拒的压力,单一数据库将不再适用,这都是随着项目的演变所带来架构的迭代改变,这个时候就涉及到分库,对于查询的数据单独作为一个数据库,作为数据的更改也单独用一个数据库,再结合那些什么负载均衡等等,数据库压力也就减弱了许多。只作查询的数据库我们称之为从数据库,对于数据库更改的数据库称之为主数据库,主-从数据库(Master-Slave)数据的同步方式也有很多,虽然我也没接触过,我们就利用SQL Server中的复制进行发布-订阅来模拟演示还是可以的。我们来看看.NET Core Web应用程序如何实现读写分离,额外加一句,项目中我也未用到,都是我私下的研究,方案行不行,合不合理可以一起探讨。我们创建了两个Demo数据库,如下:

我们将Demo1作为主数据库,Demo2作为从数据库,接下来用一张动态图演示创建复制发布-订阅(每隔10秒发布一次)。
我们给出Demo1上下文,Demo2和其一样,按照正常做法接下来我们应该在.NET Core Web应用程序中注入Demo1和Demo2上下文,如下:
public class Demo1DbContext : DbContext
{
public Demo1DbContext(DbContextOptions<Demo1DbContext> options) :base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
}
public class Demo2DbContext : DbContext
{
public Demo2DbContext(DbContextOptions<Demo2DbContext> options) :base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
}
services.AddDbContext<Demo1DbContext>(options =>
{
options.UseSqlServer("data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=Demo1;integrated security=True;");
}).AddDbContext<Demo2DbContext>(options =>
{
options.UseSqlServer("data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=Demo2;integrated security=True;");
});
然后我们创建Demo控制器,通过Demo1上下文添加数据,Demo2上下文读取数据,如下:
[Route("[controller]")]
public class DemoController : Controller
{
private readonly Demo1DbContext _demo1DbContext;
private readonly Demo2DbContext _demo2DbContext;
public DemoController(Demo1DbContext demo1DbContext, Demo2DbContext demo2DbContext)
{
_demo1DbContext = demo1DbContext;
_demo2DbContext = demo2DbContext;
}
[HttpGet("index")]
public IActionResult Index()
{
var blogs = _demo2DbContext.Blogs.ToList();
return View(blogs);
}
[HttpGet("create")]
public IActionResult CreateDemo1Blog()
{
var blog = new Blog()
{
IsDeleted = false,
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Name = "demoBlog1",
Url = "http://www.cnblogs.com/createmyslef"
};
_demo1DbContext.Blogs.Add(blog);
_demo1DbContext.SaveChanges();
return RedirectToAction(nameof(Index));
}
}
@{
ViewData["Title"] = "Index";
}
@model IEnumerable<EFCore.Blog>
<div class="panel panel-primary">
<div class="panel-heading panel-head">博客列表</div>
<div class="panel-body">
<table class="table" style="margin: 4px">
<tr>
<th>
@Html.DisplayNameFor(model => model.Id)
</th>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Url)
</th>
</tr>
@if (Model != null)
{
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Id)
</td>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Url)
</td>
</tr>
}
}
</table>
</div>
</div>

我们看到通过Demo1上下文添加数据后重定向到Demo2上下文查询到的列表页面,到了10秒自动同步到Demo2数据库,通过刷新可以看到数据显示。虽然结果如我们所期望,但是实现的路径却令我们不是那么如意,因为所用实体都是一样的,只是说所连接数据库不一样而已,但是我们需要创建两个不同的上下文实例,很显然这不是最佳实践方式,那么我们如何做才是最佳实践方式呢?接下来我们再来创建一个Demo3数据库,表结构和Demo1、Demo2一致,如下:

接下来我们在.NET Core Web应用程序Demo1、Demo2上下文所在的类库中创建如下扩展方法(方便有同行需要学习,给出Demo项目基本结构)。

public static class ChangeDatabase
{
public static void ChangeToDemo3Db(this DbContext context)
{
context.Database.GetDbConnection().ConnectionString = "data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=Demo3;integrated security=True;";
}
}
我们暂且不去看为何这样设置,我们只是添加上下文扩展方法,更改连接为Demo3的数据库,然后接下来我们获取博客列表时,调用上述扩展方法,请问:是否可以获取到Demo3的数据或者说是否会抛出异常呢?我们依然通过动态图来进行演示,如下:

一直以来我们认为利用 context.Database.GetDbConnection() 方法可以回到ADO.NET进行查询,但是我们通过实际证明,我们可以设置其他数据库连接从而达到读写分离最佳实践方式,免去再实例化一个上下文。所以对于上述我们配置的Demo1和Demo2上下文,我们大可只需要Demo1上下文即主数据库,对于从数据库进行查询,我们只需在Demo1上下文的基础上更该连接字符串即可,如下:
public static class ChangeDatabase
{
public static void ChangeToDemo2Db(this DbContext context)
{
context.Database.GetDbConnection().ConnectionString = "data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=Demo2;integrated security=True;";
}
} [HttpGet("index")]
public IActionResult Index()
{
_demo1DbContext.ChangeToDemo2Db();
var blogs = _demo1DbContext.Blogs.ToList();
return View(blogs);
}
接下来问题来了,那么为何更改Demo1上下文连接字符串就能转移到其他数据库查询呢?就是为了解决读写分离免去实例化上下文即Demo2的情况,但是内部是如何实现的呢?因为EF Core内部添加了方法实现IRelationalConnection接口,使得我们可以在已存在的上下文实例上重新设置连接字符串即更换数据库,但是其前提是必须保证当前上下文连接已关闭,也就是说比如我们在同一个事务中利用当前上下文进行更改操作,然后更改连接字符串进行更改操作,最后提交事务,因为在此事务内,当前上下文连接还未关闭,所以再更改连接字符串后进行数据库更改操作,将必定会抛出异常。
总结
花了两天时间研究研究,本文比较详细讲解了对于读写分离后,如何进行数据查询和更改操作最佳实践方式,不知道算不算最好的解决方案,若您有更好的方案,欢迎一起探讨或者说还有其他理解和疑问,也欢迎在评论中提出。
EntityFramework Core进行读写分离最佳实践方式,了解一下(一)?的更多相关文章
- EntityFramework Core进行读写分离最佳实践方式,了解一下(二)?
前言 写过上一篇关于EF Core中读写分离最佳实践方式后,虽然在一定程度上改善了问题,但是在评论中有的指出更换到从数据库,那么接下来要进行插入此时又要切换到主数据库,同时有的指出是否可以进行底层无感 ...
- EF Core 实现读写分离的最佳方案
前言 公司之前使用Ado.net和Dapper进行数据访问层的操作, 进行读写分离也比较简单, 只要使用对应的数据库连接字符串即可. 而最近要迁移到新系统中,新系统使用.net core和EF Cor ...
- EF core 实现读写分离解决方案
我们公司2019年web开发已迁移至.NET core,目前有部分平台随着用户量增加,单一数据库部署已经无法满足我们的业务需求,一直在寻找EF CORE读写分离解决方案,目前在各大技术论坛上还没找到很 ...
- 探寻ASP.NET MVC鲜为人知的奥秘(3):寻找多语言的最佳实践方式
如果你的网站需要被世界各地的人访问,访问者会使用各种不同的语言和文字书写习惯,那么创建一个支持多语言的网站就是十分必要的了,这一篇文章就讲述怎么快速合理的创建网站对多语言的支持.接下来通过一个实例来讲 ...
- .NET Core调用WCF的最佳实践
现在.NET Core貌似很火,与其他.NET开发者交流不说上几句.NET Core都感觉自己落伍了一样.但是冷静背后我们要也看到.NET Core目前还有太多不足,别的不多说,与自家的服务框架WCF ...
- React 代码共享最佳实践方式
任何一个项目发展到一定复杂性的时候,必然会面临逻辑复用的问题.在React中实现逻辑复用通常有以下几种方式:Mixin.高阶组件(HOC).修饰器(decorator).Render Props.Ho ...
- Fibonacci(斐波那契数列)的最佳实践方式(JavaScript)
1)低级版本 var fibonacci = function(n) { if (n == 0 || n == 1) { return n; } else { return fibonacci(n - ...
- Entity Framework Core 实现读写分离
在之前的版本中我们可用构造函数实现,其实现在的版本也一样,之前来构造连接字符串,现在相似,构造DbContextOptions<T> 代码如下: public SContext(Maste ...
- SQL Server、MySQL主从搭建,EF Core读写分离代码实现
一.SQL Server的主从复制搭建 1.1.SQL Server主从复制结构图 SQL Server的主从通过发布订阅来实现 1.2.基于SQL Server2016实现主从 新建一个主库&quo ...
随机推荐
- 不带parent指针的successor求解
问题: 请设计一个算法,寻找二叉树中指定结点的下一个结点(即中序遍历的后继).给定树的根结点指针TreeNode* root和结点的值int p,请返回值为p的结点的后继结点的值.保证结点的值大于等于 ...
- Java体系学习书籍推荐
以下所有资料均可在:Java知识分享网下载. 大家有推荐的书可以在下方留言! 开源代码GitHub仓库总结 计算机基础 计算机科学导论 --(如果不是计算机科班的,应先看看计算机基础,了解一些基本概 ...
- 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之六 || API项目整体搭建 6.1 仓储模式
前言 1.@LearningCoding 小伙伴关于用Sqlsugar在mysql数据库上的研究成果: sqlsugarcore支持mysql等数据库,在DbContext里面只需要设置dbtype为 ...
- ng跳转映射,被阿里云的云盾拦截,提示备案问题分析
在一个云项目调试过程中,ng映射到云时,发现被云盾拦截,提示备案. 1.客户提供的二级域名已经在华为云备案,映射的主机部署在阿里云. 2.ng映射域名时,出现备案提醒,f12调试发现跳转时,被拦截了. ...
- golang中Context的使用场景
golang中Context的使用场景 context在Go1.7之后就进入标准库中了.它主要的用处如果用一句话来说,是在于控制goroutine的生命周期.当一个计算任务被goroutine承接了之 ...
- Java分布式锁,搞懂分布式锁实现看这篇文章就对了
随着微处理机技术的发展,人们只需花几百美元就能买到一个CPU芯片,这个芯片每秒钟执行的指令比80年代最大的大型机的处理机每秒钟所执行的指令还多.如果你愿意付出两倍的价钱,将得到同样的CPU,但它却以更 ...
- 知识小罐头07(tomcat8请求源码分析 下)
感觉最近想偷懒了,哎,强迫自己也要写点东西,偷懒可是会上瘾的,嘿嘿!一有写博客的想法要赶紧行动起来,养成良好的习惯. ok,继续上一篇所说的一些东西,上一篇说到Connector包装了那两个对象,最后 ...
- 前端神器-神级代码编辑软件Sublime Text下载、使用教程、插件推荐说明、全套快捷键
Sublime Text 是一个代码编辑器,也是HTML和散文先进的文本编辑器.Sublime Text是由程序员Jon Skinner于2008年1月份所开发出来,它最初被设计为一个具有丰富扩展功能 ...
- springcloud情操陶冶-初识springcloud
许久之前便听到了springcloud如雷贯耳的大名,但是不曾谋面,其主要应用于微服务的相关架构.笔者对微服务并不是很了解,但其既然比较出众,遂也稍微接触研究下 springcloud特性 sprin ...
- .net core入门-发布及部署_异常(处理程序“aspNetCore”在其模块列表中有一个错误模块“AspNetCoreModuleV2")处理
备注:本人使用开发工具:VS2017,.NET Core 2.2,其中VS2017原本自带2.1,我单独从官网下载了2.2的程序集安装包,但是没有下配套的运行环境,运行项目时出了一个问题. 以下是我在 ...