前言

自从项目上了.NET Core平台用上了EntityFramework Core就再没碰过EntityFramework 6.x版本,目前而言EntityFramework 6.x是用的最多,无论是找工作而言还是提升自身技术而言皆自身收益,同时呢,大多数时间除了工作之外,还留有一小部分时间在写EntityFramework 6.x和EntityFramework Core的书籍,所以将EntityFramework 6.x相当于是从零学起,EntityFramework 6.x又添加了许多特性,所以花了一些时间去看并整理了下来,本节相当于是自己一直未碰到过的问题,于是花了一点时间在多个上下文迁移到不同数据库并实现分布式事务上,作为基础入口且同步于书籍,供阅读者学习也是我的点滴积累,文章如有错误,请指正。

模型建立

在开始EntityFramework 6.x内容叙述之前,我们还是老套路,首先准备模型,我们搞一个预约航班的基本模型,一个是航班实体,另外一个为预约实体,请看如下:

    /// <summary>/// 航班/// </summary>public class FlightBooking{/// <summary>/// 航班Id/// </summary>public int FlightId { get; set; }/// <summary>/// 航班名称/// </summary>public string FilghtName { get; set; }/// <summary>/// 航班号/// </summary>public string Number { get; set; }/// <summary>/// 出行日期/// </summary>public DateTime TravellingDate { get; set; }}

    /// <summary>/// 预订/// </summary>public class Reservation{/// <summary>/// 预订Id/// </summary>public int BookingId { get; set; }/// <summary>/// 预订人/// </summary>public string Name { get; set; }/// <summary>/// 预订日期/// </summary>public DateTime BookingDate { get; set; } = DateTime.Now;}

    public class TripReservation{public FlightBooking Filght { get; set; }public Reservation Hotel { get; set; }}

此类用于维护航班和预约的实体,在创建预约航班时使用。在EntityFramework 6.0+版本上出现了基于代码配置(Code-based Configuration),对于数据库初始化策略和其他等等配置,我们单独建立一个配置类来维护,而无需如我们以往一样放在DbContext上下文派生类构造函数中,这样一来上下文派生类看起来则洁净很多。

    public class HotelFlightConfiguration : DbConfiguration{public HotelFlightConfiguration(){           SetDatabaseInitializer(new DropCreateDatabaseIfModelChanges<HotelDBContext>());SetDatabaseInitializer(new DropCreateDatabaseIfModelChanges<FlightDBContext>());}}

接下来我们再来配置两个DbContext上下文派生类即HotelDbContext和FlightDbContext,并且基本配置信息利用特性来修饰,如下:

    [DbConfigurationType(typeof(HotelFlightConfiguration))]public class FlightDBContext : DbContext{public FlightDBContext() : base("name=flightConnection"){ }public DbSet<FlightBooking> FlightBookings { get; set; }protected override void OnModelCreating(DbModelBuilder modelBuilder){modelBuilder.Configurations.Add(new FlightBookingMap());base.OnModelCreating(modelBuilder);}}

    [DbConfigurationType(typeof(HotelFlightConfiguration))]public class HotelDBContext: DbContext{public HotelDBContext():base("name=reservationConnction"){ }public DbSet<Reservation> Reservations { get; set; }protected override void OnModelCreating(DbModelBuilder modelBuilder){modelBuilder.Configurations.Add(new ReservationMap());base.OnModelCreating(modelBuilder);}}

