Detailed explanation of multiple context migration in entityframework 6. X

Time:2021-3-30

preface

Since the project was launched on the. Net core platform and used the entityframework core, the 6. X version of the entityframework has never been used again. At present, the 6. X version of the entityframework is the most widely used one. Whether it is to find a job or to improve one’s own technology, it has its own benefits. At the same time, most of the time, in addition to work, there is still a small part of time to write the 6. X and entityframework Core books, so entityframework 6. X is equivalent to learning from scratch, entityframework 6. X has added many features, so I spent some time to read and sort them out. This section is a problem that I haven’t encountered before, so I spent a little time migrating multiple contexts to different databases and implementing distributed transactions. As a basic entry, it’s also a bit of accumulation for readers to learn. If there are any mistakes in this article, please correct me.

Modeling

Before we start the content description of entityframework 6. X, we are still in the old routine. First, we prepare the model. We make a basic model of booking flights, one is the flight entity, the other is the booking entity. Please see the following:

/// <summary>
 ///Flights
 /// </summary>
 public class FlightBooking
 {
  /// <summary>
  ///FlightsId
  /// </summary>
  public int FlightId { get; set; }

  /// <summary>
  ///Flights名称
  /// </summary>
  public string FilghtName { get; set; }

  /// <summary>
  ///Flights号
  /// </summary>
  public string Number { get; set; }

  /// <summary>
  ///Travel date
  /// </summary>
  public DateTime TravellingDate { get; set; }
 }
/// <summary>
 ///Booking
 /// </summary>
 public class Reservation
 {
  /// <summary>
  ///BookingId
  /// </summary>
  public int BookingId { get; set; }

  /// <summary>
  ///Booking人
  /// </summary>
  public string Name { get; set; }

  /// <summary>
  ///Booking日期
  /// </summary>
  public DateTime BookingDate { get; set; } = DateTime.Now;
 }

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

This class is used to maintain flight and reservation entities, and is used when creating reservation flights. In the 6.0 + version of the entityframework, code based configuration appears. For database initialization policy and other configurations, we need to create a configuration class to maintain it, instead of placing it in the dbcontext context derived class constructor as we used to. In this way, the context derived class looks much cleaner.


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

Next, we will configure two dbcontext derived classes, namely hoteldbcontext and flightdbcontext. The basic configuration information is modified by features, as follows:


[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);
  }
 }

The corresponding mapping configuration has been described many times, so we don’t need to talk nonsense and give it directly.


public class FlightBookingMap : EntityTypeConfiguration<FlightBooking>
 {
  public FlightBookingMap()
  {
   //table
   ToTable("FlightBookings");

   //key
   HasKey(k => k.FlightId);

   //property
   Property(p => p.FilghtName).HasMaxLength(50);
   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(20);
   Property(p => p.BookingDate);
  }
 }

The above two contexts will be migrated to different databases, so the connection strings are of course two.


<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>

OK, the model and context have been built. Next, let’s move on to migration. Please look down.

Multiple context migration

There is nothing to say about a context migration. In most scenarios, it seems that there is only one context in an application, because there is only one database behind the scenes. We can get it by hand. How can we operate multiple context migrations in response to different database migrations? If you are very familiar with the migration command, then take it as a review. If not, you can use it as a basic reference. It’s a bit wordy. Let’s go to the text. There are only three steps to migrate the model to the database and persist it.

Migrating multiple contexts to different folder directories

Enable migrations command

Add migration command

Update database command

When there is only one context in a unified application, we only need to enable migrations. However, if there are multiple contexts and the context is not specified explicitly, migration will report an error. First, we change the project to the project in which the context is located in the nuget console.

Next, run enable migrations to initialize the migration directory, and obvious migration exceptions will appear.

Since there are multiple contexts, we need to specify which context to migrate. You can specify the context by adding – contexttypename after the command, and continue to specify the migration directory by using – migrationsdirectory. Finally, there are the following commands (do not know which commands are available, add a [-] bar after each command and press the tab key to display the command you want).

Enable-Migrations -ContextTypeName FlightDbContext -MigrationsDirectory:FlightMigrations

Next, we use the add migration command to build a base frame for the pending model changes. That is to say, we change the model after the last migration to build a base frame for the next migration. At this time, the generated model state is the pending state or the pending state. We need to migrate the configuration class in the above generated flightmigrations directory, so at this time, specify – configurationtypename after the add migration command, and then specify the first base frame name through – name.

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

perhaps

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

Finally, you only need to persist to the database to generate tables through update database.

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

Similarly, we use the above three steps to migrate hoteldbcontext. Finally, we can clearly see that each context is migrated in a different directory, as follows:

There is nothing wrong with the above migration. If each context is migrated separately to generate a folder, have we ever thought of migrating multiple contexts to the same directory folder and distinguishing them? When we have only one context, the folder created for us is migrations by default, and we will generate different context migration configurations under the migrations folder.

Migrating multiple contexts to the same folder directory

In fact, this is also very simple. We can directly specify a folder generation context after – migrationdirectory, for example, C:: (a) dbcontext. The entity framework also does this. Let’s take a look.

Enable-Migrations -ContextTypeName FlightDbContext -MigrationsDirectory Migrations\FlightDbContext

Enable-Migrations -ContextTypeName HotelDbContext -MigrationsDirectory Migrations\HotelDbContext

