如何将 MS_DESCRIPTION 属性 添加到 M:N 连接 table 中的列?
How to add MS_DESCRIPTION property to column in a M:N join table?
回到我之前的问题:
How do you add column description for a foreign key in another table utilizing EF 6?
和之前的 post:
How to add description to columns in Entity Framework 4.3 code first using migrations?
根据上述参考资料,我如何插入一个扩展的属性(例如MS_Description)用于多对多关系 其中 EF 6 会在后台自动创建连接 table?
仅供参考,更多参考资料供您参考:
Towards the Self-Documenting SQL Server Database
好的,是的,可以通过迁移实现。这绝对是对 EF 工作原理的一次有趣探索,因为迁移是我倾向于避免的事情。
第一个线索在这里:(https://karatejb.blogspot.com/2018/09/entity-framework-6-code-first-6-add-or.html) 使用 EF 读写 MS_Description。从那里开始,这是一个为迁移启用它的案例。
对于包含两个实体 Fidget
和 Spinner
的 DbContext
,其中每个实体都是多对多,我想要一个 FidgetSpinner
table 由 EF 生成:
[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 table:
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");
});
}
非常简单,但我们想将描述添加到此生成的 table 上的 FidgetId 和 SpinnerId 列。有效的解决方案是利用 Table 注释和迁移:
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 生成器,它将响应创建 Table 操作并检查我们的描述 Table 注释,然后对于任何匹配的列,附加他们的使用 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 包含架构名称,因此我们将其拆分以获得存储过程调用的架构和 table 名称(真的很粗糙,建议对此进行一些更好的检查)。
最后一步是使用迁移配置注册此 SQL 生成器:
internal sealed class Configuration : DbMigrationsConfiguration<TestDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
SetSqlGenerator("System.Data.SqlClient", new DescriptionMigrationSqlGenerator());
}
}
这假定自动生成,或者您可以将其设置为手动生成。
现在,当迁移创建我们用描述注释的连接 table 时,我们请求的描述将添加到数据库列中。
感谢Steve Py's answer, it gave me the idea to create my own attributes and to combine with the previous solution from this question。
以前的解决方案适用于 1:1 并且需要完全定义的关系(参见 here)1:n[=33= 】 关系。由于 EF 6 自动为 m:n 关系创建 join-table,该解决方案将引发异常,说明外键列不存在,因为它正在寻找错误 table.
我在我这边实施的解决方案是给定解决方案的另一种变体,它使用自定义属性来定义描述,还额外需要定义外键并为 加入 table 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)
{
字符串 table名称 = 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);
}
}
}
}
回到我之前的问题:
How do you add column description for a foreign key in another table utilizing EF 6?
和之前的 post:
How to add description to columns in Entity Framework 4.3 code first using migrations?
根据上述参考资料,我如何插入一个扩展的属性(例如MS_Description)用于多对多关系 其中 EF 6 会在后台自动创建连接 table?
仅供参考,更多参考资料供您参考:
Towards the Self-Documenting SQL Server Database
好的,是的,可以通过迁移实现。这绝对是对 EF 工作原理的一次有趣探索,因为迁移是我倾向于避免的事情。
第一个线索在这里:(https://karatejb.blogspot.com/2018/09/entity-framework-6-code-first-6-add-or.html) 使用 EF 读写 MS_Description。从那里开始,这是一个为迁移启用它的案例。
对于包含两个实体 Fidget
和 Spinner
的 DbContext
,其中每个实体都是多对多,我想要一个 FidgetSpinner
table 由 EF 生成:
[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 table:
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");
});
}
非常简单,但我们想将描述添加到此生成的 table 上的 FidgetId 和 SpinnerId 列。有效的解决方案是利用 Table 注释和迁移:
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 生成器,它将响应创建 Table 操作并检查我们的描述 Table 注释,然后对于任何匹配的列,附加他们的使用 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 包含架构名称,因此我们将其拆分以获得存储过程调用的架构和 table 名称(真的很粗糙,建议对此进行一些更好的检查)。
最后一步是使用迁移配置注册此 SQL 生成器:
internal sealed class Configuration : DbMigrationsConfiguration<TestDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
SetSqlGenerator("System.Data.SqlClient", new DescriptionMigrationSqlGenerator());
}
}
这假定自动生成,或者您可以将其设置为手动生成。
现在,当迁移创建我们用描述注释的连接 table 时,我们请求的描述将添加到数据库列中。
感谢Steve Py's answer, it gave me the idea to create my own attributes and to combine with the previous solution from this question。
以前的解决方案适用于 1:1 并且需要完全定义的关系(参见 here)1:n[=33= 】 关系。由于 EF 6 自动为 m:n 关系创建 join-table,该解决方案将引发异常,说明外键列不存在,因为它正在寻找错误 table.
我在我这边实施的解决方案是给定解决方案的另一种变体,它使用自定义属性来定义描述,还额外需要定义外键并为 加入 table 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) { 字符串 table名称 = 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);
}
}
}
}