Code First Annotations
Migrations can detect any differences when a Code First model is changed and use this information to generate SQL to keep the database up-to-date. However, Code First makes use of the Entity Data Model (EDM) to do this and so can only track differences that
are represented in the model. For example, database indexes are not represented in the model and so Migrations could not detect differences.
Code First annotations are a way of annotating the Code First model with any type of database metadata such that Migrations can detect differences in this metadata as the model evolves and can scaffold appropriate migrations for these changes. This can be
coupled with extensions to the Migrations SQL generator such that SQL can be generated to update the database appropriately.
All the examples below use the model included at the end of this page.
The fluent API
To add an annotation for a table:
modelBuilder.Entity<Post>()
.HasTableAnnotation("MyAnnotation", "MyData");
To add an annotation for a column:
modelBuilder.Entity<Post>()
.Property(p => p.Title)
.HasColumnAnnotation("MyAnnotation", "MyOtherData");
Any annotation can be cleared by passing null:
modelBuilder.Entity<Post>()
.HasTableAnnotation("MyAnnotation", null);
You can also add annotations for tables and columns that are not directly mapped to properties or classes. For example, to add an annotation for a many-to-many join table:
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(t => t.Posts)
.Map(m => m.HasTableAnnotation("MyAnnotation", "MyData"));
To add an annotation for an FK in an independent association:
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithRequired(p => p.Blog)
.Map(m => m.MapKey("BlogId").HasColumnAnnotation("BlogId", "MyAnnotation", "MyData"));
Conventions
Similar APIs exist for Code First conventions. For example, to add an annotation to any column mapped from a property named “Title”:
modelBuilder.Properties()
.Where(p => p.Name == "Title")
.Configure(p => p.HasColumnAnnotation("MyAnnotation", "MyData"));
To add an annotation to any type starting with “B”:
modelBuilder.Types()
.Where(t => t.Name.StartsWith("B"))
.Configure(c => c.HasTableAnnotation("MyAnnotation", "MyData"));
Attributes
It is also possible to create a .NET attribute and couple it to a Code First convention such that putting the attribute onto an entity type or property will cause the corresponding table or column to be annotated. For example, consider this attribute
class:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false)]
public class AnnotateMeAttribute : Attribute
{
public string Data { get; set; }
}
Add a convention to use this attribute to create column annotations:
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<AnnotateMeAttribute, string>(
"MyAnnotation",
(property, attributes) => attributes.Single().Data));
Or to create table annotations:
modelBuilder.Conventions.Add(
new AttributeToTableAnnotationConvention<AnnotateMeAttribute, string>(
"MyAnnotation",
(entityType, attributes) => attributes.Single().Data));
Note that in the example above the attribute is marked with AllowMultiple = false which means the convention will only be called with exactly one attribute. If AllowMultiple = true is used, then the attributes list may contain one or more attribute instances
which usually must be processed into a single annotation since a given table or column can only have one annotation with any given name. The TypeId property of the attribute will also need to be overwritten to return a different value for each instance because
of the way .NET type descriptors handle attributes.
Scaffolding
Migrations will automatically detect changes in annotations and scaffold the appropriate migration operations. These operations may be:
- AddColumnOperation when a new column is created it will contain annotation information
- AlterColumnOperation when annotations on an existing column change
- DropColumnOperation when an annotated column is dropped
- AddTableOperation when a new table is created it will contain annotation information
- AlterTableOperation when annotations on an existing table change
- DropTableOperation when an annotated table is dropped
Annotation information in the migration is provided as a dictionary of annotation name to annotation values. For example:
public partial class Second : DbMigration
{
public override void Up()
{
AlterColumn("dbo.Posts", "Title", c => c.String(
annotations: new Dictionary<string, AnnotationValues>
{
{
"MyAnnotation",
new AnnotationValues(oldValue: "MyData", newValue: "MyOtherData")
},
}));
}
public override void Down()
{
AlterColumn("dbo.Posts", "Title", c => c.String(
annotations: new Dictionary<string, AnnotationValues>
{
{
"MyAnnotation",
new AnnotationValues(oldValue: "MyOtherData", newValue: "MyData")
},
}));
}
}
SQL generation
The default SQL generators do not do anything with annotation information. To actually generate SQL for the annotations it is necessary to replace or extend the MigrationSqlGenerator class for any database provider of interest. For example:
public class ExtendedSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(ColumnModel column, IndentedTextWriter writer)
{
// Obtain annotation info
AnnotationValues values;
column.Annotations.TryGetValue("MyAnnotation", out values);
// Do SQL generation for column using annotation value as appropriate
}
}
The extended SQL generator should then be registered, either in the Migrations configuration:
internal sealed class Configuration : DbMigrationsConfiguration<BlogContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
SetSqlGenerator("System.Data.SqlClient", new ExtendedSqlGenerator());
}
}
Or in the application’s DbConfiguration:
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
SetMigrationSqlGenerator("System.Data.SqlClient", () => new ExtendedSqlGenerator());
}
}
Non-string annotations
In all the examples above the annotation value is a simple string. This is the easiest thing to do, but annotation values can be of any type if necessary. However, since the annotations must be stored in the serialized form of the Code First model it is
necessary to be able to serialize and de-serialize the value. This is done by creating a class that implements IMetadataAnnotationSerializer. For example, annotation with a CLR type would require something like this:
internal class ClrTypeAnnotationSerializer : IMetadataAnnotationSerializer
{
public string Serialize(string name, object value)
{
return ((Type)value).AssemblyQualifiedName;
}
public object Deserialize(string name, string value)
{
return Type.GetType(value);
}
}
And the serializer must be registering in the application’s DbConfiguration:
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
SetMetadataAnnotationSerializer("Myannotation", () => new ClrTypeAnnotationSerializer());
}
}
A code generator is also required if non-string types are to be scaffolded in the generated migrations. To do this, implement a class that extends from AnnotationCodeGenerator. For example:
public class ClrTypeCSharpCodeGenerator : AnnotationCodeGenerator
{
public override void Generate(string annotationName, object annotation, IndentedTextWriter writer)
{
writer.Write("Type.GetType(\"" + ((Type)annotation).AssemblyQualifiedName + "\")");
}
}
This annotation code generator must then be added to the overall Migrations code generator in the Migrations configuration:
internal sealed class Configuration : DbMigrationsConfiguration<BlogContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
CodeGenerator.AnnotationGenerators["MyAnnotation"] = () => new ClrTypeCSharpCodeGenerator();
}
}
Unfortunately it is not possible to register the code generator in the application’s DbConfiguration.
The model
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
[AnnotateMe(Data = "MyData")]
public string Title { get; set; }
public string Content { get; set; }
public virtual Blog Blog { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}