如何首先使用迁移向Entity Framework 4.3代码中的列添加描述?

我首先使用Entity Framework 4.3.1代码进行显式迁移。 如何在实体配置类或迁移中添加列的描述,以便最终作为SQL Server中列的描述(例如2008 R2)?

我知道我可以为DbMigration类编写一个扩展方法,该方法将sp_updateextendedpropertysp_addextendedproperty过程调用注册为迁移事务中的sql迁移操作,并在迁移Up方法中创建表后调用该扩展。 但是,我还没有发现一种优雅的内置方式吗? 很高兴有一个属性,迁移的更改检测逻辑可以接收并在scaffolded迁移中生成appropritate方法调用。

我也需要这个。 所以我花了一天时间在这里:

代码

  public class DbDescriptionUpdater where TContext : System.Data.Entity.DbContext { public DbDescriptionUpdater(TContext context) { this.context = context; } Type contextType; TContext context; DbTransaction transaction; public void UpdateDatabaseDescriptions() { contextType = typeof(TContext); this.context = context; var props = contextType.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); transaction = null; try { context.Database.Connection.Open(); transaction = context.Database.Connection.BeginTransaction(); foreach (var prop in props) { if (prop.PropertyType.InheritsOrImplements((typeof(DbSet<>)))) { var tableType = prop.PropertyType.GetGenericArguments()[0]; SetTableDescriptions(tableType); } } transaction.Commit(); } catch { if (transaction != null) transaction.Rollback(); throw; } finally { if (context.Database.Connection.State == System.Data.ConnectionState.Open) context.Database.Connection.Close(); } } private void SetTableDescriptions(Type tableType) { string fullTableName = context.GetTableName(tableType); Regex regex = new Regex(@"(\[\w+\]\.)?\[(?.*)\]"); Match match = regex.Match(fullTableName); string tableName; if (match.Success) tableName = match.Groups["table"].Value; else tableName = fullTableName; var tableAttrs = tableType.GetCustomAttributes(typeof(TableAttribute), false); if (tableAttrs.Length > 0) tableName = ((TableAttribute)tableAttrs[0]).Name; foreach (var prop in tableType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) { if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) continue; var attrs = prop.GetCustomAttributes(typeof(DisplayAttribute), false); if (attrs.Length > 0) SetColumnDescription(tableName, prop.Name, ((DisplayAttribute)attrs[0]).Name); } } private void SetColumnDescription(string tableName, string columnName, string description) { string strGetDesc = "select [value] from fn_listextendedproperty('MS_Description','schema','dbo','table',N'" + tableName + "','column',null) where objname = N'" + columnName + "';"; var prevDesc = RunSqlScalar(strGetDesc); if (prevDesc == null) { RunSql(@"EXEC sp_addextendedproperty @name = N'MS_Description', @value = @desc, @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = @table, @level2type = N'Column', @level2name = @column;", new SqlParameter("@table", tableName), new SqlParameter("@column", columnName), new SqlParameter("@desc", description)); } else { RunSql(@"EXEC sp_updateextendedproperty @name = N'MS_Description', @value = @desc, @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = @table, @level2type = N'Column', @level2name = @column;", new SqlParameter("@table", tableName), new SqlParameter("@column", columnName), new SqlParameter("@desc", description)); } } DbCommand CreateCommand(string cmdText, params SqlParameter[] parameters) { var cmd = context.Database.Connection.CreateCommand(); cmd.CommandText = cmdText; cmd.Transaction = transaction; foreach (var p in parameters) cmd.Parameters.Add(p); return cmd; } void RunSql(string cmdText, params SqlParameter[] parameters) { var cmd = CreateCommand(cmdText, parameters); cmd.ExecuteNonQuery(); } object RunSqlScalar(string cmdText, params SqlParameter[] parameters) { var cmd = CreateCommand(cmdText, parameters); return cmd.ExecuteScalar(); } } public static class ReflectionUtil { public static bool InheritsOrImplements(this Type child, Type parent) { parent = ResolveGenericTypeDefinition(parent); var currentChild = child.IsGenericType ? child.GetGenericTypeDefinition() : child; while (currentChild != typeof(object)) { if (parent == currentChild || HasAnyInterfaces(parent, currentChild)) return true; currentChild = currentChild.BaseType != null && currentChild.BaseType.IsGenericType ? currentChild.BaseType.GetGenericTypeDefinition() : currentChild.BaseType; if (currentChild == null) return false; } return false; } private static bool HasAnyInterfaces(Type parent, Type child) { return child.GetInterfaces() .Any(childInterface => { var currentInterface = childInterface.IsGenericType ? childInterface.GetGenericTypeDefinition() : childInterface; return currentInterface == parent; }); } private static Type ResolveGenericTypeDefinition(Type parent) { var shouldUseGenericType = true; if (parent.IsGenericType && parent.GetGenericTypeDefinition() != parent) shouldUseGenericType = false; if (parent.IsGenericType && shouldUseGenericType) parent = parent.GetGenericTypeDefinition(); return parent; } } public static class ContextExtensions { public static string GetTableName(this DbContext context, Type tableType) { MethodInfo method = typeof(ContextExtensions).GetMethod("GetTableName", new Type[] { typeof(DbContext) }) .MakeGenericMethod(new Type[] { tableType }); return (string)method.Invoke(context, new object[] { context }); } public static string GetTableName(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName(); } public static string GetTableName(this ObjectContext context) where T : class { string sql = context.CreateObjectSet().ToTraceString(); Regex regex = new Regex("FROM (?
.*) AS"); Match match = regex.Match(sql); string table = match.Groups["table"].Value; return table; } }

如何使用

Migrations/Configuration.cs文件中,在Seed方法的末尾添加:

 DbDescriptionUpdater updater = new DbDescriptionUpdater(context); updater.UpdateDatabaseDescriptions(); 

然后在Package Manager控制台中键入update-database并按Enter键。 而已。

代码使用实体类属性的[Display(Name="Description here")]属性来设置描述。

请报告任何错误或建议改进。

谢谢

我已经使用过其他人的这些代码,我想说谢谢:

添加列描述

检查类是否派生自generics类

从Entity Framework MetaData获取数据库表名

C#中的generics,使用变量的类型作为参数

注意当前的答案非常满意(但是工作的道具!),我想要一种方法来拉取我的类中的现有注释标记而不是使用属性。 在我看来,我不知道微软为什么不支持这个,因为它似乎应该存在!

首先,打开XML Documentation文件:Project Properties-> Build-> XML documentation file-> App_Data \ YourProjectName.XML

其次,将文件包含为嵌入式资源。 构建项目,转到App_Data,显示隐藏文件并包含生成的XML文件。 选择嵌入式资源和复制如果更新(这是可选的,您可以明确指定路径,但在我看来这是更干净)。 请注意,必须使用此方法,因为程序集中不存在标记,这将使您无法找到存储XML的位置。

以下是代码实现,它是已接受答案的修改版本:

 public class SchemaDescriptionUpdater where TContext : DbContext { Type contextType; TContext context; DbTransaction transaction; XmlAnnotationReader reader; public SchemaDescriptionUpdater(TContext context) { this.context = context; reader = new XmlAnnotationReader(); } public SchemaDescriptionUpdater(TContext context, string xmlDocumentationPath) { this.context = context; reader = new XmlAnnotationReader(xmlDocumentationPath); } public void UpdateDatabaseDescriptions() { contextType = typeof(TContext); var props = contextType.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); transaction = null; try { context.Database.Connection.Open(); transaction = context.Database.Connection.BeginTransaction(); foreach (var prop in props) { if (prop.PropertyType.InheritsOrImplements((typeof(DbSet<>)))) { var tableType = prop.PropertyType.GetGenericArguments()[0]; SetTableDescriptions(tableType); } } transaction.Commit(); } catch { if (transaction != null) transaction.Rollback(); throw; } finally { if (context.Database.Connection.State == System.Data.ConnectionState.Open) context.Database.Connection.Close(); } } private void SetTableDescriptions(Type tableType) { string fullTableName = context.GetTableName(tableType); Regex regex = new Regex(@"(\[\w+\]\.)?\[(?.*)\]"); Match match = regex.Match(fullTableName); string tableName; if (match.Success) tableName = match.Groups["table"].Value; else tableName = fullTableName; var tableAttrs = tableType.GetCustomAttributes(typeof(TableAttribute), false); if (tableAttrs.Length > 0) tableName = ((TableAttribute)tableAttrs[0]).Name; // set the description for the table string tableComment = reader.GetCommentsForResource(tableType, null, XmlResourceType.Type); if (!string.IsNullOrEmpty(tableComment)) SetDescriptionForObject(tableName, null, tableComment); // get all of the documentation for each property/column ObjectDocumentation[] columnComments = reader.GetCommentsForResource(tableType); foreach (var column in columnComments) { SetDescriptionForObject(tableName, column.PropertyName, column.Documentation); } } private void SetDescriptionForObject(string tableName, string columnName, string description) { string strGetDesc = ""; // determine if there is already an extended description if(string.IsNullOrEmpty(columnName)) strGetDesc = "select [value] from fn_listextendedproperty('MS_Description','schema','dbo','table',N'" + tableName + "',null,null);"; else strGetDesc = "select [value] from fn_listextendedproperty('MS_Description','schema','dbo','table',N'" + tableName + "','column',null) where objname = N'" + columnName + "';"; var prevDesc = (string)RunSqlScalar(strGetDesc); var parameters = new List { new SqlParameter("@table", tableName), new SqlParameter("@desc", description) }; // is it an update, or new? string funcName = "sp_addextendedproperty"; if (!string.IsNullOrEmpty(prevDesc)) funcName = "sp_updateextendedproperty"; string query = @"EXEC " + funcName + @" @name = N'MS_Description', @value = @desc,@level0type = N'Schema', @level0name = 'dbo',@level1type = N'Table', @level1name = @table"; // if a column is specified, add a column description if (!string.IsNullOrEmpty(columnName)) { parameters.Add(new SqlParameter("@column", columnName)); query += ", @level2type = N'Column', @level2name = @column"; } RunSql(query, parameters.ToArray()); } DbCommand CreateCommand(string cmdText, params SqlParameter[] parameters) { var cmd = context.Database.Connection.CreateCommand(); cmd.CommandText = cmdText; cmd.Transaction = transaction; foreach (var p in parameters) cmd.Parameters.Add(p); return cmd; } void RunSql(string cmdText, params SqlParameter[] parameters) { var cmd = CreateCommand(cmdText, parameters); cmd.ExecuteNonQuery(); } object RunSqlScalar(string cmdText, params SqlParameter[] parameters) { var cmd = CreateCommand(cmdText, parameters); return cmd.ExecuteScalar(); } } public static class ReflectionUtil { public static bool InheritsOrImplements(this Type child, Type parent) { parent = ResolveGenericTypeDefinition(parent); var currentChild = child.IsGenericType ? child.GetGenericTypeDefinition() : child; while (currentChild != typeof(object)) { if (parent == currentChild || HasAnyInterfaces(parent, currentChild)) return true; currentChild = currentChild.BaseType != null && currentChild.BaseType.IsGenericType ? currentChild.BaseType.GetGenericTypeDefinition() : currentChild.BaseType; if (currentChild == null) return false; } return false; } private static bool HasAnyInterfaces(Type parent, Type child) { return child.GetInterfaces() .Any(childInterface => { var currentInterface = childInterface.IsGenericType ? childInterface.GetGenericTypeDefinition() : childInterface; return currentInterface == parent; }); } private static Type ResolveGenericTypeDefinition(Type parent) { var shouldUseGenericType = true; if (parent.IsGenericType && parent.GetGenericTypeDefinition() != parent) shouldUseGenericType = false; if (parent.IsGenericType && shouldUseGenericType) parent = parent.GetGenericTypeDefinition(); return parent; } } public static class ContextExtensions { public static string GetTableName(this DbContext context, Type tableType) { MethodInfo method = typeof(ContextExtensions).GetMethod("GetTableName", new Type[] { typeof(DbContext) }) .MakeGenericMethod(new Type[] { tableType }); return (string)method.Invoke(context, new object[] { context }); } public static string GetTableName(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName(); } public static string GetTableName(this ObjectContext context) where T : class { string sql = context.CreateObjectSet().ToTraceString(); Regex regex = new Regex("FROM (?
.*) AS"); Match match = regex.Match(sql); string table = match.Groups["table"].Value; return table; } }

从visual studio获取注释标记的类生成了XML文档文件:

 public class XmlAnnotationReader { public string XmlPath { get; protected internal set; } public XmlDocument Document { get; protected internal set; } public XmlAnnotationReader() { var assembly = Assembly.GetExecutingAssembly(); string resourceName = String.Format("{0}.App_Data.{0}.XML", assembly.GetName().Name); this.XmlPath = resourceName; using (Stream stream = assembly.GetManifestResourceStream(resourceName)) { using (StreamReader reader = new StreamReader(stream)) { XmlDocument doc = new XmlDocument(); //string result = reader.ReadToEnd(); doc.Load(reader); this.Document = doc; } } } public XmlAnnotationReader(string xmlPath) { this.XmlPath = xmlPath; if (File.Exists(xmlPath)) { XmlDocument doc = new XmlDocument(); doc.Load(this.XmlPath); this.Document = doc; } else throw new FileNotFoundException(String.Format("Could not find the XmlDocument at the specified path: {0}\r\nCurrent Path: {1}", xmlPath, Assembly.GetExecutingAssembly().Location)); } ///  /// Retrievethe XML comments documentation for a given resource /// Eg. ITN.Data.Models.Entity.TestObject.MethodName ///  ///  public string GetCommentsForResource(string resourcePath, XmlResourceType type) { XmlNode node = Document.SelectSingleNode(String.Format("//member[starts-with(@name, '{0}:{1}')]/summary", GetObjectTypeChar(type), resourcePath)); if (node != null) { string xmlResult = node.InnerText; string trimmedResult = Regex.Replace(xmlResult, @"\s+", " "); return trimmedResult; } return string.Empty; } ///  /// Retrievethe XML comments documentation for a given resource /// Eg. ITN.Data.Models.Entity.TestObject.MethodName ///  ///  public ObjectDocumentation[] GetCommentsForResource(Type objectType) { List comments = new List(); string resourcePath = objectType.FullName; PropertyInfo[] properties = objectType.GetProperties(); FieldInfo[] fields = objectType.GetFields(); List objectNames = new List(); objectNames.AddRange(properties.Select(x => new ObjectDocumentation() { PropertyName = x.Name, Type = XmlResourceType.Property }).ToList()); objectNames.AddRange(properties.Select(x => new ObjectDocumentation() { PropertyName = x.Name, Type = XmlResourceType.Field }).ToList()); foreach (var property in objectNames) { XmlNode node = Document.SelectSingleNode(String.Format("//member[starts-with(@name, '{0}:{1}.{2}')]/summary", GetObjectTypeChar(property.Type), resourcePath, property.PropertyName )); if (node != null) { string xmlResult = node.InnerText; string trimmedResult = Regex.Replace(xmlResult, @"\s+", " "); property.Documentation = trimmedResult; comments.Add(property); } } return comments.ToArray(); } ///  /// Retrievethe XML comments documentation for a given resource ///  /// The type of class to retrieve documenation on /// The name of the property in the specified class ///  ///  public string GetCommentsForResource(Type objectType, string propertyName, XmlResourceType resourceType) { List comments = new List(); string resourcePath = objectType.FullName; string scopedElement = resourcePath; if (propertyName != null && resourceType != XmlResourceType.Type) scopedElement += "." + propertyName; XmlNode node = Document.SelectSingleNode(String.Format("//member[starts-with(@name, '{0}:{1}')]/summary", GetObjectTypeChar(resourceType), scopedElement)); if (node != null) { string xmlResult = node.InnerText; string trimmedResult = Regex.Replace(xmlResult, @"\s+", " "); return trimmedResult; } return string.Empty; } private string GetObjectTypeChar(XmlResourceType type) { switch (type) { case XmlResourceType.Field: return "F"; case XmlResourceType.Method: return "M"; case XmlResourceType.Property: return "P"; case XmlResourceType.Type: return "T"; } return string.Empty; } } public class ObjectDocumentation { public string PropertyName { get; set; } public string Documentation { get; set; } public XmlResourceType Type { get; set; } } public enum XmlResourceType { Method, Property, Field, Type } 

你不能使用ExceuteSqlCommand方法。 在这里,您可以显式定义要在Table中添加的任何元属性。

http://msdn.microsoft.com/en-us/library/system.data.entity.database.executesqlcommand(v=vs.103).aspx

谢谢Mr.Mahmoodvcs的出色解决方案。 允许我修改它只需将“DisplayAttribute”替换为使用的“DescriptionAttribute”:

 [Display(Name="Description here")] 

你会用:

 [Description("Description here")] 

所以它也包括表格。

  public class DbDescriptionUpdater where TContext : System.Data.Entity.DbContext { public DbDescriptionUpdater(TContext context) { this.context = context; } Type contextType; TContext context; DbTransaction transaction; public void UpdateDatabaseDescriptions() { contextType = typeof(TContext); this.context = context; var props = contextType.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); transaction = null; try { context.Database.Connection.Open(); transaction = context.Database.Connection.BeginTransaction(); foreach (var prop in props) { if (prop.PropertyType.InheritsOrImplements((typeof(DbSet<>)))) { var tableType = prop.PropertyType.GetGenericArguments()[0]; SetTableDescriptions(tableType); } } transaction.Commit(); } catch { if (transaction != null) transaction.Rollback(); throw; } finally { if (context.Database.Connection.State == System.Data.ConnectionState.Open) context.Database.Connection.Close(); } } private void SetTableDescriptions(Type tableType) { string fullTableName = context.GetTableName(tableType); Regex regex = new Regex(@"(\[\w+\]\.)?\[(?.*)\]"); Match match = regex.Match(fullTableName); string tableName; if (match.Success) tableName = match.Groups["table"].Value; else tableName = fullTableName; var tableAttrs = tableType.GetCustomAttributes(typeof(TableAttribute), false); if (tableAttrs.Length > 0) tableName = ((TableAttribute)tableAttrs[0]).Name; var table_attrs = tableType.GetCustomAttributes(typeof(DescriptionAttribute), false); if (table_attrs != null && table_attrs.Length > 0) SetTableDescription(tableName, ((DescriptionAttribute)table_attrs[0]).Description); foreach (var prop in tableType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) { if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) continue; var attrs = prop.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attrs != null && attrs.Length > 0) SetColumnDescription(tableName, prop.Name, ((DescriptionAttribute)attrs[0]).Description); } } private void SetColumnDescription(string tableName, string columnName, string description) { string strGetDesc = "select [value] from fn_listextendedproperty('MS_Description','schema','dbo','table',N'" + tableName + "','column',null) where objname = N'" + columnName + "';"; var prevDesc = RunSqlScalar(strGetDesc); if (prevDesc == null) { RunSql(@"EXEC sp_addextendedproperty @name = N'MS_Description', @value = @desc, @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = @table, @level2type = N'Column', @level2name = @column;", new SqlParameter("@table", tableName), new SqlParameter("@column", columnName), new SqlParameter("@desc", description)); } else { RunSql(@"EXEC sp_updateextendedproperty @name = N'MS_Description', @value = @desc, @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = @table, @level2type = N'Column', @level2name = @column;", new SqlParameter("@table", tableName), new SqlParameter("@column", columnName), new SqlParameter("@desc", description)); } } private void SetTableDescription(string tableName, string description) { string strGetDesc = "select [value] from fn_listextendedproperty('MS_Description','schema','dbo','table',N'" + tableName + "',null,null);"; var prevDesc = RunSqlScalar(strGetDesc); if (prevDesc == null) { RunSql(@"EXEC sp_addextendedproperty @name = N'MS_Description', @value = @desc, @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = @table;", new SqlParameter("@table", tableName), new SqlParameter("@desc", description)); } else { RunSql(@"EXEC sp_updateextendedproperty @name = N'MS_Description', @value = @desc, @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = @table;", new SqlParameter("@table", tableName), new SqlParameter("@desc", description)); } } DbCommand CreateCommand(string cmdText, params SqlParameter[] parameters) { var cmd = context.Database.Connection.CreateCommand(); cmd.CommandText = cmdText; cmd.Transaction = transaction; foreach (var p in parameters) cmd.Parameters.Add(p); return cmd; } void RunSql(string cmdText, params SqlParameter[] parameters) { var cmd = CreateCommand(cmdText, parameters); cmd.ExecuteNonQuery(); } object RunSqlScalar(string cmdText, params SqlParameter[] parameters) { var cmd = CreateCommand(cmdText, parameters); return cmd.ExecuteScalar(); } } public static class ReflectionUtil { public static bool InheritsOrImplements(this Type child, Type parent) { parent = ResolveGenericTypeDefinition(parent); var currentChild = child.IsGenericType ? child.GetGenericTypeDefinition() : child; while (currentChild != typeof(object)) { if (parent == currentChild || HasAnyInterfaces(parent, currentChild)) return true; currentChild = currentChild.BaseType != null && currentChild.BaseType.IsGenericType ? currentChild.BaseType.GetGenericTypeDefinition() : currentChild.BaseType; if (currentChild == null) return false; } return false; } private static bool HasAnyInterfaces(Type parent, Type child) { return child.GetInterfaces() .Any(childInterface => { var currentInterface = childInterface.IsGenericType ? childInterface.GetGenericTypeDefinition() : childInterface; return currentInterface == parent; }); } private static Type ResolveGenericTypeDefinition(Type parent) { var shouldUseGenericType = true; if (parent.IsGenericType && parent.GetGenericTypeDefinition() != parent) shouldUseGenericType = false; if (parent.IsGenericType && shouldUseGenericType) parent = parent.GetGenericTypeDefinition(); return parent; } } public static class ContextExtensions { public static string GetTableName(this DbContext context, Type tableType) { MethodInfo method = typeof(ContextExtensions).GetMethod("GetTableName", new Type[] { typeof(DbContext) }) .MakeGenericMethod(new Type[] { tableType }); return (string)method.Invoke(context, new object[] { context }); } public static string GetTableName(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName(); } public static string GetTableName(this ObjectContext context) where T : class { string sql = context.CreateObjectSet().ToTraceString(); Regex regex = new Regex("FROM (?
.*) AS"); Match match = regex.Match(sql); string table = match.Groups["table"].Value; return table; } }

虽然问题是关于EF4,但这个答案针对的是EF6,考虑到自问题提出以来的通过时间,这应该是合适的。

我认为评论属于Migration Up and Down方法,而不是一些Seed方法。

因此,正如@MichaelBrown所建议的那样,从启用XML文档输出开始,并将文档文件作为嵌入式资源包含在项目中。

然后,让我们使用Convention将注释转换为表/列注释。 对于诸如多行注释和摆脱过多的空白之类的事情,有一些调整。

 public class CommentConvention : Convention { public const string NewLinePlaceholder = "<>"; public CommentConvention() { var docuXml = new XmlDocument(); // Read the documentation xml using (var commentStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Namespace.Documentation.xml")) { docuXml.Load(commentStream); } // configure class/table comment Types() .Having(pi => docuXml.SelectSingleNode($"//member[starts-with(@name, 'T:{pi?.FullName}')]/summary")) .Configure((c, a) => { c.HasTableAnnotation("Comment", GetCommentTextWithNewlineReplacement(a)); }); // configure property/column comments Properties() .Having(pi => docuXml.SelectSingleNode( $"//member[starts-with(@name, 'P:{pi?.DeclaringType?.FullName}.{pi?.Name}')]/summary")) .Configure((c, a) => { c.HasColumnAnnotation("Comment", GetCommentTextWithNewlineReplacement(a)); }); } // adjust the documentation text to handle newline and whitespace private static string GetCommentTextWithNewlineReplacement(XmlNode a) { if (string.IsNullOrWhiteSpace(a.InnerText)) { return null; } return string.Join( NewLinePlaceholder, a.InnerText.Trim() .Split(new string[] {"\r\n", "\r", "\n"}, StringSplitOptions.None) .Select(line => line.Trim())); } } 

OnModelCreating方法中注册约定。

预期结果:创建新迁移时,注释将作为注释包含在内

 CreateTable( "schema.Table", c => new { Id = c.Decimal(nullable: false, precision: 10, scale: 0, identity: true, annotations: new Dictionary { { "Comment", new AnnotationValues(oldValue: null, newValue: "Commenting the Id Column") }, }), // ... 

继续第二部分:调整SQL生成器以从注释创建注释。

这个是针对Oracle的,但MS Sql应该非常相似

 class CustomOracleSqlCodeGen : MigrationSqlGenerator { // the actual SQL generator private readonly MigrationSqlGenerator _innerSqlGenerator; public CustomOracleSqlCodeGen(MigrationSqlGenerator innerSqlGenerator) { _innerSqlGenerator = innerSqlGenerator; } public override IEnumerable Generate(IEnumerable migrationOperations, string providerManifestToken) { var ms = _innerSqlGenerator.Generate(AddCommentSqlStatements(migrationOperations), providerManifestToken); return ms; } // generate additional SQL operations to produce comments IEnumerable AddCommentSqlStatements(IEnumerable migrationOperations) { foreach (var migrationOperation in migrationOperations) { // the original inputted operation yield return migrationOperation; // create additional operations to produce comments if (migrationOperation is CreateTableOperation cto) { foreach (var ctoAnnotation in cto.Annotations.Where(x => x.Key == "Comment")) { if (ctoAnnotation.Value is string annotation) { var commentString = annotation.Replace( CommentConvention.NewLinePlaceholder, Environment.NewLine); yield return new SqlOperation($"COMMENT ON TABLE {cto.Name} IS '{commentString}'"); } } foreach (var columnModel in cto.Columns) { foreach (var columnModelAnnotation in columnModel.Annotations.Where(x => x.Key == "Comment")) { if (columnModelAnnotation.Value is AnnotationValues annotation) { var commentString = (annotation.NewValue as string)?.Replace( CommentConvention.NewLinePlaceholder, Environment.NewLine); yield return new SqlOperation( $"COMMENT ON COLUMN {cto.Name}.{columnModel.Name} IS '{commentString}'"); } } } } } } } 

DbMigrationsConfiguration构造函数中,注册新的代码生成器(同样,这是oracle特定的,但对于其他SQL提供程序将类似)

 internal sealed class Configuration : DbMigrationsConfiguration { public Configuration() { AutomaticMigrationsEnabled = false; var cg = GetSqlGenerator("Oracle.ManagedDataAccess.Client"); SetSqlGenerator("Oracle.ManagedDataAccess.Client", new CustomOracleSqlCodeGen(cg)); } // ... 

预期结果: UpDown方法中的注释注释被转换为更改数据库中注释的SQL语句。