Comment ajouter une description aux colonnes dans le cadre D'Entity 4.3 coder d'abord en utilisant migrations?

J'utilise le code Entity Framework 4.3.1 d'abord avec des migrations explicites. Comment ajouter des descriptions pour les colonnes soit dans les classes de configuration des entités ou dans les migrations, de sorte qu'elles finissent par être la description d'une colonne dans SQL server (par exemple 2008 R2)?

je sais que je peux probablement écrire une méthode d'extension pour l' DbMigration classe qui permettrait d'inscrire l' sp_updateextendedproperty ou sp_addextendedproperty appel de procédure comme une opération de migration sql à l'intérieur de la transaction de migration et appeler cette extension après création de la table dans la migration Up méthode. Mais y a-t-il une élégante construction que je n'ai pas encore découverte? Ce serait bien d'avoir un attribut sur lequel la logique de détection de changement des migrations peut s'appuyer et générer des appels de méthode appropriés dans la migration scaffoldée.

19
demandé sur Perception 2012-04-10 02:18:39

5 réponses

j'en avais besoin aussi. Alors j'ai passé une journée et voilà:

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

Comment Utiliser

Dans votre Migrations/Configuration.cs fichier, ajoutez ceci à la fin de l' Seed méthode:

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

puis dans le Gestionnaire de paquets, tapez Console update-database et appuyez sur Entrée. C'est tout.

le code utilise [Display(Name="Description here")] attribut sur les propriétés de votre classe entity pour définir la description.

veuillez signaler tout bug ou suggérer amélioration.

merci à

j'ai utilisé ce code d'autres personnes et je veux dire merci:

ajout d'une description de colonne

Vérifier si une classe est dérivée d'une classe générique

Obtenir la Table de Base de données Nom de l'Entité Cadre des Métadonnées

Génériques en C#, à l'aide du type d'une variable en paramètre

10
répondu Mahmoodvcs 2017-05-23 12:00:14

notez plutôt satisfait de la réponse actuelle (mais les accessoires pour le travail!), Je voulais un moyen de tirer le commentaire existant balisage dans mes classes au lieu d'utiliser les attributs. Et à mon avis, Je ne sais pas pourquoi L'enfer de Microsoft n'a pas soutenu ceci comme il semble évident qu'il devrait être là!

tout d'abord, activez le fichier de Documentation XML: Propriétés du projet->Build->fichier de documentation XML->App_Data\YourProjectName.XML

dans un Deuxième temps, inclure le fichier en tant que ressource incorporée. Construire votre projet, allez à App_Data, montrez les fichiers cachés et incluez le fichier XML qui a été généré. Sélectionnez la ressource intégrée et copiez si plus récente (c'est facultatif, vous pouvez spécifier le chemin explicitement mais à mon avis c'est plus propre). Notez que vous devez utiliser cette méthode car le markup n'est pas présent dans l'assemblage, et vous évitera de localiser où votre XML est stocké.

Voici l'implémentation du code qui est une version modifiée de la réponse acceptée:

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

Et la classe qui reçoit le Markup commentaire du fichier de documentation XML généré par visual studio:

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
}
4
répondu Michael Brown 2015-03-24 03:40:10

Vous ne pouvez pas utiliser le ExceuteSqlCommand méthode. Ici, vous pouvez définir explicitement n'importe quelle propriété meta que vous voulez ajouter dans votre Table.

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

2
répondu Som Poddar 2013-04-02 19:00:05

merci Mr. Mahmoodvcs pour la grande solution. permettez-moi de le modifier juste remplacer "DisplayAttribute" par "DescriptionAttribute"" insted de l'aide :

[Display(Name="Description here")]

vous allez utiliser :

[Description("Description here")]

donc il inclut la table.

    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;
    }
}
0
répondu Abdullah 2018-03-02 21:03:01

bien que la question porte sur EF4, cette réponse vise EF6, ce qui devrait être approprié compte tenu du temps écoulé depuis que la question a été posée.

je pense que les Commentaires appartiennent à la Migration Up et Down méthodes plutôt que certaines Seed méthode.

donc, comme suggéré par @MichaelBrown, commencez par activer la sortie de Documentation XML et incluez le fichier de documentation comme ressource intégrée dans votre projet.

alors, tournons les commentaires dans une annotation de tableau / colonne en utilisant un Convention. Il ya quelques modifications à faire pour des choses comme les commentaires multilignes et se débarrasser de l'espace blanc excessif.

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

Enregistrer la convention dans le OnModelCreating méthode.

Résultat Prévu: Lorsqu'une nouvelle Migration est créée, les commentaires seront inclus sous forme d'annotations comme

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

passons à la deuxième partie: ajustez le générateur SQL pour créer des commentaires à partir annotation.

celui-ci est pour Oracle, mais MS Sql devrait être très similaire

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}'");
                        }
                    }
                }
            }
        }
    }
}

Dans le DbMigrationsConfiguration constructeur, enregistrez le nouveau générateur de code (encore une fois, c'est spécifique à oracle mais sera similaire pour les autres fournisseurs 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));
    }
    // ...

résultat escompté: les annotations de commentaires de la Up et Down les méthodes sont traduites en énoncés SQL qui modifient les commentaires dans la base de données.

0
répondu grek40 2018-06-29 11:14:49