对应的映射配置已经叙述很多次了,我们不用废话,直接给出。

    public class FlightBookingMap : EntityTypeConfiguration<FlightBooking>{public FlightBookingMap(){//tableToTable("FlightBookings");//keyHasKey(k => k.FlightId);//propertyProperty(p => p.FilghtName).HasMaxLength(50);Property(p => p.Number);Property(p => p.TravellingDate);}}

    public class ReservationMap : EntityTypeConfiguration<Reservation>{public ReservationMap(){//tableToTable("Reservations");//keyHasKey(k => k.BookingId);//propertyProperty(p => p.BookingId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);Property(p => p.Name).HasMaxLength(20);Property(p => p.BookingDate);}}

如上两个上下文我们将迁移到不同数据库,所以连接字符串当然是两个啦。

<connectionStrings><add name="reservationConnction" connectionString="Data Source=WANGPENG;Initial Catalog=ReservationDb;Integrated Security=true" providerName="System.Data.SqlClient" /><add name="flightConnection" connectionString="Data Source=WANGPENG;Initial Catalog=FlightDb;Integrated Security=true" providerName="System.Data.SqlClient" /></connectionStrings>

好了,模型和上下文一切都已构建完毕,接下来进入到迁移,请往下看。

多个上下文迁移

一个上下文进行迁移已经没有什么可说的了,在大多数场景下,貌似都是一个应用程序中仅仅存在一个上下文,因为幕后对应的只有一个数据库,这个大家是手到擒来,而对于多个上下文迁移对应不同数据库迁移又怎么去操作呢?如果你非常熟悉迁移命令,那么就当做是回顾吧,如若不然,可以此作为基本参考,有点啰嗦了哈,我们进入正文。将模型迁移至数据库并持久化只需要如下三步。

多个上下文迁移至不同文件夹目录

  • Enable-Migrations命令

  • Add-Migration命令

  • Update-database命令

当统一应用程序只存在一个上下文时,我们只需要Enabel-Migrations即可,但是若存在多个上下文,若不明确指定上下文很显然会迁移报错,首先我们在NuGet控制台将项目更换到上下文所在项目中。

接下来运行Enable-Migrations初始化迁移目录,很明显会出现迁移异常。

由于存在多个上下文,所以我们需要明确指定迁移哪个上下文。通过在其命令后继续添加-ContextTypeName指定上下文,并继续利用-MigrtionsDirectory指定迁移目录,最后则是如下命令(不知道有哪些命令吗,在每个命令后添加一个【-】横杆并按下Tab键则出现你想要的命令)。

  • Enable-Migrations -ContextTypeName FlightDbContext -MigrationsDirectory:FlightMigrations

接下来利用Add-Migration命令对已挂起模型改变搭建基架,也就是说将上次迁移后我们对模型发生了更改,以此为下一次迁移搭建基架,此时生成的模型状态为挂起状态或者称作为待定状态。我们需要迁移上述生成FlightMigrations目录下的Configuration类,所以此时在Add-Migration命令后指定-ConfigurationTypeName,然后通过-Name指定第一次基架名称。

  • Add-Migration -ConfigurationTypeName EntityFrameworkTransactionScope.Data.FlightMigrations.Configuration -Name Initial

或者

  • Add-Migration -ConfigurationTypeName EntityFrameworkTransactionScope.Data.FlightMigrations.Configuration "Initial"

最后则只需要通过Update-database来持久化到数据库生成表了。

  • Update-Database -ConfigurationTypeName EntityFrameworkTransactionScope.Data.FlightMigrations.Configuration

同理我们对HotelDbContext利用上述三步命令来进行迁移,最后我们能够很清晰的看到,每个上下文迁移在不同目录,如下:

上述迁移也没任何毛病,将每个上下文单独迁移生成文件夹,那么我们是否有想过将多个上下文迁移到同一目录文件夹下且区分开来呢,在我们只有一个上下文时默认给我们创建的文件夹为Migrations,我们就在Migrtions文件夹下生成不同上下文迁移配置。

多个上下文迁移至相同文件夹目录

这个其实也很简单,我们在-MigrationDirectoty后面可以直接指定某个文件夹生成上下文,例如C:\A\DbContext,EntityFramework也做到了这点,下面我们来看看。

  • Enable-Migrations -ContextTypeName FlightDbContext -MigrationsDirectory Migrations\FlightDbContext
  • Enable-Migrations -ContextTypeName HotelDbContext -MigrationsDirectory Migrations\HotelDbContext

其余两步运行方式和迁移不同一样,最终我们会看到想要的结果。

通过上述迁移最终将生成FlightDb和ReservationDb两个数据库并对应FlightBookings和Reservations表。好了到此关于多个上下文迁移两种方式就结束了,我们继续本节的话题。

分布式事务

有时候我们需要跨数据库管理事务,例如有这样一个场景,有两个数据库db1和db2,而tb1在db1中,tb2在db2中,同时tb1和tb2是关联的,在上述中我们创建的航班和预订模型,我们需要同时插入航班数据和预约数据到不同数据库中,此时则要求事务一致性,所以为了处理这样的要求,在.NET 2.0,在System.Transaction命名空间下为我们提供了TransactionScope类。 此类提供了一种使代码块参与事务而不需要与事务本身交互的简单方式。强烈建议在using块中创建TransactionScope对象。

当TransactionScope被实例化时,事务管理器需要确定要参与哪个事务。一旦确定,该实例将一直参与到事务中。 在创建TransactionScope对象时,我们需要传递具有以下值的TransactionScopeOption枚举:

  • Required:实例必须需要事务,如果事务已存在,则使用已存在事务,否则将创建新事务。
  • RequiresNew:始终为实例创建一个新的事务。
  • Suppress:创建实例时,其他已存在事务将被抑制,因为该实例内的所有操作的完成而无需其他已存在事务。

接下来我们利用上述枚举中第二种方式来实现航班预约,简单逻辑如下:

    public class MakeReservation{FlightDBContext flight;HotelDBContext hotel;public MakeReservation(){flight = new FlightDBContext();hotel = new HotelDBContext();}//处理事务方法public bool ReservTrip(TripReservation trip){bool reserved = false;//绑定处理事务范围using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew)){try{//航班信息
                    flight.FlightBookings.Add(trip.Filght);flight.SaveChanges();//预约信息
                    hotel.Reservations.Add(trip.Hotel);hotel.SaveChanges();reserved = true;//完成事务并提交
                    scope.Complete();}catch (Exception ex){throw ex;}}return reserved;}}

上述ReservTrip方法接受TripReservation对象。 该方法定义了TransactionScope,并在事务的上下文中捆绑了用于Flight和Hotel的Create操作,并将代码写入try-catch块中。 如果两个实体的SaveChanges方法成功执行,那么事务将被完成,否则回滚。接下来进行控制器调用。

    public class TripController : Controller{MakeReservation reserv;public TripController(){reserv = new MakeReservation();}public ActionResult Index(){return View();}public ActionResult Create(){return View(new TripReservation());}[HttpPost]public ActionResult Create(TripReservation tripinfo){try{tripinfo.Filght.TravellingDate = DateTime.Now;tripinfo.Hotel.BookingDate = DateTime.Now;var res = reserv.ReservTrip(tripinfo);if (!res){return View("Error");}}catch (Exception){return View("Error");}return View("Success");}}

我们添加航班预约视图:

@model EntityFrameworkTransactionScope.Data.Entity.TripReservation@{ViewBag.Title = "Create";
}<h2 class="text-center">旅游出行</h2>
@using(Html.BeginForm()){<table class="table table-condensed table-striped table-bordered"><tr><td><table class="table table-condensed table-striped table-bordered"><tr><td colspan="2" class="text-center">航班信息</td></tr><tr><td>航班Id:</td><td>@Html.EditorFor(m => m.Filght.FlightId)</td></tr><tr><td>航班名称:</td><td>@Html.EditorFor(m => m.Filght.FilghtName)</td></tr><tr><td>航班号:</td><td>@Html.EditorFor(m => m.Filght.Number)</td></tr></table></td><td><table class="table table-condensed table-striped table-bordered"><tr><td colspan="2" class="text-center">预约信息</td></tr><tr><td>预约Id:</td><td>@Html.EditorFor(m => m.Hotel.BookingId)</td></tr><tr><td>客户名称</td><td>@Html.EditorFor(m => m.Hotel.Name)</td></tr></table></td></tr><tr><td colspan="2" class="text-center"><input type="submit" value="提交预约" /></td></tr>
</table>}

视图展示UI如下:

要运行应用程序并检查事务,我们需要使用分布式事务处理协调器(DTC)服务。 该服务协调更新两个或多个事务受保护资源的事务,例如数据库,消息队列,文件系统等。首先我们需要确保DTC是否已经开启,在服务中进行查看并启用。

接下来打开DTC设置,请按照下列步骤操作或者直接运行【dcomcnfg.exe】一步到位打开组件服务。

  • 打开控制面板
  • 找到管理工具
  • 找到组件服务

接下来我们填写相关信息来进行航班预约。

如上显示已经预约成功,我们看看两个数据库中的数据是否正确插入。

在DTC服务中,若每次提交未中止则提交数量将增加1,在我们对预约模型进行配置时,我们将主键未设置为标识列,所以在我们对主键重复的情况下再来看看表中数据。我们提交三次而预约主键不重复,在第四次时主键输入为第三次的主键,此时看看结果如下:

我们验证leFlightBookings和Reservations表中的数据,则新添加的记录将不会显示在其中。 这意味着TransactionScope已经通过在单个范围中将连接与Flight和Hotel数据库捆绑在一起来管理Transaction,并监控了Committed和Aborted Transaction。

总结

正如我们在使用EntityFramework实体框架作为概念上的数据访问层时,在ASP.NET MVC应用程序中看到的那样,在执行多个数据库操作以存储与其相关的相关数据时,始终建议使用TransactionScope来管理事务。

EntityFramework 6.x多个上下文迁移实现分布式事务相关推荐

  1. mongoDB 从单节点迁移到分布式集群 遇到的问题

    mongoDB 从单节点迁移到分布式集群 遇到的问题: 1.linux 环境下limit 的设置问题: 错误:     $ ps -ef|grep mongod     -bash: fork: re ...

  2. Vcenter一次性将服务器四个网卡从端口组迁移到分布式交换机的方法

    如果你的服务器已经在清单列表里了,那么可以先从分布式交换机将这台服务器删除,然后再添加一次.这个时候的添加就可以选择四个网卡(包括端口组,包括管理端口组),一次性加入分布式交换机

  3. 2018年Spring Cloud中国社区技术沙龙-成都站

    Spring Cloud中国社区(http://springcloud.cn) 是国内基于Spring Cloud微服务体系创建的非盈利技术社区,发展至今刚好两周岁.自2016年10月份创建以来,在北 ...

  4. Java面试所需的知识

    目录 1. 计算机网络 (1)网络7层架构 (2)TCP/IP原理 (3)HTTP原理 (4)加密算法 2. 数据结构 3. 算法 (1) Java算法 (2)海量数据处理 4. 操作系统 5. My ...

  5. Sharding-JDBC 原理以及相关入门

    基于关系型数据库的水平扩展方案有很多开源的解决方案,但成熟稳定的产品凤毛麟角.当当自研的数据库中间层 Sharding-JDBC 在公司内部已广泛使用,并在开源社区推广且初见成果.目前的 Shardi ...

  6. Linq to Sql : 三种事务处理方式

    Linq to SQL支持三种事务处理模型:显式本地事务.显式可分发事务.隐式事务.(from  MSDN: 事务 (LINQ to SQL)).MSDN中描述得相对比较粗狂,下面就结合实例来对此进行 ...

  7. spring5企业级开发实战 pdf_终于总结出Spring全家桶+微服务设计模式+Netty+MySQL调优PDF...

    Spring源码深度解析(2020年1月第二版) Spring是一个源码开放的轻量级Java开发框架,旨在解决业务逻辑层和其他各层的松耦合问题! 自从2003年推出以来,Spring 逐渐发展成为事实 ...

  8. idou老师教你学istio:监控能力介绍

    经过了一年多的开发和测试,Istio于北京时间7月31日发布了1.0版本,并且宣布1.0版本已经可以成熟的应用于生产环境.对于istio的各项主要功能,之前的文章已经介绍的非常详细,并且还会有更多的文 ...

  9. Jaeger入门简介

    Jaeger主要用于监视和诊断基于微服务的分布式系统,包括: 分布式上下文传播.分布式事务监控.根本原因分析.服务依赖性分析.性能/延迟优化.用于程序间(服务间)轨迹追踪.行为追踪.调用关系追踪. L ...

最新文章

  1. 元旦福利 | Python、机器学习、TensorFlow 图书送一波
  2. Java集合之ArrayList源码解析
  3. SQL Server 2008 高可用性视频(四)-- 故障转移群集
  4. C#操作 MongoDB【原创】
  5. Struts2的声明式异常处理
  6. 升级到新SQL Server版本
  7. 使用Xmanger登陆aix系统桌面时报桌面服务DT未启动问题
  8. 再说变体结构 - 回复 彬 的问题
  9. 计算机桌面桌面设置动态视频,电脑怎么设置动态桌面?电脑设置动态视频桌面图文教程...
  10. [UE4]大型户外场景制作教程
  11. Linux格式化逻辑卷的命令,Linux LVM逻辑卷管理
  12. 微信小程序自定义函数返回值
  13. meta20 无法安装 google play_【黑科技】安卓手机安装Google Play
  14. java实现文件上传功能详解
  15. 《神经科学:探索脑》学习笔记(第19章 脑的节律)
  16. 07【Listener、Ajax、Json】
  17. 日本旅馆业、民宿分类及管理规定
  18. 编译 文件“libboost_log-vc120-mt-sgd-1_59.lib”
  19. 解决在英文版MSSQL中插入中文乱码的问题
  20. 西部数据硬盘支持linux,西数ZoneFS系统纳入Linux,改善SMR硬盘可靠性问题

热门文章

  1. 如何使用mysql建立项目_【dbForge Studio for MySQL入门教程】如何在项目中使用数据库对象和如何使用项目构建配置...
  2. cli命令行配置路由器_2.3.3 使用CLI执行基本路由器配置
  3. 软件测试薪资标准新鲜出炉,你达标了吗?
  4. 《Java并发编程的艺术》:第2章 Java并发机制的底层实现原理
  5. java 解锁关闭文件占用_程序员:Java文件锁定、解锁和其它NIO操作
  6. fread读取同一个文件得到缓冲区大小不同_c++日志文件操作
  7. 存储过程返回结果集_PostgreSQL函数返回结果集
  8. 基于深度卷积神经网络的玉米病害识别
  9. arm linux 进程页表,arm-linux内存页表创建
  10. 曲面化原理创新设计_曲面丝印机会给我们带来什么样的美丽