演练:扩展数据库项目生成以生成模型统计信息
可以创建生成参与者,以在生成数据库项目时执行自定义操作。 在本演练中,您将创建一个名为 ModelStatistics 的生成参与者,该参与者在您生成数据库项目时从数据库模型输出统计信息。 因为此生成参与者在您生成时接受参数,所以需要执行一些附加步骤。
在本演练中,您将完成以下主要任务:
创建生成参与者
安装生成参与者
测试生成参与者
系统必备
您需要以下组件来完成本演练:
必须已安装 Visual Studio 2010 高级专业版或 Visual Studio 2010 旗舰版。
必须拥有包含数据库对象的数据库项目
提示
本演练专供已熟悉 Visual Studio 高级专业版数据库功能的用户使用。 同时,您还应熟悉基本的 Visual Studio 概念,例如如何创建类库以及如何使用代码编辑器向类添加代码。
创建生成参与者
若要创建生成参与者,必须执行以下任务:
创建类库项目并添加所需引用
定义一个名为 ModelStatistics 的类,该类继承自 BuildContributor
重写 OnPopulateArguments 和 OnExecute 方法
添加几个私有帮助器方法
生成结果程序集
提示
当使用 MSBuild 生成数据库项目时,此参与者将仅进行输出。 默认情况下将会关闭报告,但可以通过在 MSBuild 命令行上提供一个属性来重写它。 有关如何启用输出窗口中的输出的示例,请参见演练:扩展数据库项目部署以分析部署计划。
创建类库项目
创建一个名为 MyBuildContributor 的 Visual Basic 或 Visual C# 类库项目。
在解决方案资源管理器中,右击该项目节点,然后单击**“添加引用”**。
单击**“.NET”**选项卡。
突出显示**“Microsoft.Data.Schema”和“Microsoft.Data.Schema.Sql”项,然后单击“确定”**。
接下来,开始向类中添加代码。
定义 ModelStatistics 类
在代码编辑器中,更新 class1.cs 文件以匹配以下 using 或 Imports 语句:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.Data.Schema; using Microsoft.Data.Schema.Build; using Microsoft.Data.Schema.Extensibility; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.Sql;
Imports System Imports System.Collections.Generic Imports System.IO Imports System.Linq Imports System.Xml.Linq Imports Microsoft.Data.Schema Imports Microsoft.Data.Schema.Build Imports Microsoft.Data.Schema.Extensibility Imports Microsoft.Data.Schema.SchemaModel Imports Microsoft.Data.Schema.Sql
更新类定义以匹配以下内容:
/// <summary> /// The ModelStatistics class demonstrates /// how you can create a class that inherits BuildContributor /// to perform actions when you build a database project. /// </summary> [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))] public sealed class ModelStatistics : BuildContributor { }
''' <summary> ''' The ModelStatistics class demonstrates ''' how you can create a class that inherits BuildContributor ''' to perform actions when you build a database project. ''' </summary> <DatabaseSchemaProviderCompatibility(GetType(SqlDatabaseSchemaProvider))> _ Public NotInheritable Class ModelStatistics Inherits BuildContributor End Class
现在,您已定义了生成参与者,并使用特性指示此参与者与从 SqlDatabaseSchemaProvider 继承的所有数据库架构提供程序都兼容。
接下来,添加以下成员。 您将使用这些成员使此提供程序可以接受命令行生成参数:
private const string GenerateModelStatistics = "GenerateModelStatistics"; private const string SortModelStatisticsBy = "SortModelStatisticsBy"; private const string GenerateModelStatisticsVariable = "$(" + GenerateModelStatistics + ")"; private const string SortModelStatisticsByVariable = "$(" + SortModelStatisticsBy + ")"; private enum SortBy { None, Name, Value }; private static Dictionary<string, SortBy> SortByMap = new Dictionary<string, SortBy>(StringComparer.OrdinalIgnoreCase) { { "none", SortBy.None }, { "name", SortBy.Name }, { "value", SortBy.Value }, }; private SortBy _sortBy = SortBy.None;
Private Const GenerateModelStatistics As String = "GenerateModelStatistics" Private Const SortModelStatisticsBy As String = "SortModelStatisticsBy" Private Const GenerateModelStatisticsVariable As String = "$(" & GenerateModelStatistics & ")" Private Const SortModelStatisticsByVariable As String = "$(" & SortModelStatisticsBy & ")" Private Enum SortBy None Name Value End Enum '' SortByMap maps the command-line parameter string values to the enumeration values Private Shared SortByMap As New Dictionary(Of String, SortBy)(StringComparer.OrdinalIgnoreCase) Private _sortBy As SortBy = SortBy.None
这些成员使用户可以使用 GenerateModelStatistics 选项指定是否应生成统计信息,并通过指定 SortModelStatisticsBy 选项来指定应如何对统计信息进行排序。
接下来,重写 OnPopulateArguments 方法以生成要传递给生成参与者的参数列表。
重写 OnPopulateArguments
将下面的重写方法添加到 ModelStatistics 类:
/// <summary> /// Override the OnPopulateArgument method to build a list of arguments from the input /// configuration information. /// </summary> protected override IList<ContributorArgumentConfiguration> OnPopulateArguments() { List<ContributorArgumentConfiguration> args = new List<ContributorArgumentConfiguration>(); args.Add(new ContributorArgumentConfiguration( name: GenerateModelStatistics, value: GenerateModelStatisticsVariable, condition: null)); args.Add(new ContributorArgumentConfiguration( name: SortModelStatisticsBy, value: SortModelStatisticsByVariable, condition: null)); return args; }
''' <summary> ''' Override the OnPopulateArgument method to build a list of arguments from the input ''' configuration information. ''' </summary> Protected Overrides Function OnPopulateArguments() As System.Collections.Generic.IList(Of Microsoft.Data.Schema.Build.ContributorArgumentConfiguration) Dim args As List(Of ContributorArgumentConfiguration) = New List(Of ContributorArgumentConfiguration) args.Add(New ContributorArgumentConfiguration(name:=GenerateModelStatistics, _ value:=GenerateModelStatisticsVariable, _ condition:=Nothing)) args.Add(New ContributorArgumentConfiguration(name:=SortModelStatisticsBy, _ value:=SortModelStatisticsByVariable, _ condition:=Nothing)) Return MyBase.OnPopulateArguments() End Function
生成两个 ContributorArgumentConfiguration 对象,并将这两个对象添加到参数列表。
接下来,重写 OnExecute 方法,以添加要在生成数据库项目时运行的代码。
重写 OnExecute
将下面的方法添加到 ModelStatistics 类:
/// <summary> /// Override the OnExecute method to perform actions when you build a database project. /// </summary> protected override void OnExecute(BuildContributorContext context, ErrorManager errorsContainer) { // handle related arguments, passed in as part of // the context information. bool generateModelStatistics; ParseArguments(context.Arguments, errorsContainer, out generateModelStatistics); // Only generate statistics if requested to do so if (generateModelStatistics) { // First, output model-wide information, such // as the type of database schema provider (DSP) // and the collation. List<DataSchemaError> args = new List<DataSchemaError>(); args.Add(new DataSchemaError(" ", ErrorSeverity.Message)); args.Add(new DataSchemaError("Model Statistics:", ErrorSeverity.Message)); args.Add(new DataSchemaError("=================", ErrorSeverity.Message)); args.Add(new DataSchemaError(" ", ErrorSeverity.Message)); errorsContainer.Add(args, ErrorManager.DefaultCategory); var model = context.Model; // Start building up the XML that will later // be serialized. var xRoot = new XElement("ModelStatistics"); SummarizeModelInfo(model, xRoot, errorsContainer); // First, count the elements that are contained // in this model. var elements = model.GetElements(typeof(IModelElement), ModelElementQueryFilter.Internal); Summarize(elements, element => element.ElementClass.ClassName, "Internal Elements", xRoot, errorsContainer); // Now, count the elements that are defined in // another model. Examples include built-in types, // roles, filegroups, assemblies, and any // referenced objects from another database. elements = model.GetElements(typeof(IModelElement), ModelElementQueryFilter.External); Summarize(elements, element => element.ElementClass.ClassName, "External Elements", xRoot, errorsContainer); // Now, count the number of each type // of relationship in the model. SurveyRelationships(model, xRoot, errorsContainer); // Count the various types of annotations // in the model. var annotations = model.GetAllAnnotations(); Summarize(annotations, anno => anno.AnnotationClass.ClassName, "Annotations", xRoot, errorsContainer); // finally, count any types of custom data // defined for the model. var customData = model.GetCustomData(); Summarize(customData, custom => custom.Category, "Custom Data", xRoot, errorsContainer); // Determine where the user wants to save // the serialized XML file. string outDir; if (context.Arguments.TryGetValue("OutDir", out outDir) == false) { outDir = "."; } var filePath = Path.Combine(outDir, "ModelStatistics.xml"); // Save the XML file and tell the user // where it was saved. xRoot.Save(filePath); DataSchemaError resultArg = new DataSchemaError("Result was saved to " + filePath, ErrorSeverity.Message); errorsContainer.Add(resultArg, ErrorManager.BuildCategory); } }
''' <summary> ''' Override the OnExecute method to perform actions when you build a database project. ''' </summary> Protected Overloads Overrides Sub OnExecute(ByVal context As BuildContributorContext, ByVal errorsContainer As ErrorManager) ' handle related arguments, passed in as part of ' the context information. Dim generateModelStatistics As Boolean ParseArguments(context.Arguments, errorsContainer, generateModelStatistics) ' Only generate statistics if requested to do so If generateModelStatistics Then ' First, output model-wide information, such ' as the type of database schema provider (DSP) ' and the collation. Dim args As New List(Of DataSchemaError)() args.Add(New DataSchemaError(" ", ErrorSeverity.Message)) args.Add(New DataSchemaError("Model Statistics:", ErrorSeverity.Message)) args.Add(New DataSchemaError("=================", ErrorSeverity.Message)) args.Add(New DataSchemaError(" ", ErrorSeverity.Message)) errorsContainer.Add(args, ErrorManager.DefaultCategory) Dim model = context.Model ' Start building up the XML that will later ' be serialized. Dim xRoot = New XElement("ModelStatistics") SummarizeModelInfo(model, xRoot, errorsContainer) ' First, count the elements that are contained ' in this model. Dim elements = model.GetElements(GetType(IModelElement), ModelElementQueryFilter.Internal) Summarize(elements, Function(element) element.ElementClass.ClassName, "Internal Elements", xRoot, errorsContainer) ' Now, count the elements that are defined in ' another model. Examples include built-in types, ' roles, filegroups, assemblies, and any ' referenced objects from another database. elements = model.GetElements(GetType(IModelElement), ModelElementQueryFilter.External) Summarize(elements, Function(element) element.ElementClass.ClassName, "External Elements", xRoot, errorsContainer) ' Now, count the number of each type ' of relationship in the model. SurveyRelationships(model, xRoot, errorsContainer) ' Count the various types of annotations ' in the model. Dim annotations = model.GetAllAnnotations() Summarize(annotations, Function(anno) anno.AnnotationClass.ClassName, "Annotations", xRoot, errorsContainer) ' finally, count any types of custom data ' defined for the model. Dim customData = model.GetCustomData() Summarize(customData, Function([custom]) [custom].Category, "Custom Data", xRoot, errorsContainer) ' Determine where the user wants to save ' the serialized XML file. Dim outDir As String If context.Arguments.TryGetValue("OutDir", outDir) = False Then outDir = "." End If Dim filePath = Path.Combine(outDir, "ModelStatistics.xml") ' Save the XML file and tell the user ' where it was saved. xRoot.Save(filePath) Dim resultArg As New DataSchemaError("Result was saved to " & filePath, ErrorSeverity.Message) errorsContainer.Add(resultArg, ErrorManager.BuildCategory) End If End Sub
会向 OnExecute 方法传递一个 BuildContributorContext 对象,该对象提供对任何指定参数、数据库模型、生成属性和扩展文件的访问。 在此示例中,我们检索模型,然后调用帮助器函数以输出有关模型的信息。 还会向该方法传递一个 ErrorManager,用于报告发生的任何错误。
其他相关类型和方法包括:DataSchemaModel、ModelStore、GetElements、GetAllAnnotations、GetCustomData 和 ModelElement。
接下来,定义检查模型详细信息的帮助器方法。
添加生成统计信息的帮助器方法
首先,通过添加下面的代码来添加四个帮助器方法的主干:
/// <summary> /// Examine the arguments provided by the user /// to determine if model statistics should be generated /// and, if so, how the results should be sorted. /// </summary> private void ParseArguments(IDictionary<string, string> arguments, ErrorManager errorsContainer, out bool generateModelStatistics) { } /// <summary> /// Retrieve the database schema provider for the /// model and the collation of that model. /// Results are output to the console and added to the XML /// being constructed. /// </summary> private static void SummarizeModelInfo(DataSchemaModel model, XElement xContainer, ErrorManager errorsContainer) { } /// <summary> /// For a provided list of model elements, count the number /// of elements for each class name, sorted as specified /// by the user. /// Results are output to the console and added to the XML /// being constructed. /// </summary> private void Summarize<T>(IList<T> set, Func<T, string> groupValue, string category, XElement xContainer, ErrorManager errorsContainer) { } /// <summary> /// Iterate over all model elements, counting the /// styles and types for relationships that reference each /// element /// Results are output to the console and added to the XML /// being constructed. /// </summary> private static void SurveyRelationships(DataSchemaModel model, XElement xContainer, ErrorManager errorsContainer) { } /// <summary> /// Performs the actual output for this contributor, /// writing the specified set of statistics, and adding any /// output information to the XML being constructed. /// </summary> private static void OutputResult<T>(string category, Dictionary<string, T> statistics, XElement xContainer, ErrorManager errorsContainer) { }
''' <summary> ''' This method goes through the provided arguments to the contributor and determines what ''' parameters and parameter values were provided by the user. ''' </summary> Private Sub ParseArguments(ByVal arguments As IDictionary(Of String, String), ByVal errorsContainer As ErrorManager, ByRef generateModelStatistics__1 As Boolean) End Sub ''' <summary> ''' This method collects and outputs information about the model itself. ''' </summary> Private Shared Sub SummarizeModelInfo(ByVal model As DataSchemaModel, ByVal xContainer As XElement, ByVal errorsContainer As ErrorManager) End Sub ''' <summary> ''' This method goes counts the number of elements in specific categories from the provided element list. ''' </summary> Private Sub Summarize(Of T)(ByVal [set] As IList(Of T), ByVal groupValue As Func(Of T, String), ByVal category As String, ByVal xContainer As XElement, ByVal errorsContainer As ErrorManager) End Sub ''' <summary> ''' This method counts the number of relationships of each type that reference elements in the model ''' </summary> Private Shared Sub SurveyRelationships(ByVal model As DataSchemaModel, ByVal xContainer As XElement, ByVal errorsContainer As ErrorManager) End Sub ''' <summary> ''' This method processes the provided data, outputting it to the console and adding the information ''' to the provided XML. ''' </summary> Private Shared Sub OutputResult(Of T)(ByVal category As String, ByVal statistics As Dictionary(Of String, T), ByVal xContainer As XElement, ByVal errorsContainer As ErrorManager) End Sub
定义 ParseArguments 方法的主体
将下面的代码添加到 ParseArguments 方法的主体:
// By default, we don't generate model statistics generateModelStatistics = false; // see if the user provided the GenerateModelStatistics // option and if so, what value was it given. string valueString; arguments.TryGetValue(GenerateModelStatistics, out valueString); if (string.IsNullOrWhiteSpace(valueString) == false) { if (bool.TryParse(valueString, out generateModelStatistics) == false) { generateModelStatistics = false; // The value was not valid from the end user DataSchemaError invalidArg = new DataSchemaError( GenerateModelStatistics + "=" + valueString + " was not valid. It can be true or false", ErrorSeverity.Error); errorsContainer.Add(invalidArg, ErrorManager.BuildCategory); return; } } // Only worry about sort order if the user requested // that we generate model statistics. if (generateModelStatistics) { // see if the user provided the sort option and // if so, what value was provided. arguments.TryGetValue(SortModelStatisticsBy, out valueString); if (string.IsNullOrWhiteSpace(valueString) == false) { SortBy sortBy; if (SortByMap.TryGetValue(valueString, out sortBy)) { _sortBy = sortBy; } else { // The value was not valid from the end user DataSchemaError invalidArg = new DataSchemaError( SortModelStatisticsBy + "=" + valueString + " was not valid. It can be none, name, or value", ErrorSeverity.Error); errorsContainer.Add(invalidArg, ErrorManager.BuildCategory); } } }
' By default, we don't generate model statistics generateModelStatistics__1 = False ' see if the user provided the GenerateModelStatistics ' option and if so, what value was it given. Dim valueString As String = "" arguments.TryGetValue(GenerateModelStatistics, valueString) If String.IsNullOrWhiteSpace(valueString) = False Then If Boolean.TryParse(valueString, generateModelStatistics__1) = False Then generateModelStatistics__1 = False ' The value was not valid from the end user Dim invalidArg As New DataSchemaError((GenerateModelStatistics & "=") + valueString & " was not valid. It can be true or false", ErrorSeverity.[Error]) errorsContainer.Add(invalidArg, ErrorManager.BuildCategory) Exit Sub End If End If If SortByMap.Count = 0 Then '' haven't populated the map yet SortByMap.Add("none", SortBy.None) SortByMap.Add("name", SortBy.Name) SortByMap.Add("value", SortBy.Value) End If ' Only worry about sort order if the user requested ' that we generate model statistics. If generateModelStatistics__1 Then ' see if the user provided the sort option and ' if so, what value was provided. arguments.TryGetValue(SortModelStatisticsBy, valueString) If String.IsNullOrWhiteSpace(valueString) = False Then Dim localSortBy As SortBy If SortByMap.TryGetValue(valueString, localSortBy) Then _sortBy = localSortBy Else ' The value was not valid from the end user Dim invalidArg As New DataSchemaError((SortModelStatisticsBy & "=") + valueString & " was not valid. It can be none, name, or value", ErrorSeverity.[Error]) errorsContainer.Add(invalidArg, ErrorManager.BuildCategory) End If End If End If
相关类型和方法包括:ErrorManager 和 DataSchemaError。
定义 SummarizeModelInfo 方法的主体
将下面的代码添加到 SummarizeModelInfo 方法的主体:
// use a Dictionary to accumulate the information // that will later be output. var info = new Dictionary<string, string>(); // Two things of interest: the database schema // provider for the model, and the language id and // case sensitivity of the collation of that // model info.Add("DSP", model.DatabaseSchemaProvider.GetType().Name); info.Add("Collation", string.Format("{0}({1})", model.Collation.Lcid, model.Collation.CaseSensitive ? "CS" : "CI")); // Output the accumulated information and add it to // the XML. OutputResult("Basic model info", info, xContainer, errorsContainer);
' use a Dictionary to accumulate the information ' that will later be output. Dim info = New Dictionary(Of String, String)() ' Two things of interest: the database schema ' provider for the model, and the language id and ' case sensitivity of the collation of that ' model info.Add("DSP", model.DatabaseSchemaProvider.[GetType]().Name) info.Add("Collation", String.Format("{0}({1})", model.Collation.Lcid, If(model.Collation.CaseSensitive, "CS", "CI"))) ' Output the accumulated information and add it to ' the XML. OutputResult("Basic model info", info, xContainer, errorsContainer)
此处的关键类型和成员包括:DataSchemaModel、ModelStore、DatabaseSchemaProvider 和 ModelCollation。
添加 Summarize 方法的主体
将下面的代码添加到 Summarize 方法的主体:
// Use a Dictionary to keep all summarized information var statistics = new Dictionary<string, int>(); // For each element in the provided list, // count items based on the specified grouping var groups = from item in set group item by groupValue(item) into g select new { g.Key, Count = g.Count() }; // order the groups as requested by the user if (this._sortBy == SortBy.Name) { groups = groups.OrderBy(group => group.Key); } else if (this._sortBy == SortBy.Value) { groups = groups.OrderBy(group => group.Count); } // build the Dictionary of accumulated statistics // that will be passed along to the OutputResult method. foreach (var item in groups) { statistics.Add(item.Key, item.Count); } statistics.Add("subtotal", set.Count); statistics.Add("total items", groups.Count()); // output the results, and build up the XML OutputResult(category, statistics, xContainer, errorsContainer);
' Use a Dictionary to keep all summarized information Dim statistics = New Dictionary(Of String, Integer)() ' For each element in the provided list, ' count items based on the specified grouping Dim groups = From item In [set] _ Group item By groupValue(item)Intog _ Select New () ' order the groups as requested by the user If Me._sortBy = SortBy.Name Then groups = groups.OrderBy(Function(group) group.Key) ElseIf Me._sortBy = SortBy.Value Then groups = groups.OrderBy(Function(group) group.Count) End If ' build the Dictionary of accumulated statistics ' that will be passed along to the OutputResult method. For Each item In groups statistics.Add(item.Key, item.Count) Next statistics.Add("subtotal", [set].Count) statistics.Add("total items", groups.Count()) ' output the results, and build up the XML OutputResult(category, statistics, xContainer, errorsContainer)
同样,代码注释提供相关信息。
添加 SurveyRelationships 方法的主体
将下面的代码添加到 SurveyRelationships 方法的主体:
// get a list that contains all elements in the model var elements = model.GetElements(typeof(IModelElement), ModelElementQueryFilter.All); // We are interested in all relationships that // reference each element. var entries = from element in elements from entry in element.GetReferencedRelationshipEntries() select entry; // initialize our counting buckets var single = 0; var many = 0; var composing = 0; var hierachical = 0; var peer = 0; var reverse = 0; // process each relationship, adding to the // appropriate bucket for style and type. foreach (var entry in entries) { switch (entry.RelationshipClass.ModelRelationshipCardinalityStyle) { case ModelRelationshipCardinalityStyle.Many: ++many; break; case ModelRelationshipCardinalityStyle.Single: ++single; break; default: break; } switch (entry.RelationshipClass.ModelRelationshipType) { case ModelRelationshipType.Composing: ++composing; break; case ModelRelationshipType.Hierarchical: ++hierachical; break; case ModelRelationshipType.Peer: ++peer; break; case ModelRelationshipType.Reverse: // We count these, but reverse relationships // are not actually stored*, so the count // will always be zero. // * - reverse relationships are generated // dynamically when they are accessed. ++reverse; break; default: break; } } // build a dictionary of data to pass along // to the OutputResult method. var stat = new Dictionary<string, int>(); stat.Add("Multiple", many); stat.Add("Single", single); stat.Add("Composing", composing); stat.Add("Hierarchical", hierachical); stat.Add("Peer", peer); // As noted, no need to output the count of reverse // relationships as it will always be zero. //stat.Add("Reverse", reverse); stat.Add("subtotal", entries.Count()); OutputResult("Relationships", stat, xContainer, errorsContainer);
' get a list that contains all elements in the model Dim elements = model.GetElements(GetType(IModelElement), ModelElementQueryFilter.All) ' We are interested in all relationships that ' reference each element. Dim entries = From element In elements _ From entry In element.GetReferencedRelationshipEntries() _ Select entry ' initialize our counting buckets Dim [single] = 0 Dim many = 0 Dim composing = 0 Dim hierachical = 0 Dim peer = 0 Dim reverse = 0 ' process each relationship, adding to the ' appropriate bucket for style and type. For Each entry In entries Select Case entry.RelationshipClass.ModelRelationshipCardinalityStyle Case ModelRelationshipCardinalityStyle.Many many += 1 Exit Select Case ModelRelationshipCardinalityStyle.[Single] [single] += 1 Exit Select Case Else Exit Select End Select Select Case entry.RelationshipClass.ModelRelationshipType Case ModelRelationshipType.Composing composing += 1 Exit Select Case ModelRelationshipType.Hierarchical hierachical += 1 Exit Select Case ModelRelationshipType.Peer peer += 1 Exit Select Case ModelRelationshipType.Reverse ' We count these, but reverse relationships ' are not actually stored*, so the count ' will always be zero. ' * - reverse relationships are generated ' dynamically when they are accessed. reverse += 1 Exit Select Case Else Exit Select End Select Next ' build a dictionary of data to pass along ' to the OutputResult method. Dim stat = New Dictionary(Of String, Integer)() stat.Add("Multiple", many) stat.Add("Single", [single]) stat.Add("Composing", composing) stat.Add("Hierarchical", hierachical) stat.Add("Peer", peer) ' As noted, no need to output the count of reverse ' relationships as it will always be zero. 'stat.Add("Reverse", reverse); stat.Add("subtotal", entries.Count()) OutputResult("Relationships", stat, xContainer, errorsContainer)
代码注释解释了此方法的关键方面。 值得注意的引用类型和方法包括:DataSchemaModel、ModelStore、GetElements 和 ModelElement。
添加 OutputResult 方法的主体
将下面的主体添加到 OutputResult 方法:
var maxLen = statistics.Max(stat => stat.Key.Length) + 2; var format = string.Format("{{0, {0}}}: {{1}}", maxLen); List<DataSchemaError> args = new List<DataSchemaError>(); args.Add(new DataSchemaError(category, ErrorSeverity.Message)); args.Add(new DataSchemaError("-----------------", ErrorSeverity.Message)); // Remove any blank spaces from the category name var xCategory = new XElement(category.Replace(" ", "")); xContainer.Add(xCategory); foreach (var item in statistics) { //Console.WriteLine(format, item.Key, item.Value); var entry = string.Format(format, item.Key, item.Value); args.Add(new DataSchemaError(entry, ErrorSeverity.Message)); // Replace any blank spaces in the element key with // underscores. xCategory.Add(new XElement(item.Key.Replace(' ', '_'), item.Value)); } args.Add(new DataSchemaError(" ", ErrorSeverity.Message)); errorsContainer.Add(args, ErrorManager.BuildCategory);
Dim maxLen = statistics.Max(Function(stat) stat.Key.Length) + 2 Dim format = String.Format("{{0, {0}}}: {{1}}", maxLen) Dim args As New List(Of DataSchemaError)() args.Add(New DataSchemaError(category, ErrorSeverity.Message)) args.Add(New DataSchemaError("-----------------", ErrorSeverity.Message)) ' Remove any blank spaces from the category name Dim xCategory = New XElement(category.Replace(" ", "")) xContainer.Add(xCategory) For Each item In statistics 'Console.WriteLine(format, item.Key, item.Value); Dim entry = String.Format(format, item.Key, item.Value) args.Add(New DataSchemaError(entry, ErrorSeverity.Message)) ' Replace any blank spaces in the element key with ' underscores. xCategory.Add(New XElement(item.Key.Replace(" "c, "_"c), item.Value)) Next args.Add(New DataSchemaError(" ", ErrorSeverity.Message)) errorsContainer.Add(args, ErrorManager.BuildCategory)
将更改保存到 Class1.cs 中。
接下来生成类库。
为程序集签名并生成程序集
在**“项目”菜单上,单击“MyBuildContributor 属性”**。
单击**“签名”**选项卡。
单击**“为程序集签名”**。
在**“选择强名称密钥文件”中,单击“<新建>”**。
在**“创建强名称密钥”对话框中的“密钥文件名称”**中,键入 MyRefKey。
(可选)可以为强名称密钥文件指定密码。
单击**“确定”**。
在**“文件”菜单上,单击“全部保存”**。
在**“生成”菜单上,单击“生成解决方案”**。
接下来,必须安装并注册程序集,以便在生成数据库项目时加载该程序集。
安装生成参与者
若要安装生成参与者,必须执行以下任务:
将程序集和关联的 .pdb 文件复制到 Extensions 文件夹
创建 Extensions.xml 文件以注册生成参与者,从而使其可在您生成数据库项目时进行加载
安装 MyBuildContributor 程序集
在 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions 文件夹中创建一个名为 MyExtensions 的文件夹。
将经过签名的程序集 (MyBuildContributor.dll) 复制到 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions 文件夹。
提示
建议您不要将 XML 文件直接复制到 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions 文件夹中。 如果您改用一个子文件夹,则可以防止意外地更改随 Visual Studio 高级专业版提供的其他文件。
接下来,必须注册程序集(即,某种类型的功能扩展),以便在 Visual Studio 高级专业版中显示该程序集。
注册 MyBuildContributor 程序集
在**“视图”菜单上,单击“其他窗口”,然后单击“命令窗口”打开“命令”**窗口。
在**“命令”**窗口中,键入以下代码。 将 FilePath 替换为已编译的 .dll 文件的路径和文件名。 在路径和文件名的两侧加双引号。
提示
默认情况下,已编译的 .dll 文件的路径是“您的解决方案路径\bin\Debug”或“您的解决方案路径\bin\Release”。
? System.Reflection.Assembly.LoadFrom("FilePath").FullName
? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
按 Enter。
将所得到的行复制到剪贴板上。 该行应该与下面的内容类似:
"MyBuildContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
打开纯文本编辑器,如“记事本”。
重要事项 在 Windows Vista 和 Microsoft Windows Server 2008 中,以管理员身份打开编辑器,以便您能够将文件保存到 Program Files 文件夹中。
请提供下列信息。 可以粘贴在步骤 4 中复制的信息。 指定您自己的程序集名称、公钥标记和扩展类型:
<?xml version="1.0" encoding="utf-8" ?> <extensions assembly="" version="1" xmlns="urn:Microsoft.Data.Schema.Extensions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:Microsoft.Data.Schema.Extensions Microsoft.Data.Schema.Extensions.xsd"> <extension type="MyBuildContributor.ModelStatistics" assembly="MyBuildContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" /> </extensions>
使用此 XML 文件可注册从 BuildContributor 继承的类。
在 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions 文件夹中将文件另存为 MyBuildContributor.extensions.xml。
提示
在“保存类型”中,确认您指定了“所有文件”,否则“记事本”将忽略扩展名并用 .txt 扩展名保存文件。
关闭 Visual Studio。
接下来,您将生成数据库项目以测试参与者。
测试生成参与者
若要测试生成参与者,必须执行以下任务:
向计划生成的 .dbproj 文件添加属性
通过使用 MSBuild 并提供适当的参数以生成数据库项目
向数据库项目 (.dbproj) 文件添加属性
因为此生成参与者从 MSBuild 接受命令行参数,所以您必须修改数据库项目,以使用户可以通过 MSBuild 传递这些参数。 可以通过两种方法执行此操作。 可以手动修改 .dbproj 文件以添加所需参数。 如果仅使用 MSBuild 生成数据库项目,则可以选择进行此操作。 如果选择此选项,请将以下语句添加到 .dbproj 文件,具体位置在文件中最后一个 </ItemGroup> 节点与最后一个 </Project> 节点之间:
<ItemGroup>
<BuildContributorArgument Include="OutDir=$(OutDir)" />
<BuildContributorArgument Include="GenerateModelStatistics=$(GenerateModelStatistics)" />
<BuildContributorArgument Include="SortModelStatisticsBy=$(SortModelStatisticsBy)" />
</ItemGroup>
更简单的方法是将数据库项目一次加载到 Visual Studio 生成中,然后退出 Visual Studio。在退出时保存对项目的更改。 如果使用此方法,则其他参数会自动添加到数据库项目文件 (.dbproj) 中。
在按照这些方法之一执行操作后,您可以使用 MSBuild 传入命令行生成的参数。
向数据库项目文件添加属性
在 Visual Studio 中打开数据库项目。 有关更多信息,请参见如何:打开数据库或服务器项目。
生成数据库项目。 有关更多信息,请参见如何:生成数据库项目以生成经过编译的架构 (.dbschema) 文件。
关闭 Visual Studio。在对您进行提示时保存解决方案和项目。
接下来,可以通过使用 MSBuild 并指定使生成参与者可以生成模型统计信息的参数,来生成数据库项目。
生成数据库项目
使用 MSBuild 重新生成数据库项目并生成统计信息
打开一个 Visual Studio 命令提示符。 在**“开始”菜单上,依次单击“所有程序”、“Microsoft Visual Studio 2010”、“Visual Studio 工具”和“Visual Studio 命令提示(2010)”**。
在命令提示符下,定位到您的数据库项目所在的文件夹。
在命令提示符下,键入以下命令行:
MSBuild /t:Rebuild MyDatabaseProject.dbproj /p:GenerateModelStatistics=true /p:SortModelStatisticsBy=name /p:OutDir=.\
将 MyDatabaseProject 替换为要生成的数据库项目的名称。 如果在上次生成项目后对其进行了更改,则可以使用 /t:Build 代替 /t:Rebuild。
将显示类似以下内容的输出:
Microsoft (R) Build Engine Version 4.0.20817.0
[Microsoft .NET Framework, Version 4.0.20817.0]
Copyright (C) Microsoft Corporation 2007. All rights reserved.
Build started 8/19/2009 2:46:04 PM.
Project "C:\Users\UserName\Documents\Visual Studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\MyDatabaseProject.dbproj" on node 1 (Rebuild target(s)).
CoreClean:
Deleting file "c:\users\UserName\documents\visual studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\sql\debug\MyDatabaseProject_Script.PreDeployment.sql".
Deleting file "c:\users\UserName\documents\visual studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\sql\debug\MyDatabaseProject_Script.PostDeployment.sql".
Deleting file "c:\users\UserName\documents\visual studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\sql\debug\MyDatabaseProject_Database.sqlsettings".
Deleting file "c:\users\UserName\documents\visual studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\sql\debug\MyDatabaseProject_Database.sqldeployment".
Deleting file "c:\users\UserName\documents\visual studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\sql\debug\MyDatabaseProject_Database.sqlcmdvars".
Deleting file "c:\users\UserName\documents\visual studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\sql\debug\MyDatabaseProject.deploymanifest".
Deleting file "c:\users\UserName\documents\visual studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\sql\debug\MyDatabaseProject.dbschema".
Deleting file "c:\users\UserName\documents\visual studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\obj\Debug\MyDatabaseProject.dbschema".
DspBuild:
Creating a model to represent the project...
Loading project files...
Building the project model and resolving object interdependencies...
Validating the project model...
Model Statistics:
=================
Basic model info
-----------------
DSP: Sql100DatabaseSchemaProvider
Collation: 1033(CI)
Internal Elements
-----------------
ISql100DatabaseOptions: 1
ISql100Filegroup: 1
ISql100FullTextIndex: 3
ISql100Index: 95
ISql100MultiStatementTableValuedFunction: 1
ISql100PrimaryKeyConstraint: 71
ISql100Procedure: 10
ISql100ScalarFunction: 10
ISql100SimpleColumn: 481
ISql100SubroutineParameter: 41
ISql100Table: 71
ISql100UniqueConstraint: 1
ISql100View: 20
ISql100XmlIndex: 8
ISql90CheckConstraint: 89
ISql90ComputedColumn: 302
ISql90DatabaseDdlTrigger: 1
ISql90DefaultConstraint: 152
ISql90DmlTrigger: 10
ISql90File: 3
ISql90ForeignKeyConstraint: 90
ISql90FullTextCatalog: 1
ISql90Route: 1
ISql90Schema: 5
ISql90TriggerEventTypeSpecifier: 125
ISql90TypeSpecifier: 524
ISql90UserDefinedDataType: 6
ISql90XmlSchemaCollection: 6
ISql90XmlTypeSpecifier: 8
ISqlDynamicColumnSource: 5
ISqlExtendedProperty: 1161
ISqlFullTextIndexColumnSpecifier: 4
ISqlIndexedColumnSpecification: 220
ISqlScriptFunctionImplementation: 11
subtotal: 3538
total items: 34
External Elements
-----------------
ISql100Filegroup: 1
ISql100Queue: 3
ISql100Service: 3
ISql90Assembly: 1
ISql90AssemblySource: 1
ISql90ClrMethod: 151
ISql90ClrMethodParameter: 138
ISql90ClrProperty: 16
ISql90Contract: 6
ISql90Endpoint: 5
ISql90MessageType: 14
ISql90Role: 10
ISql90Schema: 13
ISql90TypeSpecifier: 305
ISql90User: 4
ISql90UserDefinedDataType: 1
ISql90UserDefinedType: 3
ISqlBuiltInType: 32
ISqlServerRole: 9
subtotal: 716
total items: 19
Relationships
-----------------
Multiple: 3002
Single: 4017
Composing: 2332
Hierarchical: 1812
Peer: 2875
subtotal: 7019
Annotations
-----------------
ExternalPropertyAnnotation: 1475
ExternalReferenceAnnotation: 187
ExternalSourceAnnotation: 2
ModuleInvocationAnnotation: 20
ParameterOrVariableAnnotation: 68
ResolveTimeVerifiedDanglingRelationshipAnnotation: 119
SqlInlineConstraintAnnotation: 1
SqlModelBuilderResolvableAnnotation: 7825
SysCommentsObjectAnnotation: 52
subtotal: 9749
total items: 9
Custom Data
-----------------
AnsiNulls: 1
ClrTypesDbSchema: 1
CompatibilityMode: 1
ModelCapability: 1
Permissions: 1
QuotedIdentifier: 1
subtotal: 6
total items: 6
Result was saved to .\ModelStatistics.xml
Writing model to MyDatabaseProject.dbschema...
CopyFilesToOutputDirectory:
Copying file from "obj\Debug\MyDatabaseProject.dbschema" to ".\sql\debug\MyDatabaseProject.dbschema".
MyDatabaseProject -> C:\Users\UserName\Documents\Visual Studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\sql\debug\MyDatabaseProject.dbschema
Done Building Project "C:\Users\UserName\Documents\Visual Studio 2010\Projects\MyDatabaseProject\MyDatabaseProject\MyDatabaseProject.dbproj" (Rebuild target(s)).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:11.20
打开 ModelStatistics.xml 并检查内容。
报告的结果还会保存到 XML 文件。
后续步骤
可以创建其他工具来执行对输出 XML 文件的处理。 这仅是生成参与者的一个示例。 您还可以创建生成参与者以执行其他操作,例如在生成中输出数据字典文件。