EntityFramework 6.x多个上下文迁移实现分布式事务
前言
自从项目上了.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()
{
//table
ToTable("FlightBookings"); //key
HasKey(k => k.FlightId); //property
Property(p => p.FilghtName).HasMaxLength();
Property(p => p.Number);
Property(p => p.TravellingDate);
}
}
public class ReservationMap : EntityTypeConfiguration<Reservation>
{
public ReservationMap()
{
//table
ToTable("Reservations"); //key
HasKey(k => k.BookingId); //property
Property(p => p.BookingId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(p => p.Name).HasMaxLength();
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多个上下文迁移实现分布式事务的更多相关文章
- Cookies 初识 Dotnetspider EF 6.x、EF Core实现dynamic动态查询和EF Core注入多个上下文实例池你知道有什么问题? EntityFramework Core 运行dotnet ef命令迁移背后本质是什么?(EF Core迁移原理)
Cookies 1.创建HttpCookies Cookie=new HttpCookies("CookieName");2.添加内容Cookie.Values.Add(&qu ...
- EntityFramework Code First便捷工具——数据迁移
使用EntityFramework Code First开发,数据迁移是一个不得不提的技术. 在我们的开发过程中,难免需要对模型进行改进,模型改进后,会导致实体集与数据库不一致,当然我们可以通过删除数 ...
- 将ESXI所有的端口组迁移到分布式交换机的步骤
1.如果是DELL服务器,一般有2-4个网口,那么所有的网口都把网线插到交换机上:2.DELL安装ESXI系统,根据不同的DELL硬件,要安装不同的ESXI版本.原则上越高版本,支持的硬件越多向下兼容 ...
- EntityFramework Core 运行dotnet ef命令迁移背后本质是什么?(EF Core迁移原理)
前言 终于踏出第一步探索EF Core原理和本质,过程虽然比较漫长且枯燥乏味还得反复论证,其中滋味自知,EF Core的强大想必不用我再过多废话,有时候我们是否思考过背后到底做了些什么,到底怎么实现的 ...
- EntityFrameWork简单操作 EF数据上下文对象操作数据增删改差及批处理
/// <summary> /// EF针对 留言数据库 的 数据上下文对象!!!! /// </summary> static LeaveWordBoradEntities ...
- EF多个上下文迁移
步骤: 1. Enable-Migrations 2. add-migration Initial -ConfigurationTypeName ModelOneDbConfig 3. update- ...
- Spring父子上下文(WebApplicationContext)(防止事务失效)
如果你使用了listener监听器来加载配置,一般在Struts+Spring+Hibernate的项目中都是使用listener监听器的.如下 <listener> <listen ...
- entityframework分布式事务中遇到的 “与基础事务管理器的通信失败”的解决方法
首先是ef的多数据库操作实现事务的方法 public int AddDifferenceDB(userinfo1 user1, userinfo user) { ; using (var test2D ...
- Vcenter一次性将服务器四个网卡从端口组迁移到分布式交换机的方法
如果你的服务器已经在清单列表里了,那么可以先从分布式交换机将这台服务器删除,然后再添加一次.这个时候的添加就可以选择四个网卡(包括端口组,包括管理端口组),一次性加入分布式交换机
随机推荐
- 斜率DP hdu 3507
Problem Description Zero has an old printer that doesn't work well sometimes. As it is antique, he s ...
- linux下权限问题思考
今天遇到一些关于linux的权限问题,文件的所有者,文件的所属组等问题,文件对于所有者所属组是非常敏感的,同一个脚本所属者所属组不同,得到执行的结果也是差很多的.
- Maven 项目 @Override must override a superclass method` 问题
问题 Maven 项目 @Override must override a superclass method` 原因 JDK 在1.5以上的版本,才支持@Override 注解 解决方法 (1)po ...
- jquery 的 each 方法中 return 的坑
jquery 的 each 方法中 return 的坑 Chapter 0 在项目中使用 jquery 的 each 方法时想在 each 的循环中返回一个布尔类型的值于是掉进一个坑中... Chap ...
- Linux io Model
socket阻塞与非阻塞,同步与异步 作者:huangguisu 1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调 ...
- python爬虫scrapy框架——人工识别知乎登录知乎倒立文字验证码和数字英文验证码
目前知乎使用了点击图中倒立文字的验证码: 用户需要点击图中倒立的文字才能登录. 这个给爬虫带来了一定难度,但并非无法解决,经过一天的耐心查询,终于可以人工识别验证码并达到登录成功状态,下文将和大家一一 ...
- nginx正向代理
通过把Nginx设置为正向代理,我们就可以在局域网中用运行着Nginx的主机作为正向代理服务器了.那什么是正向代理和反向代理呢?正向代理和反向代理-百度百科 正向代理:如果把局域网外的Internet ...
- Android学习记录:线程
在Java中,线程的建立方法如下. 新建一个类,接口Runnable,重载 run方法 import javax.swing.JTextField; public class test impleme ...
- jquery 函数大全
jquery函数大全转载 Attribute:$(”p”).addClass(css中定义的样式类型); 给某个元素添加样式$(”img”).attr({src:”test.jpg”,alt:”te ...
- 201521123107 《Java程序设计》第5周学习总结
第5周作业-继承.多态.抽象类与接口 1.本周学习总结 2.书面作业 1.代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.java文件能否编译通过?哪句会出现错误?试改 ...