简体   繁体   English

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

[英]How to add description to columns in Entity Framework 4.3 code first using migrations?

I'm using Entity Framework 4.3.1 code first with explicit migrations.我首先使用 Entity Framework 4.3.1 代码进行显式迁移。 How do I add descriptions for columns either in the entity configuration classes or the migrations, so that it ends up as the description of a column in SQL server (eg 2008 R2)?如何在实体配置类或迁移中添加列的描述,使其最终成为 SQL 服务器(例如 2008 R2)中列的描述?

I know I can probably write an extension method for the DbMigration class that would register the sp_updateextendedproperty or sp_addextendedproperty procedure call as a sql migration operation inside the migration transaction and call that extension after table creation in the migration Up method.我知道我可以为DbMigration class 编写一个扩展方法,它将sp_updateextendedpropertysp_addextendedproperty过程调用注册为迁移事务中的 sql 迁移操作,并在迁移Up方法中创建表后调用该扩展。 But is there an elegant built in way that I've yet to discover?但是有没有一种我还没有发现的优雅的内置方式? Would be nice to have an attribute that the migrations' change detection logic can pick up on and generate appropritate method calls in the scaffolded migration.最好有一个属性,迁移的更改检测逻辑可以在脚手架迁移中获取并生成适当的方法调用。

I needed this too.我也需要这个。 So I spent a day and here it is:所以我花了一天,这里是:

The Code代码

    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;
        }
    }

How To Use如何使用

In your Migrations/Configuration.cs file, add this at the end of the Seed method:在您的Migrations/Configuration.cs文件中,将其添加到Seed方法的末尾:

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

Then in Package Manager Console type update-database and hit Enter.然后在 Package Manager Console 中键入update-database并按 Enter。 That's it.而已。

The code uses [Display(Name="Description here")] attribute on your entity class properties to set the description.该代码使用实体 class 属性上的[Display(Name="Description here")]属性来设置描述。

Please report any bug or suggest improvements.请报告任何错误或提出改进建议。

Thanks to谢谢

I've used these code from other people and I want to say thanks:我已经使用了其他人的这些代码,我想说声谢谢:

adding a column description添加列说明

Check if a class is derived from a generic class检查 class 是否派生自通用 class

Get Database Table Name from Entity Framework MetaData从实体框架元数据中获取数据库表名

Generics in C#, using type of a variable as parameter C#中的Generics,使用变量的类型作为参数

Note quite satisfied with the current answer (but props for the work,).注意对当前的答案相当满意(但工作的道具,)。 I wanted a way to pull the existing comment markup in my classes instead of using attributes, And in my opinion, I don't know why the hell Microsoft didn't support this as it seems obvious it should be there!我想要一种方法来提取我的类中现有的注释标记,而不是使用属性,而且在我看来,我不知道为什么微软不支持这个,因为它看起来很明显应该存在!

First, turn on XML Documentation file: Project Properties->Build->XML documentation file->App_Data\YourProjectName.XML首先,开启XML文档文件:Project Properties->Build->XML documentation file->App_Data\YourProjectName.XML

Second, include the file as an embedded resource.其次,将文件作为嵌入式资源包含在内。 Build your project, go to App_Data, show hidden files and include the XML file that was generated.构建您的项目,go 到 App_Data,显示隐藏文件并包含生成的 XML 文件。 Select embedded resource and Copy if newer (this is optional, you could specify the path explicitly but in my opinion this is cleaner). Select 嵌入资源,如果更新则复制(这是可选的,您可以明确指定路径,但我认为这更清晰)。 Note, you must use this method as the markup is not present in the assembly, and will save you from locating where your XML is stored.请注意,您必须使用此方法,因为标记不存在于程序集中,并且可以避免您定位 XML 的存储位置。

Here is the code implementation which is a modified version of the accepted answer:这是代码实现,它是已接受答案的修改版本:

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;
    }
}

And the class that gets the comment markup from the visual studio generated XML documentation file:而从 visual studio 中获取注释标记的 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));
    }

    /// <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
}

