26

最初に、明示的な移行でEntityFramework4.3.1コードを使用しています。エンティティ構成クラスまたは移行のいずれかに列の説明を追加して、SQLサーバー(2008 R2など)の列の説明になるようにするにはどうすればよいですか?

またはプロシージャ呼び出しを移行トランザクション内のSQL移行操作としてDbMigration登録し、移行メソッドでテーブルを作成した後にその拡張機能を呼び出すクラスの拡張メソッドを作成できることはわかっています。しかし、私がまだ発見していないエレガントな作り付けの方法はありますか?移行の変更検出ロジックが取得して、スキャフォールド移行で適切なメソッド呼び出しを生成できる属性があると便利です。sp_updateextendedpropertysp_addextendedpropertyUp

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")]、エンティティ クラス プロパティの属性を使用して説明を設定します。

バグを報告するか、改善を提案してください。

おかげで

私は他の人からこれらのコードを使用しましたが、感謝したいと思います:

列の説明の追加

クラスがジェネリック クラスから派生しているかどうかを確認する

Entity Framework MetaData からデータベース テーブル名を取得する

変数の型をパラメーターとして使用する C# のジェネリック

于 2013-09-26T09:27:34.723 に答える
10

現在の回答に非常に満足していることに注意してください(ただし、作業の小道具です!)、属性を使用する代わりに、クラスで既存のコメントマークアップをプルする方法が必要でした。そして、私の意見では、マイクロソフトがこれをサポートしなかった理由がわかりません。

まず、XML ドキュメント ファイルをオンにします: [プロジェクト プロパティ] -> [ビルド] -> [XML ドキュメント ファイル] -> [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を対象としています。これは、質問が出されてからの経過時間を考えると適切なはずです。

コメントは、何らかの方法ではなく、移行Upと方法に属していると思います。DownSeed

そのため、@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")
                    },
                }),
// ...

2 番目の部分に進みます。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 に答える