The other two steps run in the same way as migration, and eventually we will see the desired results.

Through the above migration, flightdb and reservationdb databases will be generated, corresponding to flightbookings and reserves tables. Well, that’s the end of the two ways of migrating multiple contexts. Let’s continue with the topic in this section.

Distributed transaction

Sometimes we need to manage transactions across databases. For example, there is a scenario where there are two databases db1 and DB2, and tb1 is in db1 and TB2 is in DB2. At the same time, tb1 and TB2 are associated. In the flight and reservation model we created above, we need to insert flight data and reservation data into different databases at the same time. At this time, transaction consistency is required, so in order to deal with the problem In. Net 2.0, in System.Transaction The transactionscope class is provided under the namespace. This class provides a simple way for a code block to participate in a transaction without having to interact with the transaction itself. It is strongly recommended that you create a transaction scope object in a transaction scope block.

When transactionscope is instantiated, the transaction manager needs to determine which transaction to participate in. Once determined, the instance will always participate in the transaction. When creating a transactionscope object, we need to pass the transactionscopeoption enumeration with the following values:

  • Required: the instance must require a transaction. If the transaction already exists, the existing transaction will be used, otherwise a new transaction will be created.
  • Requiresnew: always create a new transaction for the instance.
  • Supply: when an instance is created, other existing transactions are suppressed because all operations in the instance are completed without other existing transactions.

Next, we use the second method in the above enumeration to realize flight reservation. The simple logic is as follows:

public class MakeReservation
 {

  FlightDBContext flight;

  HotelDBContext hotel;

  public MakeReservation()
  {
   flight = new FlightDBContext();
   hotel = new HotelDBContext();
  }

  //Transaction processing method
  public bool ReservTrip(TripReservation trip)
  {
   bool reserved = false;

   //Binding transaction scope
   using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew))
   {
    try
    {
     //Flight information
     flight.FlightBookings.Add(trip.Filght);
     flight.SaveChanges();

     //Appointment information
     hotel.Reservations.Add(trip.Hotel);
     hotel.SaveChanges();

     reserved = true;

     //Complete transaction and commit
     scope.Complete();
    }
    catch (Exception ex)
    {
     throw ex;
    }
   }
   return reserved;
  }
 }

The above reservtrip method accepts the tripreservation object. This method defines transactionscope, binds create operations for flight and hotel in the context of transaction, and writes the code into try catch block. If the saveChanges method of the two entities is executed successfully, the transaction will be completed, otherwise the transaction will be rolled back. Next, make the controller call.


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");
  }
 }

We add the flight reservation view:

@model EntityFrameworkTransactionScope.Data.Entity.TripReservation

@{
 ViewBag.Title = "Create";
}

<h2>Travel</h2>
@using(Html.BeginForm()){


<table>
 <tr>
  <td>
   <table>
    <tr>
     <td colspan="2">
      Flight information
     </td>
    </tr>
    <tr>
     <td>
      Flight ID:
     </td>
     <td>
      @Html.EditorFor(m => m.Filght.FlightId)
     </td>
    </tr>
    <tr>
     <td>
      Flight Name:
     </td>
     <td>
      @Html.EditorFor(m => m.Filght.FilghtName)
     </td>
    </tr>
    <tr>
     <td>
      flight number:
     </td>
     <td>
      @Html.EditorFor(m => m.Filght.Number)
     </td>
    </tr>
   </table>
  </td>
  <td>
   <table>
    <tr>
     <td colspan="2">
      Appointment information
     </td>
    </tr>
    <tr>
     <td>
      Appointment ID:
     </td>
     <td>
      @Html.EditorFor(m => m.Hotel.BookingId)
     </td>
    </tr>
    <tr>
     <td>
      Customer name
     </td>
     <td>
      @Html.EditorFor(m => m.Hotel.Name)
     </td>
    </tr>

   </table>
  </td>
 </tr>
 <tr>
  <td colspan="2">
   < input type = submit "value = submit appointment / >
  </td>
 </tr>
</table>

}

The view display UI is as follows:

To run an application and check transactions, we need to use the Distributed Transaction Coordinator (DTC) service. The service coordinates transactions that update two or more protected resources, such as databases, message queues, file systems, etc. First of all, we need to make sure whether the DTC has been turned on, check it in the service and enable it.

Next, open the DTC setting, please follow the steps below or run it directly【 dcomcnfg.exe 】Open component services in one step.

  • Open the control panel
  • Find management tools
  • Component services found

Next, we fill in the relevant information to make a flight reservation.

As shown above, the reservation is successful. Let’s see if the data in the two databases are inserted correctly.

In the DTC service, if each submission is not aborted, the number of submissions will increase by 1. When we configure the reservation model, we do not set the primary key as the identification column, so let’s look at the data in the table when we repeat the primary key. We submit three times without repeating the primary key of the appointment. In the fourth time, the primary key input is the primary key of the third time. At this time, the results are as follows:

If we verify the data in the leflightbookings and reserves tables, the newly added records will not be displayed in them. This means that transactionscope has managed transactions by bundling connections with flight and hotel databases in a single scope, and monitored committed and aborted transactions.

summary

As we are using the Entity Framework as the conceptual data access layer, in the ASP.NET As seen in MVC applications, it is always recommended to use transactionscope to manage transactions when performing multiple database operations to store related data.