如何将 MS_DESCRIPTION 属性添加到 M:N 连接表中的列?

Posted

技术标签:

【中文标题】如何将 MS_DESCRIPTION 属性添加到 M:N 连接表中的列?【英文标题】:How to add MS_DESCRIPTION property to column in a M:N join table? 【发布时间】:2021-02-27 07:10:53 【问题描述】:

参考我之前的问题:

How do you add column description for a foreign key in another table utilizing EF 6?

还有这个之前的帖子:

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

基于上述参考,我如何为多对多关系插入扩展属性(例如 MS_Description) EF 6 会在后台自动创建连接表吗?

仅供参考,为您提供更多参考资料:

Towards the Self-Documenting SQL Server Database

SQL Server extended properties

sp_addextendedproperty (Transact-SQL)

【问题讨论】:

【参考方案1】:

好的,是的,可以通过迁移来实现。这绝对是对 EF 工作方式的一次有趣的深入了解,因为我倾向于避免迁移。

第一个线索在这里:(https://karatejb.blogspot.com/2018/09/entity-framework-6-code-first-6-add-or.html) 使用 EF 读写 MS_Description。从那里开始,可以为迁移启用此功能。

对于包含两个实体 FidgetSpinnerDbContext,其中每个都是多对多,我想要一个由 EF 生成的 FidgetSpinner 表:

[Table("Fidgets")]
public class Fidget

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int FidgeId  get; set; 
    public string Name  get; set; 

    public virtual ICollection<Spinner> Spinners  get; set;  = new List<Spinner>();


[Table("Spinners")]
public class Spinner

    [Key]
    public int SpinnerId  get; set; 
    public string Name  get; set; 

    public virtual ICollection<Fidget> Fidgets  get; set;  = new List<Fidget>();

然后映射到指定我的 FidgetSpinner 表:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Fidget>()
            .HasMany(x => x.Spinners)
            .WithMany(x => x.Fidgets)
            .Map(x =>
            
                x.MapLeftKey("FidgetId"); 
                x.MapRightKey("SpinnerId"); 
                x.ToTable("FidgetSpinners"); 
            );
    

很简单,但是我们想在这个生成的表的 FidgetId 和 SpinnerId 列中添加描述。有效的解决方案是利用表注释和迁移:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        base.OnModelCreating(modelBuilder);

        string descriptions = JsonConvert.SerializeObject(new[]
        
            new KeyValuePair<string, string>("SpinnerId", "FK Reference to a Spinner"),
            new KeyValuePair<string, string>("FidgetId", "FK Reference to a Fidget")
        );

        modelBuilder.Entity<Fidget>()
            .HasMany(x => x.Spinners)
            .WithMany(x => x.Fidgets)
            .Map(x =>
            
                x.MapLeftKey("FidgetId"); 
                x.MapRightKey("SpinnerId"); 
                x.ToTable("FidgetSpinners"); 
                x.HasTableAnnotation("Descriptions", descriptions);
            );
    

在这里,我将我的注释附加为 JSON 字符串,其中包含我要添加的列名称和描述的 KeyValuePairs。这可以很容易地成为一个自定义类型的容器。但是,您发送的任何内容都必须可序列化为字符串。

接下来将创建一个 SQL 生成器,它将响应创建表操作并检查我们的描述表注释,然后对于任何匹配的列,使用 sp_addextendedproperty 附加它们的描述:

public class DescriptionMigrationSqlGenerator : SqlServerMigrationSqlGenerator

    protected override void Generate(CreateTableOperation createTableOperation)
    
        base.Generate(createTableOperation);
        if (createTableOperation.Annotations.TryGetValue("Descriptions", out object descriptionData))
        
            try
            
                var descriptionValues = JsonConvert.DeserializeObject<KeyValuePair<string, string>[]>(descriptionData.ToString());

                foreach (var descriptionValue in descriptionValues)
                
                    var column = createTableOperation.Columns.SingleOrDefault(x => x.Name.ToLower() == descriptionValue.Key.ToLower());
                    if (column != null)
                    
                        var tableNameParts = createTableOperation.Name.Split('.');
                        var schemaName = tableNameParts.First();
                        var tableName = tableNameParts.Last();
                        var statement = string.Format(@"EXEC sp_addextendedproperty @name=N'MS_Description', @value = N'0', @level0type=N'Schema', @level0name= 1, @level1type=N'Table', @level1name=2, @level2type=N'Column', @level2name=3",
                            descriptionValue.Value, schemaName, tableName, column.Name);
                        Statement(statement);
                    
                
            
            catch   // TODO: Handle situation where invalid description annotation provided. (I.e. not provided collection of key value pairs...
        
    

这只是一个模板,应该扩展异常处理,例如处理无效值或请求不存在的列的情况。 (目前被忽略)createTableOperation.Name 包含架构名称,因此我们将其拆分以获取存储过程调用的架构和表名称(非常粗略,建议对此进行更好的检查)。

最后一步是使用迁移配置注册此 SQL 生成器:

internal sealed class Configuration : DbMigrationsConfiguration<TestDbContext>

    public Configuration()
    
        AutomaticMigrationsEnabled = true;
        SetSqlGenerator("System.Data.SqlClient", new DescriptionMigrationSqlGenerator());
    

这假定自动生成,或者您可以将其设置为手动生成。

现在,当迁移创建我们用描述注释的连接表时,我们请求的描述被添加到 DB 列中。

【讨论】:

【参考方案2】:

感谢Steve Py 的回答,它让我产生了创建自己的属性并与question 中的先前解决方案相结合的想法。

以前的解决方案很容易适用于 1:1,并且需要完全定义的关系(请参阅here)来处理 1:n 关系。由于 EF 6 自动为 m:n 关系创建连接表,因此该解决方案将抛出异常,指出外键列不存在,因为它正在查找错误的表。

我实施的解决方案是给定解决方案的另一种变体,它使用自定义属性来定义描述,并且还需要为 m:n 关系定义外键和连接表.

我相信那里有更优雅的答案,但在它发布之前,我正在实现下面的代码。

以下是我创建的自定义属性:

public class CustomTblDesc : Attribute

    private string desc = "";

    public CustomTblDesc(string description)
    
        desc = description;
    

    public string Description  get  return desc;  


public class CustomColDesc : Attribute

    private string desc = "";
    private string table = "";
    private string propName = "";

    public CustomColDesc(string description)
    
        desc = description;
    

    public CustomColDesc(string description, string tableName)
    
        desc = description;
        table = tableName;
    

    public CustomColDesc(string description, string tableName, string linkedTablePropertyName)
    
        desc = description;
        table = tableName;
        propName = linkedTablePropertyName;
    

    public string Description  get  return desc;  
    public string LinkedTable  get  return table;  
    public string LinkedTablePropertyName  get  return propName; 

下面是我根据question的解决方案修改的代码:

private void SetTableDescriptions(Type tableType) string tableName = tableType.Name;

    // -- Set table description

    CustomTblDesc tblDesc = null;

    var custDescs = tableType.GetCustomAttributes(typeof(CustomTblDesc), false);
    if (custDescs != null && custDescs.Length > 0)
    
        tblDesc = custDescs[0] as CustomTblDesc;
        SetTableDescription(tableName, tblDesc.Description);
    

    // -- Set column description

    foreach (var prop in tableType.GetProperties(System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
    
        if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) 
            continue;
        else
        
            CustomColDesc colDesc = null;

            custDescs = prop.GetCustomAttributes(typeof(CustomColDesc), false);
            if (custDescs != null && custDescs.Length > 0)
            
                colDesc = custDescs[0] as CustomColDesc;
             
                if (string.IsNullOrEmpty(colDesc.LinkedTable))
                    SetColumnDescription(tableName, prop.Name, colDesc.Description);
                else
                    SetColumnDescription(colDesc.LinkedTable, colDesc.LinkedTablePropertyName, colDesc.Description);
            
        
    

【讨论】:

以上是关于如何将 MS_DESCRIPTION 属性添加到 M:N 连接表中的列?的主要内容,如果未能解决你的问题,请参考以下文章

PowerDesigner15.1给自定义架构表字段添加MS_Description出错

SQL SERVER sys.sp_addextendedproperty 添加扩展属性

sql server 添加表注释字段注释

从扩展属性中获取元数据 (SQL Server)

sql2000添加表注释,列注释 及修改 删除 注释

●sql语句-添加表和字段的说明