연습: 데이터베이스 프로젝트 빌드를 확장하여 모델 통계 생성

데이터베이스 프로젝트를 빌드할 때 사용자 지정 작업을 수행하는 빌드 참가자를 만들 수 있습니다. 이 연습에서는 데이터베이스 프로젝트를 빌드할 때 데이터베이스 모델에서 통계를 출력하는 ModelStatistics라는 빌드 참가자를 만듭니다. 이 빌드 참가자는 빌드 시 매개 변수를 받으므로 몇 가지 추가 단계가 필요합니다.

이 연습에서는 다음 주요 작업을 수행합니다.

  • 빌드 참가자 만들기

  • 빌드 참가자 설치

  • 빌드 참가자 테스트

사전 요구 사항

이 연습을 완료하려면 다음 구성 요소가 필요합니다.

  • Visual Studio 2010 Premium 또는 Visual Studio 2010 Ultimate이 설치되어 있어야 합니다.

  • 데이터베이스 개체가 포함된 데이터베이스 프로젝트가 있어야 합니다.

참고

이 연습은 Visual Studio Premium의 데이터베이스 기능에 이미 친숙한 사용자를 위한 것입니다. 사용자는 클래스 라이브러리를 만드는 방법, 코드 편집기를 사용하여 클래스에 코드를 추가하는 방법 등과 같은 기본 Visual Studio 개념도 알고 있어야 합니다.

빌드 참가자 만들기

빌드 참가자를 만들려면 다음 작업을 수행해야 합니다.

  • 클래스 라이브러리 프로젝트를 만들고 필요한 참조를 추가합니다.

  • BuildContributor에서 상속하는 ModelStatistics라는 클래스를 정의합니다.

  • OnPopulateArguments 및 OnExecute 메서드를 재정의합니다.

  • 몇 개의 private 도우미 메서드를 추가합니다.

  • 결과 어셈블리를 빌드합니다.

참고

이 참가자는 MSBuild를 사용하여 데이터베이스 프로젝트를 빌드하는 경우에만 출력됩니다. 보고서는 기본적으로 해제되어 있지만 이는 MSBuild 명령줄에 속성을 제공하여 재정의할 수 있습니다. 출력 창에서 출력을 사용하도록 설정하는 방법에 대한 예제를 보려면 연습: 데이터베이스 프로젝트 배포를 확장하여 배포 계획 분석을 참조하십시오.

클래스 라이브러리 프로젝트를 만들려면

  1. MyBuildContributor라는 Visual Basic 또는 Visual C# 클래스 라이브러리 프로젝트를 만듭니다.

  2. 솔루션 탐색기에서 프로젝트 노드를 마우스 오른쪽 단추로 클릭한 다음 참조 추가를 클릭합니다.

  3. .NET 탭을 클릭합니다.

  4. Microsoft.Data.SchemaMicrosoft.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가 전달됩니다.

    주목할 추가 형식 및 메서드로는 DataSchemaModel, ModelStore, GetElements, GetAllAnnotations, GetCustomDataModelElement가 있습니다.

    다음에는 모델에 대한 세부 사항을 조사하는 도우미 메서드를 정의합니다.

통계를 생성하는 도우미 메서드를 추가하려면

  • 먼저 다음 코드를 추가하여 네 개의 도우미 메서드에 대한 기본 구조를 추가합니다.

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

    주요 형식 및 멤버로는 DataSchemaModel, ModelStore, DatabaseSchemaProviderModelCollation이 있습니다.

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, GetElementsModelElement가 있습니다.

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 Premium에서 제공된 다른 파일을 실수로 변경하는 것을 방지할 수 있습니다.

    다음에는 기능 확장의 한 형식인 어셈블리가 Visual Studio Premium에 표시되도록 등록해야 합니다.

MyBuildContributor 어셈블리를 등록하려면

  1. 보기 메뉴에서 다른 창, 명령 창을 차례로 클릭하여 명령 창을 엽니다.

  2. 명령 창에서 다음 코드를 입력합니다. FilePath 대신 컴파일된 .dll 파일의 경로 및 파일 이름을 입력합니다. 이때 경로 및 파일 이름을 따옴표로 묶습니다.

    참고

    기본적으로 컴파일된 .dll 파일의 경로는 YourSolutionPath\bin\Debug 또는 YourSolutionPath\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 Tools, **Visual Studio 명령 프롬프트(2010)**를 차례로 클릭합니다.

  2. 명령 프롬프트에서 사용자 데이터베이스 프로젝트가 포함된 폴더로 이동합니다.

  3. 명령 프롬프트에서 다음 명령줄을 입력합니다.

    MSBuild /t:Rebuild MyDatabaseProject.dbproj /p:GenerateModelStatistics=true /p:SortModelStatisticsBy=name /p:OutDir=.\
    

    MyDatabaseProject를 빌드할 데이터베이스 프로젝트의 이름으로 바꿉니다. 프로젝트를 마지막으로 빌드한 후 변경한 적이 있으면 /t:Rebuild 대신 /t:Build를 사용할 수 있습니다.

    다음과 같은 출력이 나타납니다.

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의 데이터베이스 기능 확장

기타 리소스

빌드 및 배포 참가자를 사용하여 데이터베이스 빌드 및 배포 사용자 지정

연습: 데이터베이스 프로젝트 배포를 확장하여 배포 계획 분석