Как добавить описание в столбцы в коде Entity Framework 4.3 с использованием миграции?

Сначала я использую код Entity Framework 4.3.1 с явными переходами. Как добавить описания для столбцов либо в classы конфигурации сущности, либо в связи с миграциями, чтобы они оказались в качестве описания столбца на SQL-сервере (например, 2008 R2)?

Я знаю, что, возможно, я могу написать метод расширения для classа DbMigration , который зарегистрировал sp_updateextendedproperty sp_addextendedproperty процедуры sp_updateextendedproperty или sp_addextendedproperty как операцию миграции sql внутри транзакции миграции и вызвал это расширение после создания таблицы в методе миграции Up . Но есть ли элегантный способ, который мне еще предстоит открыть? Было бы неплохо иметь атрибут, который логика обнаружения изменений миграции может захватывать и генерировать соответствующие вызовы методов в миграции подмостей.

Мне тоже это нужно. Поэтому я провел целый день, и вот он:

Код

  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(); 

Затем в окне диспетчера диспетчера диспетчера пакетов введите update-database и нажмите Enter. Вот и все.

В коде для определения описания используется атрибут [Display(Name="Description here")] на свойства classа сущности.

Сообщите об ошибке или предложите улучшения.

Благодаря

Я использовал этот код у других людей, и я хочу сказать спасибо:

добавление описания столбца

Проверьте, является ли class производным от общего classа

Получить имя таблицы базы данных из Entity Framework MetaData

Generics в C #, используя тип переменной в качестве параметра

Заметьте, вполне удовлетворенный текущим ответом (но реквизитом для работы!), Мне нужен способ вытащить существующую разметку комментариев в моих classах вместо использования атрибутов. И, на мой взгляд, я не знаю, почему, черт возьми, Microsoft не поддержала это, поскольку кажется очевидным, что он должен быть там!

Сначала включите файл документации XML: Project Properties-> Build-> файл документации XML-> 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; } }

И class, который получает разметку комментариев из созданной визуальной студией файла документации 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 . Здесь вы можете явно определить, какое мета-свойство вы хотите добавить в таблицу.

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

Благодарю вас, господин Махмудвш за отличное решение. позвольте мне изменить его, просто замените «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, который должен быть соответствующим с учетом прошедшего времени с момента запроса.

Я думаю, что комментарии относятся к методам миграции Up и 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 .

Ожидаемый результат: когда создается новая миграция, комментарии будут включены как annotations, такие как

 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)); } // ... 

Ожидаемый результат: annotations комментариев из методов Up и Down переводится в операторы SQL, которые изменяют комментарии в базе данных.

Давайте будем гением компьютера.