Purpose and semantic of IMigrationMetadata interface in Entity Framework

The IMigrationMetadata Interface has the following responsibilities that I know of.

  1. Identify the migration via the ID property so that is can be recognized and included by commands such as Update-Database.
  2. Supply a snapshot of the model as it is after the migration is applied via the Target property. This is used to determine the changes that should be included in a new migration.

I am guessing that the Source property is often not implemented by the tooling as it is not required in the implementation of Add-Migration. That code probably just compares the model as it was at the end of the most recent, existing migration with a model generated from the code to determine the changes that need to be included in the new migration.

The Target property returns a model in EDMX format that has been both compressed using the GZipStream and encoded using Convert.ToBase64String. I wrote the following code to both decode and encode these values. You would probaly find this useful if you are going to be coding migrations manually.

using System;
using System.IO;
using System.IO.Compression;
using System.Text;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main()
        {
            var minimalModel = File.ReadAllText("Model1.edmx");

            var encodedMinimalModel = Encode(minimalModel);

            var decodedMinimalModel = Decode(encodedMinimalModel);
        }

        private static string Decode(string encodedText)
        {
            var compressedBytes = Convert.FromBase64String(encodedText);

            var decompressedBytes = Decompress(compressedBytes);

            return Encoding.UTF8.GetString(decompressedBytes);
        }

        private static string Encode(string plainText)
        {
            var bytes = Encoding.UTF8.GetBytes(plainText);

            var compressedBytes = Compress(bytes);

            return Convert.ToBase64String(compressedBytes);
        }

        public static byte[] Decompress(byte[] bytes)
        {
            using (var memorySteam = new MemoryStream(bytes))
            {
                using (var gzipStream = new GZipStream(memorySteam, CompressionMode.Decompress))
                {
                    return ToByteArray(gzipStream);
                }
            }
        }

        private static byte[] ToByteArray(Stream stream)
        {
            using (var resultMemoryStream = new MemoryStream())
            {
                stream.CopyTo(resultMemoryStream);

                return resultMemoryStream.ToArray();
            }
        }

        public static byte[] Compress(byte[] bytes)
        {
            using (var memoryStream = new MemoryStream())
            {
                using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress))
                {
                    gzipStream.Write(bytes,0, bytes.Length);
                }

                return memoryStream.ToArray();
            }
        }
    }
}

The compression probably explains your query as to why a non-human readable format was chosen. This content is repeated at least once (in the Target property) for each migration and can be large depending on the size of the model. The compression saves on space.

On that note, as far as I can see, it is really only the last migration that is required to return a true representation of the model after it has been applied. Only that migration is used by Add-Migration to calculate the changes required in the new migration. If you are dealing with a very large model and/or a very large number of migrations, removing that content could be advantageous. The remainder of this post covers my derivation of a minimal value for the Target property which can be used in all but the most recent migration.

The Target property must return a string object - an ArgumentNullException is thrown in a call to System.Convert.FromBase64String in System.Data.Entity.Migrations.DbMigrator.ApplyMigration when update-database is called if Target returns null.

Further, it must be a valid XML document. When I returned an empty string from Target I got an XmlException with the message "Root element is missing.".

From this point on, I used my code from above to encode the values.

I did not get very far with gradually building up the model starting with <root /> for example so I swapped over to discarding elements from an empty EDMX file that I generated by adding a new 'ADO.Net Entity Data Model' to my project and then choosing the 'Empty Model' option. This was the result.

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="3.0" xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx">
  <edmx:Runtime>
    <edmx:StorageModels>
      <Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl" Namespace="Model1.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005">
      </Schema>
    </edmx:StorageModels>
  </edmx:Runtime>
</edmx:Edmx>

When I encoded this using my code from above, this was the result.

H4sIAAAAAAAEAJVQy07DMBC8I/EP1t6xExASRA1VVTgWIYK4W/amtfCjeN2q/D12HsqJAxdLOzOe2Z3V+uIsO2MkE3wLNa+AoVdBG79v4ZT6mwdYP11frVC7S/OSH/Y5i++KOH/31BS2hUNKx0YIUgd0krgzKgYKfeIqOCF1ELdV9SjqWhQ5ZFfGRt/3k0/G4YDMWJdClHvcBY2WJiZz3WA+xv4vURBpC+xVOqSjVNjC4F3zkoTANtbIbNmh7YG9xXA2GmOefyih488ySd5926016NMi2ElveqT0Eb4wd5Lz7mHZVozrzoeJPy6biKWGCSh95+kXfT3Qv6UBAAA=

Be careful to ensure that you retain the real Target values for each of your migrations in source control in case you need to roll back to an earlier version. You could try applying the migration to a database and then using Visual Studio to generate an EDMX file. Another alternative would be to roll back the classes that form your model and then execute Add-Migration. Take the Target value from the newly created migration.