26

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

我知道我可能可以为DbMigration类编写一个扩展方法,将sp_updateextendedpropertyorsp_addextendedproperty过程调用注册为迁移事务中的 sql 迁移操作,并在迁移Up方法中创建表后调用该扩展。但是有没有一种我还没有发现的优雅的内置方式?如果有一个属性,迁移的更改检测逻辑可以获取并在脚手架迁移中生成适当的方法调用,那就太好了。

4

5 回答 5

17

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

编码

    public class DbDescriptionUpdater<TContext>
        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+\]\.)?\[(?<table>.*)\]");
            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<T>(this DbContext context) where T : class
        {
            ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;

            return objectContext.GetTableName<T>();
        }

        public static string GetTableName<T>(this ObjectContext context) where T : class
        {
            string sql = context.CreateObjectSet<T>().ToTraceString();
            Regex regex = new Regex("FROM (?<table>.*) AS");
            Match match = regex.Match(sql);

            string table = match.Groups["table"].Value;
            return table;
        }
    }

如何使用

在您的Migrations/Configuration.cs文件中,将其添加到方法的末尾Seed

DbDescriptionUpdater<ContextClass> updater = new DbDescriptionUpdater<ContextClass>(context);
updater.UpdateDatabaseDescriptions();

然后在包管理器控制台中键入update-database并按 Enter。而已。

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

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

谢谢

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

添加列描述

检查一个类是否派生自一个泛型类

从实体框架元数据中获取数据库表名

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

于 2013-09-26T09:27:34.723 回答
10

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

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

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

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

public class SchemaDescriptionUpdater<TContext> 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+\]\.)?\[(?<table>.*)\]");
        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<SqlParameter>
        {
            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<T>(this DbContext context) where T : class
    {
        ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;

        return objectContext.GetTableName<T>();
    }

    public static string GetTableName<T>(this ObjectContext context) where T : class
    {
        string sql = context.CreateObjectSet<T>().ToTraceString();
        Regex regex = new Regex("FROM (?<table>.*) 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));
    }

    /// <summary>
    /// Retrievethe XML comments documentation for a given resource
    /// Eg. ITN.Data.Models.Entity.TestObject.MethodName
    /// </summary>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// Retrievethe XML comments documentation for a given resource
    /// Eg. ITN.Data.Models.Entity.TestObject.MethodName
    /// </summary>
    /// <returns></returns>
    public ObjectDocumentation[] GetCommentsForResource(Type objectType)
    {
        List<ObjectDocumentation> comments = new List<ObjectDocumentation>();
        string resourcePath = objectType.FullName;

        PropertyInfo[] properties = objectType.GetProperties();
        FieldInfo[] fields = objectType.GetFields();
        List<ObjectDocumentation> objectNames = new List<ObjectDocumentation>();
        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();
    }

    /// <summary>
    /// Retrievethe XML comments documentation for a given resource
    /// </summary>
    /// <param name="objectType">The type of class to retrieve documenation on</param>
    /// <param name="propertyName">The name of the property in the specified class</param>
    /// <param name="resourceType"></param>
    /// <returns></returns>
    public string GetCommentsForResource(Type objectType, string propertyName, XmlResourceType resourceType)
    {
        List<ObjectDocumentation> comments = new List<ObjectDocumentation>();
        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
}
于 2015-03-24T03:40:10.540 回答
3

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

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

于 2013-04-02T19:00:05.963 回答
3

谢谢Mahmoodvcs先生的出色解决方案。允许我修改它,只需将“DisplayAttribute”替换为“DescriptionAttribute”即可:

[Display(Name="Description here")]

你将使用:

[Description("Description here")]

所以它也包括表格。

    public class DbDescriptionUpdater<TContext>
   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+\]\.)?\[(?<table>.*)\]");
        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<T>(this DbContext context) where T : class
    {
        ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;

        return objectContext.GetTableName<T>();
    }

    public static string GetTableName<T>(this ObjectContext context) where T : class
    {
        string sql = context.CreateObjectSet<T>().ToTraceString();
        Regex regex = new Regex("FROM (?<table>.*) AS");
        Match match = regex.Match(sql);

        string table = match.Groups["table"].Value;
        return table;
    }
}
于 2018-03-02T21:03:01.103 回答
2

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

我认为评论属于迁移UpDown方法,而不是某种Seed方法。

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

然后,让我们使用Convention. 需要对多行注释和消除过多的空白等内容进行一些调整。

public class CommentConvention : Convention
{
    public const string NewLinePlaceholder = "<<NEWLINE>>";

    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<string, AnnotationValues>
                {
                    { 
                        "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<MigrationStatement> Generate(IEnumerable<MigrationOperation> migrationOperations, string providerManifestToken)
    {
        var ms = _innerSqlGenerator.Generate(AddCommentSqlStatements(migrationOperations), providerManifestToken);

        return ms;
    }

    // generate additional SQL operations to produce comments
    IEnumerable<MigrationOperation> AddCommentSqlStatements(IEnumerable<MigrationOperation> 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<EntityFramework.Dev.ZdbTestContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
        var cg = GetSqlGenerator("Oracle.ManagedDataAccess.Client");
        SetSqlGenerator("Oracle.ManagedDataAccess.Client", new CustomOracleSqlCodeGen(cg));
    }
    // ...

Up预期结果:和方法中的注释注释Down被转换为改变数据库中注释的 SQL 语句。

于 2018-06-29T09:44:01.447 回答