请注意对当前答案非常满意(但工作的道具!),我想要一种方法来提取我的类中的现有注释标记,而不是使用属性。在我看来,我不知道为什么微软不支持这个,因为它似乎很明显应该在那里!
首先,打开XML Documentation文件:Project Properties->Build->XML documentation file->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
}