前言

本来打算写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进行读写分离最佳实践方式,了解一下(一)?的更多相关文章

  1. EntityFramework Core进行读写分离最佳实践方式,了解一下(二)?

    前言 写过上一篇关于EF Core中读写分离最佳实践方式后,虽然在一定程度上改善了问题,但是在评论中有的指出更换到从数据库,那么接下来要进行插入此时又要切换到主数据库,同时有的指出是否可以进行底层无感 ...

  2. EF Core 实现读写分离的最佳方案

    前言 公司之前使用Ado.net和Dapper进行数据访问层的操作, 进行读写分离也比较简单, 只要使用对应的数据库连接字符串即可. 而最近要迁移到新系统中,新系统使用.net core和EF Cor ...

  3. EF core 实现读写分离解决方案

    我们公司2019年web开发已迁移至.NET core,目前有部分平台随着用户量增加,单一数据库部署已经无法满足我们的业务需求,一直在寻找EF CORE读写分离解决方案,目前在各大技术论坛上还没找到很 ...

  4. 探寻ASP.NET MVC鲜为人知的奥秘(3):寻找多语言的最佳实践方式

    如果你的网站需要被世界各地的人访问,访问者会使用各种不同的语言和文字书写习惯,那么创建一个支持多语言的网站就是十分必要的了,这一篇文章就讲述怎么快速合理的创建网站对多语言的支持.接下来通过一个实例来讲 ...

  5. .NET Core调用WCF的最佳实践

    现在.NET Core貌似很火,与其他.NET开发者交流不说上几句.NET Core都感觉自己落伍了一样.但是冷静背后我们要也看到.NET Core目前还有太多不足,别的不多说,与自家的服务框架WCF ...

  6. React 代码共享最佳实践方式

    任何一个项目发展到一定复杂性的时候,必然会面临逻辑复用的问题.在React中实现逻辑复用通常有以下几种方式:Mixin.高阶组件(HOC).修饰器(decorator).Render Props.Ho ...

  7. Fibonacci(斐波那契数列)的最佳实践方式(JavaScript)

    1)低级版本 var fibonacci = function(n) { if (n == 0 || n == 1) { return n; } else { return fibonacci(n - ...

  8. Entity Framework Core 实现读写分离

    在之前的版本中我们可用构造函数实现,其实现在的版本也一样,之前来构造连接字符串,现在相似,构造DbContextOptions<T> 代码如下: public SContext(Maste ...

  9. SQL Server、MySQL主从搭建,EF Core读写分离代码实现

    一.SQL Server的主从复制搭建 1.1.SQL Server主从复制结构图 SQL Server的主从通过发布订阅来实现 1.2.基于SQL Server2016实现主从 新建一个主库&quo ...

随机推荐

  1. Vue(day7)

    一.环境搭建 下面我们需要为后面要做的Vue项目搭建开发环境. 1.基本的运行环境 该项目使用node& vue在webpack环境下进行开发.首先安装基本的模块文件: npm install ...

  2. 常用Latex公式

    注意: 1 在博客中书写Latex公式时,需在公式两侧用$包括 2 大括号{ }在Latex有本身的含义,如果要输出为普通字符需要使用\{...\} 符号 公式 说明 $ \in $ \in 包含 $ ...

  3. 网卡也能虚拟化?网卡虚拟化技术 macvlan 详解

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 01 macv ...

  4. 利用 vue-cli 构建一个 Vue 项目

    一.项目初始构建 现在如果要构建一个 Vue 的项目,最方便的方式,莫过于使用官方的 vue-cli . 首先,咱们先来全局安装 vue-cli ,打开命令行工具,输入以下命令: $ npm inst ...

  5. 用ASP.NET Core 2.0 建立规范的 REST API -- DELETE, UPDATE, PATCH 和 Log

    本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314 ...

  6. Identity Server 4 - Hybrid Flow - 保护API资源

    这个系列文章介绍的是Identity Server 4 的 Hybrid Flow, 前两篇文章介绍了如何保护MVC客户端, 本文介绍如何保护API资源. 保护MVC客户端的文章: https://w ...

  7. 网络协议 15 - P2P 协议:小种子大学问

    [前五篇]系列文章传送门: 网络协议 10 - Socket 编程(上):实践是检验真理的唯一标准 网络协议 11 - Socket 编程(下):眼见为实耳听为虚 网络协议 12 - HTTP 协议: ...

  8. pycharm安装svn插件

    弄了svn的服务端和客户端,为了方便我pycharm的使用,我又在pycharm里进行了配置,要用到subversion 下载 https://sourceforge.net/projects/win ...

  9. SpringBoot整合Jsp和Thymeleaf (附工程)

    前言 本篇文章主要讲述SpringBoot整合Jsp以及SpringBoot整合Thymeleaf,实现一个简单的用户增删改查示例工程.事先说明,有三个项目,两个是单独整合的,一个是将它们整合在一起的 ...

  10. vue-cli3安装过程

    作为一个本来是java开发的搬运工,在公司前端人员缺乏的时候,就直接顶上来开发前台页面了(话说我已经很久很久没写java代码了(:′⌒`)) 好吧言归正传,刚开始弄前台就是vue,vue2还没弄熟,老 ...