演练:扩展数据库项目生成以生成模型统计信息

可以创建生成参与者,以在生成数据库项目时执行自定义操作。 在本演练中,您将创建一个名为 ModelStatistics 的生成参与者,该参与者在您生成数据库项目时从数据库模型输出统计信息。 因为此生成参与者在您生成时接受参数,所以需要执行一些附加步骤。

在本演练中,您将完成以下主要任务:

  • 创建生成参与者

  • 安装生成参与者

  • 测试生成参与者

系统必备

您需要以下组件来完成本演练:

  • 必须已安装 Visual Studio 2010 高级专业版或 Visual Studio 2010 旗舰版。

  • 必须拥有包含数据库对象的数据库项目

提示

本演练专供已熟悉 Visual Studio 高级专业版数据库功能的用户使用。 同时,您还应熟悉基本的 Visual Studio 概念,例如如何创建类库以及如何使用代码编辑器向类添加代码。

创建生成参与者

若要创建生成参与者,必须执行以下任务:

  • 创建类库项目并添加所需引用

  • 定义一个名为 ModelStatistics 的类,该类继承自 BuildContributor

  • 重写 OnPopulateArguments 和 OnExecute 方法

  • 添加几个私有帮助器方法

  • 生成结果程序集

提示

当使用 MSBuild 生成数据库项目时,此参与者将仅进行输出。 默认情况下将会关闭报告,但可以通过在 MSBuild 命令行上提供一个属性来重写它。 有关如何启用输出窗口中的输出的示例,请参见演练:扩展数据库项目部署以分析部署计划

创建类库项目

  1. 创建一个名为 MyBuildContributor 的 Visual Basic 或 Visual C# 类库项目。

  2. 在解决方案资源管理器中,右击该项目节点,然后单击**“添加引用”**。

  3. 单击**“.NET”**选项卡。

  4. 突出显示**“Microsoft.Data.Schema”“Microsoft.Data.Schema.Sql”项,然后单击“确定”**。

    接下来,开始向类中添加代码。

定义 ModelStatistics 类

  1. 在代码编辑器中,更新 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
    
  2. 更新类定义以匹配以下内容:

    /// <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 继承的所有数据库架构提供程序都兼容。

  3. 接下来,添加以下成员。 您将使用这些成员使此提供程序可以接受命令行生成参数:

            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,用于报告发生的任何错误。

    其他相关类型和方法包括:DataSchemaModelModelStoreGetElementsGetAllAnnotationsGetCustomDataModelElement

    接下来,定义检查模型详细信息的帮助器方法。

添加生成统计信息的帮助器方法

  • 首先,通过添加下面的代码来添加四个帮助器方法的主干:

            /// <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
    

    相关类型和方法包括:ErrorManagerDataSchemaError

定义 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)
    

    此处的关键类型和成员包括:DataSchemaModelModelStoreDatabaseSchemaProviderModelCollation

添加 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)
    

    代码注释解释了此方法的关键方面。 值得注意的引用类型和方法包括:DataSchemaModelModelStoreGetElementsModelElement

添加 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 中。

    接下来生成类库。

为程序集签名并生成程序集

  1. 在**“项目”菜单上,单击“MyBuildContributor 属性”**。

  2. 单击**“签名”**选项卡。

  3. 单击**“为程序集签名”**。

  4. 在**“选择强名称密钥文件”中,单击“<新建>”**。

  5. 在**“创建强名称密钥”对话框中的“密钥文件名称”**中,键入 MyRefKey。

  6. (可选)可以为强名称密钥文件指定密码。

  7. 单击**“确定”**。

  8. 在**“文件”菜单上,单击“全部保存”**。

  9. 在**“生成”菜单上,单击“生成解决方案”**。

    接下来,必须安装并注册程序集,以便在生成数据库项目时加载该程序集。

安装生成参与者

若要安装生成参与者,必须执行以下任务:

  • 将程序集和关联的 .pdb 文件复制到 Extensions 文件夹

  • 创建 Extensions.xml 文件以注册生成参与者,从而使其可在您生成数据库项目时进行加载

安装 MyBuildContributor 程序集

  1. 在 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions 文件夹中创建一个名为 MyExtensions 的文件夹。

  2. 将经过签名的程序集 (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 程序集

  1. 在**“视图”菜单上,单击“其他窗口”,然后单击“命令窗口”打开“命令”**窗口。

  2. 在**“命令”**窗口中,键入以下代码。 将 FilePath 替换为已编译的 .dll 文件的路径和文件名。 在路径和文件名的两侧加双引号。

    提示

    默认情况下,已编译的 .dll 文件的路径是“您的解决方案路径\bin\Debug”或“您的解决方案路径\bin\Release”。

    ? System.Reflection.Assembly.LoadFrom("FilePath").FullName
    
    ? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
    
  3. Enter

  4. 将所得到的行复制到剪贴板上。 该行应该与下面的内容类似:

    "MyBuildContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
    
  5. 打开纯文本编辑器,如“记事本”。

    重要说明重要事项

    在 Windows Vista 和 Microsoft Windows Server 2008 中,以管理员身份打开编辑器,以便您能够将文件保存到 Program Files 文件夹中。

  6. 请提供下列信息。 可以粘贴在步骤 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 继承的类。

  7. 在 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions 文件夹中将文件另存为 MyBuildContributor.extensions.xml。

    提示

    “保存类型”中,确认您指定了“所有文件”,否则“记事本”将忽略扩展名并用 .txt 扩展名保存文件。

  8. 关闭 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 传入命令行生成的参数。

向数据库项目文件添加属性

  1. 在 Visual Studio 中打开数据库项目。 有关更多信息,请参见如何:打开数据库或服务器项目

  2. 生成数据库项目。 有关更多信息,请参见如何:生成数据库项目以生成经过编译的架构 (.dbschema) 文件

  3. 关闭 Visual Studio。在对您进行提示时保存解决方案和项目。

    接下来,可以通过使用 MSBuild 并指定使生成参与者可以生成模型统计信息的参数,来生成数据库项目。

生成数据库项目

使用 MSBuild 重新生成数据库项目并生成统计信息

  1. 打开一个 Visual Studio 命令提示符。 在**“开始”菜单上,依次单击“所有程序”“Microsoft Visual Studio 2010”“Visual Studio 工具”“Visual Studio 命令提示(2010)”**。

  2. 在命令提示符下,定位到您的数据库项目所在的文件夹。

  3. 在命令提示符下,键入以下命令行:

    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
  1. 打开 ModelStatistics.xml 并检查内容。

    报告的结果还会保存到 XML 文件。

后续步骤

可以创建其他工具来执行对输出 XML 文件的处理。 这仅是生成参与者的一个示例。 您还可以创建生成参与者以执行其他操作,例如在生成中输出数据字典文件。

请参见

概念

扩展 Visual Studio 的数据库功能

其他资源

使用生成参与者和部署参与者自定义数据库生成和部署

演练:扩展数据库项目部署以分析部署计划