Purpose and semantic of IMigrationMetadata interfa

2019-03-27 13:47发布

I'm trying to find out what is the semantic of System.Data.Entity.Migrations.Infrastructure.IMigrationMetadata interface in the EF. I know that it's used to manage and apply DB migrations. But I can't find detailed information about it. To be specific I would like to know:

  1. What Source property is used for? Why it's always null when I generate migrations using tools?
  2. What Target property is used for? I see that tools is generating something Base64-looking and placed into resources. What is it? Why it's generated in such non-friendly format?
  3. Is it possible to develop migration manually without tools usage? I suppose it is not easy because of that Target property Base64-like value which should be generated somehow. Am I right?
  4. When this interface is actually used? At the moment I found out that migrations not implementing this interface can't be found automatically by migrator. Am I right? Is it the only purpose of the interface?

2条回答
不美不萌又怎样
2楼-- · 2019-03-27 14:04

You go to: EF6 repository on codeplex and you see:

public interface IMigrationMetadata
{
    /// <summary>
    ///     Gets the unique identifier for the migration.
    /// </summary>
    string Id { get; }

    /// <summary>
    ///     Gets the state of the model before this migration is run.
    /// </summary>
    string Source { get; }

    /// <summary>
    ///     Gets the state of the model after this migration is run.
    /// </summary>
    string Target { get; }
}

You can get the project and check references to see how this interface is being used. The base64 thing is your model. Again with the code you should be able to track how it is done.

查看更多
爷的心禁止访问
3楼-- · 2019-03-27 14:10

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.

查看更多
登录 后发表回答