can you not use the ExceuteSqlCommand method.你不能使用ExceuteSqlCommand方法吗? Here, you can explicitly define whatever meta property you want to add in you Table.在这里,您可以明确定义要添加到表中的任何元属性。

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

thank you Mr.Mahmoodvcs for the great solution.感谢Mr.Mahmoodvcs提供的出色解决方案。 allow me to modify it just replace "DisplayAttribute" with "DescriptionAttribute" insted of using:请允许我修改它,只需将“DisplayAttribute”替换为“DescriptionAttribute”而不是使用:

[Display(Name="Description here")]

you will use:你将使用:

[Description("Description here")]

so it includes the table as well.所以它也包括表格。

    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;
    }
}

While the question is about EF4, this answer targets EF6, which should be appropriate given the passed time since the question was asked.虽然问题是关于 EF4 的,但这个答案针对的是 EF6,考虑到自提出问题以来经过的时间,这应该是合适的。

I think the Comments belong in the Migration Up and Down methods rather than some Seed method.我认为 Comments 属于 Migration UpDown方法,而不是某种Seed方法。

So, as suggested by @MichaelBrown, start with enabling XML Documentation output and include the documentation file as Embedded Resource in your project.因此,正如@MichaelBrown 所建议的那样,首先启用 XML 文档 output 并将文档文件作为嵌入式资源包含在您的项目中。

Then, lets turn the comments into a table/column annotation by using a Convention .然后,让我们使用Convention将注释转换为表/列注释。 There are some tweaks to be made for things like multiline comments and getting rid of excessive whitespace.对于多行注释和去除过多的空格等问题,需要进行一些调整。

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

Register the convention in the OnModelCreating method.OnModelCreating方法中注册约定。

Expected Result: When a new Migration is created, the comments will be included as annotations like预期结果:创建新迁移时,评论将作为注释包含在内,例如

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

Moving on to the second part: adjust the SQL generator to create comments from annotations.继续第二部分:调整 SQL 生成器以从注释创建评论。

This one is for Oracle, but MS Sql should be very similar这个是给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}'");
                        }
                    }
                }
            }
        }
    }
}

In the DbMigrationsConfiguration constructor, register the new code generator (again, this is oracle specific but will be similar for other SQL providers)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));
    }
    // ...

Expected result: The comment annotations from the Up and Down methods are translated to SQL statements that alter the comments in database.预期结果:来自UpDown方法的评论注释被翻译成 SQL 语句,这些语句改变了数据库中的评论。

Now in 2023 I have updated the solution provided by Mahmood Dehghan to.Net 6 and Entity Framework Core 6 here with some minor modifications to include attributes from base class (if not exists in the main class) and also adding extended property to the table.现在,在 2023 年,我已将 Mahmood Dehghan 提供的解决方案更新到 .Net 6 和 Entity Framework Core 6 here ,并进行了一些小修改,以包含来自基类 class 的属性(如果主类中不存在),并向表中添加了扩展属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用迁移将描述添加到 Entity Framework Core 代码优先中的列? - How to add description to columns in Entity Framework Core code-first using migrations? 实体框架代码第一次迁移 - Entity Framework Code First Migrations 实体框架6迁移:首先使用包含列代码创建索引 - Entity Framework 6 migrations : create index with include columns code first 使用Entity Framework Code First 4.3创建存储库 - Creating Repository Using Entity Framework Code First 4.3 实体框架 4.3 迁移错误 - Entity Framework 4.3 migrations error 实体框架4.3代码首先是数据库命名 - Entity Framework 4.3 code first database naming 使用Entity Framework代码优先迁移来更改数据库表名称 - Change a database table name using Entity Framework code first migrations 将.Net成员资格提供程序与Entity Framework 4.3迁移结合使用 - Using .Net Membership Provider in conjunction with Entity Framework 4.3 migrations 实体框架:如何在“代码优先”迁移中禁用模型兼容性检查 - Entity Framework: How to disable model compatibility check in Code First Migrations 如何在实体框架代码优先迁移中设置小数精度和小数位数 - How to set decimal precision and scale in entity framework code first migrations
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM