Home .NET Entity Framework Code First in teamwork

Entity Framework Code First in teamwork

by admin

From the translator : A great article on understanding the migration mechanism in Entity Framework 6 and how to solve migration conflicts in teamwork.Original article : Code First Migrationsin Team Environments
This article assumes that you are familiar with Entity Framework and the basics of working with it. Otherwise, you should first read Code First Migrations , before proceeding.

Pour a cup of coffee, you need to read the whole article

In teamwork, the problems are mostly related to merging migrations created by two developers in their local databases. While the steps to solve these problems are fairly straightforward, they require a clear understanding of how migrations work. Please take the time to read the entire article carefully.

Some general principles

Before we delve into how to manage the merging of migrations created by multiple developers, here are some general principles.

Each team member must have a local database for development

The migration mechanism uses the table __MigrationsHistory to store a list of migrations that have been applied to the database. If you have more than one developer on your team generating migrations, when trying to work with the same database (and hence the same table __MigrationsHistory ) the migration mechanism may experience difficulties.
Of course, if you have team members who don’t create migrations, you won’t have any problems working with the central database for development.

Avoid automatic migrations

The bottom line is that automatic migrations initially look good in teamwork, but in reality they just don’t work. If you want to know the reason, keep reading, otherwise you can skip to the next chapter.
Automatic migrations allow the database schema to be updated according to the current model without the need to create migration code files (code-based migration).
Automatic migrations will only work well in teamwork if you have ever used them and never created any code-based migrations. The problem is that automatic migrations are limited and cannot handle a number of operations – renaming a property/column, moving data from one table to another, etc. To handle such scenarios, you will end up creating code-based migrations (and editing the generated code) which leads to mixing up the changes that are handled by automatic migrations. This makes it almost impossible to merge the changes of two developers.
From the translator: there are 2 screencasts in the original article, I recommend you read them

Principles of the migration mechanism

The key to the successful use of migrations in a team is a basic understanding of how the migration engine tracks and uses model information to detect changes.

First migration

When you add the first migration to your project, you run something like Add-Migration First in the Package Manager Console. Below are the steps this command does.
Entity Framework Code First in teamwork
Based on the code, the current model (1) is calculated. Then the necessary database objects are calculated with model differ (2) – since this is the first migration, model differ uses an empty model for comparison. The necessary changes are passed to the code generator to create the necessary migration code (3), which is then added to the Visual Studio solution (4).
In addition to the migration code, which is stored in the main file, the migration engine also creates additional code-behind files. These are metadata files that are used by the migration engine and you should not change them. One of these files is a resource file (.resx) which contains a snapshot of the model at the time the migration is created. In the next section you will see how it is used.
At this point you can execute Update-Database to apply the changes to the database, and then start implementing the rest of your application.

Subsequent migrations

Let’s make some changes to the model – in our example, we will add the property Url into the class Blog Then you need to execute the command Add-Migration AddUrl to create a migration to apply the appropriate changes to the database. The steps that this command performs are shown below.
Entity Framework Code First in teamwork
Just like last time, the current model is calculated from the code (1). However, this time there are existing migrations, and the previous model is extracted from the last migration (2). The two models are compared to find the necessary changes in the database (3), and then the process is completed as before.
The same process is used for all the following migrations that are added to the project.

Why bother with pictures of the model?

