This issue has been resolved.
For versions 6.6.3 or newer of Connector/Net, this workaround is not required.

Our team recently came across a Chinese post reporting an issue when using Code First with MySQL. You get the following exception while trying to create the database.

MySql.Data.MySqlClient.MySqlException: You have an error in
your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near 'NOT
NULL,
        `ProductVersion` mediumtext NOT NULL);

ALTER TABLE `__MigrationH' at line 5

The Problem

If you look at the full SQL that it is trying to run, the problem becomes clearer.

CREATE TABLE `__MigrationHistory` (
    `MigrationId` mediumtext NOT NULL,
    `Model` varbinary NOT NULL,
    `ProductVersion` mediumtext NOT NULL);

ALTER TABLE `__MigrationHistory`
ADD PRIMARY KEY (MigrationId);

In MySQL, varbinary types must specify a max length. That’s not the only problem though; a mediumtext primary key also must specify a key length. Interestingly, if you look at your database after recieving this exception, all of your tables are created and, if you try to run your app again everything appears to work. So what’s the problem? The problem is that there is no __MigrationHistory table. This table is essential for the Database.CompatibleWithModel method to work properly which, in turn, is used by the CreateDatabaseIfNotExists and DropCreateDatabaseIfModelChanges database initializers.

A Workaround

Until the Connector/Net provider is updated to properly handle the __MigrationHistory table, we’ll need to create it ourselves fixing the two problems mentioned above. I’ve created a database initializer to do this for you modeled after the behavior of the CreateDatabaseIfNotExists initializer. Most of the code here can also be used to create one that mirrors DropCreateDatabaseIfModelChanges too. Here it is.

class CreateMySqlDatabaseIfNotExists<TContext>
    : IDatabaseInitializer<TContext>
        where TContext : DbContext
{
    public void InitializeDatabase(TContext context)
    {
        if (context.Database.Exists())
        {
            if (!context.Database.CompatibleWithModel(false))
            {
                throw new InvalidOperationException(
                    "The model has changed!");
            }
        }
        else
        {
            CreateMySqlDatabase(context);
        }
    }

    private void CreateMySqlDatabase(TContext context)
    {
        try
        {
            // Create as much of the database as we can
            context.Database.Create();

            // No exception? Don't need a workaround
            return;
        }
        catch (MySqlException ex)
        {
            // Ignore the parse exception
            if (ex.Number != 1064)
            {
                throw;
            }
        }

        // Manually create the metadata table
        using (var connection = ((MySqlConnection)context
            .Database.Connection).Clone())
        using (var command = connection.CreateCommand())
        {
            command.CommandText =
@"
CREATE TABLE __MigrationHistory (
    MigrationId mediumtext NOT NULL,
    Model mediumblob NOT NULL,
    ProductVersion mediumtext NOT NULL);

ALTER TABLE __MigrationHistory
ADD PRIMARY KEY (MigrationId(255));

INSERT INTO __MigrationHistory (
    MigrationId,
    Model,
    ProductVersion)
VALUES (
    'InitialCreate',
    @Model,
    @ProductVersion);
";
            command.Parameters.AddWithValue(
                "@Model",
                GetModel(context));
            command.Parameters.AddWithValue(
                "@ProductVersion",
                GetProductVersion());

            connection.Open();
            command.ExecuteNonQuery();
        }
    }

    private byte[] GetModel(TContext context)
    {
        using (var memoryStream = new MemoryStream())
        {
            using (var gzipStream = new GZipStream(
                memoryStream,
                CompressionMode.Compress))
            using (var xmlWriter = XmlWriter.Create(
                gzipStream,
                new XmlWriterSettings { Indent = true }))
            {
                EdmxWriter.WriteEdmx(context, xmlWriter);
            }

            return memoryStream.ToArray();
        }
    }

    private string GetProductVersion()
    {
        return typeof(DbContext).Assembly
            .GetCustomAttributes(false)
            .OfType<AssemblyInformationalVersionAttribute>()
            .Single()
            .InformationalVersion;
    }
}

There you have it. We basically let the Database.Create call do as much work as it can, then take over when it fails to create the __MigrationHistory table.

You can use the new initializer by calling Database.SetInitializer. One of the best places to do this is in your context’s static constructor.

class MyContext : DbContext
{
    static MyContext()
    {
        Database.SetInitializer(
            new CreateMySqlDatabaseIfNotExists<MyContext>();
    }

    public MyContext()
        : base("Name=LocalMySqlServer")
    {
    }

    // Add DbSet properties here
}

Alternatively, you can set it in your App/Web.config.

<entityFramework>
  <contexts>
    <context type="MyNamespace.MyContext, MyAssembly">
      <databaseInitializer type="MyNamespace.
CreateMySqlDatabaseIfNotExists`1[[MyNamespace.MyContext,
MyAssembly]], MyAssembly" />
    </context>
  </contexts>
</entityFramework>

The Fix

Like any good open source software user, I’ve filed two bugs with the Connecter/Net team. You can check the status to see what progress has been made towards an actual fix for the problem.

  • Bug #65289 - Cannot create an entity with a key of type string
  • Bug #65290 - Cannot create an entity with a property of type byte[]