MEYWD MEYWD - 3 months ago 15
C# Question

EF 6 CodeFirst Add-Migration scaffoldes and generates changes that doesn't exist

I have been using EF Migrations for a while in my current project and all was working great, that is until today, the situation is as follows:


  1. I made a small change of adding a string property

  2. I called an API method and got an error that there are changes in the model

  3. I ran the command "Add-Migration MigrationXYZ"

  4. A new migration is created with extra changes that didn't happen



I ran the "Add-Migration MigrationXYZ -Force" to make sure its not a one thing issue, I dropped the DB, restarted VS(2015) but all the same

Another issue is that even if I apply the migration as done by the scaffolder, an error still returns saying "Unable to update database to match the current model because there are pending changes..."

After looking at these changes, they all but one are about having a string property with the [Required] attribute and the scaffolder need to make it nullable, below is a sample.

public partial class MigrationXYZ: DbMigration
{
public override void Up()
{
AddColumn("dbo.Foos", "NewProperty", c => c.String());//<-- Expected Change
AlterColumn("dbo.Bars", "Name", c => c.String());//<-- Unexpected Change
}

public override void Down()
{
AlterColumn("dbo.Bars", "Name", c => c.String(nullable: false));//<-- Unexpected Change
DropColumn("dbo.Foos", "NewProperty");//<-- Expected Change
}
}

public class Bar
{
//This was not touched in ages, some even before adding the first migration
[Required]
public string Name { get; set; }
}


And now I am stuck and don't know how to fix this...Corruption in the Migration state

Edit

I have been trying to debug the
Add-Migration
command to understand why does EF see the model is different than it really is, but using EF source is not possible when you have dependencies like Identity which needs signed DLLs to work.

However additional research lead me to the answer here which leads to this blog post By @trailmax and the code to decipher the migrations hash, and with a little search in the EF source I made a small app to extract both the current model and the last migration model to compare side to side.

The code to get the current model representation in XML

//Extracted from EF Source Code
public static class DbContextExtensions
{
public static XDocument GetModel(this DbContext context)
{
return GetModel(w => EdmxWriter.WriteEdmx(context, w));
}

public static XDocument GetModel(Action<XmlWriter> writeXml)
{
using (var memoryStream = new MemoryStream())
{
using (var xmlWriter = XmlWriter.Create(
memoryStream, new XmlWriterSettings
{
Indent = true
}))
{
writeXml(xmlWriter);
}

memoryStream.Position = 0;

return XDocument.Load(memoryStream);
}
}
}

//In Program.cs
using (var db = new DbContext())
{
var model = db.GetModel();
using (var streamWriter = new StreamWriter(@"D:\Current.xml"))
{
streamWriter.Write(model);
}
}


The code to extract the model from the migration in XML

//Code from Trailmax Tech Blog
public class MigrationDecompressor
{
public string ConnectionString { get; set; }

public String DecompressMigrationFromSource(IMigrationMetadata migration)
{
var target = migration.Target;
var xmlDoc = Decompress(Convert.FromBase64String(target));
return xmlDoc.ToString();
}

public String DecompressDatabaseMigration(String migrationName)
{
var sqlToExecute = String.Format("select model from __MigrationHistory where migrationId like '%{0}'", migrationName);

using (var connection = new SqlConnection(ConnectionString))
{
connection.Open();

var command = new SqlCommand(sqlToExecute, connection);

var reader = command.ExecuteReader();
if (!reader.HasRows)
{
throw new Exception("Now Rows to display. Probably migration name is incorrect");
}

while (reader.Read())
{
var model = (byte[])reader["model"];
var decompressed = Decompress(model);
return decompressed.ToString();
}
}

throw new Exception("Something went wrong. You should not get here");
}

/// <summary>
/// Stealing decomposer from EF itself:
/// http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Migrations/Edm/ModelCompressor.cs
/// </summary>
private XDocument Decompress(byte[] bytes)
{
using (var memoryStream = new MemoryStream(bytes))
{
using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
return XDocument.Load(gzipStream);
}
}
}
}

//Inside Program.cs

var decompresser = new MigrationDecompressor
{
ConnectionString = "<connection string>"
};

var databaseSchemaRecord = decompresser.DecompressDatabaseMigration("<migration name>");
using (var streamWriter = new StreamWriter(@"D:\LastMigration.xml"))
{
streamWriter.Write(databaseSchemaRecord);
}


Unfortunately I still cannot find the issue, the only difference between the model and the one hashed with the last migration is the expected change of the added property, none of the unexpected changes show up, also after running the migration suggested by EF, then comparing the current model with the suggested migration, still the model doesn't match the changes, what should be not null is still not null in the model, while the suggested migration show it as nullable.

The expected changes show up

<Property Name="NewProperty" Type="String" MaxLength="Max" FixedLength="false" Unicode="true" />
.
.
.
<ScalarProperty Name="NewProperty" ColumnName="NewProperty" />
.
.
.
<Property Name="NewProperty" Type="nvarchar(max)" Nullable="true" />

Answer

Well, looking again at @trailmax's answer, I wanted to try something, an info that I didn't include in the question, and was dismissing as the cause since its used in other places, was not changed in this migration, and was dismissed as the cause by @trailmax as well, which is attributes and ExpressiveAnnotations attributes in specific.

My actual Bar class looks like this

public class Bar
{
    //This was not touched in ages, some even before adding the first migration
    [Required]
    [AssertThat(@"<Condition>", ErrorMessage = "Please revise the name")]
    public string Name { get; set; }
}

I commented out the AssertThat attribute, and guess what, all the changes that shouldn't exist disappeared.

Comments