You might wonder why EF uses model snapshots for comparison – why not just look at the database.
There are a number of reasons why EF retains model states :

  • This allows your database to be different from the EF model. These changes can be made directly in the database, or you can change the scaffolded code in your migrations to make the change. Here are some examples of this in practice :
  • You want to add Inserted and Updated columns to one or more tables, but you don’t want them included in the EF model. If the migration engine looked at the database, it would constantly try to remove these columns every time you generated migration code. Using a model snapshot, EF would only detect the changes you want in the model.
  • You want to change the body of a stored procedure used to update some debugging information. If the migration engine looked at the stored procedure in the database, it would keep trying to reset it back to a definition. With a model snapshot, EF will generate code to change the stored procedure when you change the procedure in the EF model
  • The same principles apply when adding additional indexes, including additional tables in the database, mapping EF to DB View, etc.

  • An EF model contains more than just a database image. With a model, the migration engine lets you see information about your model’s properties and classes and how they map to columns and tables. This information allows the migration engine to be smarter when automatically generating code. For example, if you rename a column, the migration mapping will detect the renaming by seeing that it is the same property. This would be impossible to do if we were only looking at the database
  • What raises questions in teamwork

    The process discussed in the previous section works fine if you are the only developer working on the application. It also works well as a team if you are the only person making changes to the model. In that case, you can make changes to the model, generate migrations, and send them to the version control system. Other developers can receive those changes and run Update-Database to update the schema.
    Problems start to occur when you have multiple developers making changes to the EF model and sending them to the version control system. What EF lacks is a first class way to merge local migrations with migrations sent to version control by other developers after the last sync.

    Example of a conflict merger

    First, let’s look at a specific example of merging such a conflict. We will continue with the example we discussed earlier. As a starting point, let’s assume that the changes from the previous section have been checked out by a real developer. We will keep track of two developers who make changes to the code.
    We will track the EF model and migrations through a series of changes. Both developers are synchronized through the repository in the version control system, as shown in the following figure.
    Entity Framework Code First in teamwork
    Developer #1 and developer #2 make some changes to the EF model in the local codebase. Developer #1 adds the property Rating into the class Blog , creates migration AddRating to apply the changes to the database. Developer #2 adds the property Readers to the class Blog , creates migration AddReaders Both developers run Update-Database to apply the changes to their local databases, and then continue developing the application.
    Note : The migrations start with a timestamp, so our figure shows that the AddReaders migration from developer #2 comes after the AddRatingmigration from developer #1. In terms of teamwork, we don’t care in what order these changes were created, we’ll look at the process of merging them together in the next section.
    Entity Framework Code First in teamwork
    Developer #1 is lucky today, because he will be the first to submit his changes to the version control system. Since no one else has submitted changes to the repository, he can just submit his changes without doing any merge.
    Entity Framework Code First in teamwork
    Now it’s time for developer #2 to send in the changes. He is not so lucky. Since someone published the changes after the last sync, the developer has to pick them up and do the merge. The version control system will most likely be able to automatically merge the changes at the code level, since they are very simple. The state of the developer’s local codebase #2 after synchronization is depicted in the following figure.
    Entity Framework Code First in teamwork
    At this point developer #2 can run Update-Database which allows a new AddRating migration (which has not been applied to developer database #2), and apply it. The column is now Rating has been added to the table Blogs , and the database is synchronized with the model.
    There are several problems, such as :

    1. Although the operation Update-Database will apply the migrations AddRating , it will also show a warning :
      Unable to update database to match the current model because there are pending changes and automatic migration is disabled…
      The problem is that the model snapshot is stored in the last migration (AddReader), which skips the property Rating in the class Blog (since it was not part of the model when the migration was created).
      Code First detects that the model in the past migration does not match the current model and displays a warning.
    2. Running the application will result in InvalidOperationException «The model backing the ‘BloggingContext’ context has changed since the database was created. Consider using Code First Migrations to update the database…»
      Again, the problem is that the model snapshot is stored in the latest migration, not corresponding to the current model
    3. Finally, one would expect the launch of the Add-Migration would now generate an empty migration (since there are no changes to be applied to the database).But since the migrations compare the current model in one of the last migrations (which does not have the property Rating ) this will generate another AddColumn call to add a column Rating
      Of course, this migration will fail at Update-Database because the column Rating already exists.

    Resolving merger conflicts

    The good news is that it is not very hard to merge migrations manually if you understand how migration works. So if you missed the beginning of this article… sorry, you need to go back and read the first part of the article first!
    There are two options, the simplest is to create an empty migration that has the correct model snapshot. The second option is to update the snapshot in the last migration to have the correct model snapshot. This is a bit more complicated and this option cannot be used in every case. Its advantage is that it does not involve adding an additional migration.

    Option 1: Adding an empty "merge" migration

    In this option, we generate an empty migration solely to ensure that the last migration has the correct model snapshot stored in it.
    This feature can be used regardless of who generated the last migration. In the example we observed developer #2 generating the last migration. But the same steps can be used if developer #1 had generated the last migration. The steps also apply if there are multiple migrations.
    The following algorithm can be used from the point where there are changes that need to be synchronized with the version control system.

    1. Make sure that all model changes in your local codebase have been saved in the migration. This step ensures that you do not miss any important changes when it comes time to create a clean migration
    2. Synchronize code with version control system
    3. Run Update-Database to apply any new migrations made by other developers.
      Note : if you do not get any warnings while running Update-Database, then there were no new migrations from other developers and there is no need to perform additional merges.
    4. Run Add-Migration <pick_a_name> -ignorechanges (e.g, add-migration merge -ignorechanges ).This command creates a migration with all the metadata (including the snapshot of the current model), but will ignore any changes it finds when comparing the current model with the snapshot of the last migration (that is, you will get empty Up and Down methods).
    5. Continue development, or send changes to the version control system (after running unit tests of course).

    This is the state of developer model #2 after applying this approach.
    Entity Framework Code First in teamwork

    Option 2: Update the model snapshot of the last migration

    This option is very similar to option 1, but removes the extra empty migration.
    This approach is possible if the last migration exists only locally and has not yet been sent to the version control system (i.e. the last migration was created by the user performing the merge).Editing the metadata of migrations which have been applied by other developers, or even worse, applied to the combat database, can lead to unexpected side effects. In the process, we will roll back the last migration in our local database and reapply with updated metadata.
    As long as the last migration is local, there is no limit to the number or order of migrations.
    The same steps apply when there are multiple migrations from several different developers.
    The following algorithm can be used when there are changes that need to be synchronized with the version control system.

    1. Make sure that all model changes in your local codebase have been saved in the migration.
      This step ensures that you don’t miss any important changes when it comes time to create a clean migration.
    2. Synchronize the code with the version control system.
    3. Run Update-Database to apply any new migrations made by other developers.
      Note : If you do not get any warnings while running Update-Database then there were no new migrations from other developers and there is no need to perform additional merges.
    4. Run Update-Database-TargetMigration <second_last_migration> (in the example it would be Update-Database -TargetMigration AddRating ).
      This action rolls the database back to the state of the penultimate migration – in fact,
      where the last migration to the database was not applied.
      Note : This step is necessary to make it safe to edit the migration metadata because the metadata is also stored in the __MigrationsHistory table in the database. This is why you should only use this function if the last migration is only there locally. If you need to apply the last migration on other databases, you also need to roll it back and reapply the last migration to update the metadata.
    5. Run Add-Migration <full_name_including_timestamp_of_last_migration> (in the example it would be something like a migration add-on Add-Migration 201311062215252_AddReaders ).
      Note : You need to specify the label so that the migration engine understands that you want to change an existing migration, not create a new one. This will update the metadata for the last migration to match the current model. You will get the following warning when you complete the command, but this is exactly what you want. "Only the Designer Code for migration ‘201311062215252_AddReaders’ was re-scaffolded. To re-scaffold the entire migration, use the -Force parameter."
    6. Run Update-Database to reapply the latest migration with the updated metadata.
    7. Continue development, or send changes to the version control system (after running unit tests of course).

    This is the state of the local developer codebase #2 after applying this approach.
    Entity Framework Code First in teamwork

    Total

    There are some problems when using Code First migrations in a team. Nevertheless, a general understanding of how migrations work, and some simple approaches to resolving merge conflicts make it easy to overcome these problems.
    The fundamental problem is erroneous metadata stored in the last migration. This allows Code First to incorrectly determine that the current model and database schema do not match and generate incorrect code for the next migration. This situation can be corrected by generating an empty migration with the correct model, or by updating the metadata in the last migration.

    You may also like