Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Du kan skapa en byggdeltagare för att utföra anpassade åtgärder när du skapar ett databasprojekt. I den här genomgången skapar du en build-deltagare med namnet ModelStatistics som matar ut statistik från SQL-databasmodellen när du skapar ett databasprojekt. Eftersom den här byggdeltagaren tar parametrar när du skapar krävs några extra steg.
I den här genomgången utför du följande viktiga uppgifter:
Förutsättningar
Du behöver följande komponenter för att slutföra den här genomgången:
Du måste ha installerat en version av Visual Studio som innehåller SQL Server Data Tools (SSDT) och stöder utveckling av C# eller Visual Basic (VB).
Du måste ha ett SQL-projekt som innehåller SQL-objekt.
Not
Den här genomgången är avsedd för användare som redan är bekanta med SQL-funktionerna i SSDT. Du förväntas också känna till grundläggande Visual Studio-begrepp, till exempel hur du skapar ett klassbibliotek och hur du använder kodredigeraren för att lägga till kod i en klass.
Skapa deltagarbakgrund
Byggbidragsgivare körs under projektkompileringen, efter att modellen som representerar projektet har genererats, men innan projektet sparas på disk. De kan användas för flera scenarier, till exempel:
Validera modellinnehållet och rapportera valideringsfel till anroparen. Detta kan göras genom att lägga till fel i en lista som skickas som en parameter till metoden OnExecute.
Generera modellstatistik och rapportera till användaren. Det här är exemplet som visas här.
Den viktigaste startpunkten för byggdeltagare är metoden OnExecute. Alla klasser som ärver från BuildContributor måste implementera den här metoden. Ett BuildContributorContext-objekt skickas till den här metoden – det innehåller alla relevanta data för bygget, till exempel en modell av databasen, byggegenskaper och argument/filer som ska användas av byggdeltagare.
TSqlModel och databasmodell-API:et
Det mest användbara objektet är databasmodellen, som representeras av ett TSqlModel-objekt. Det här är en logisk representation av en databas, inklusive alla tabeller, vyer och andra element, plus relationerna mellan dem. Det finns ett starkt skrivet schema som kan användas för att fråga efter specifika typer av element och korsa intressanta relationer. Du ser exempel på hur detta används i genomgångskoden.
Här är några av de kommandon som används av exempeldeltagaren i den här genomgången:
| Class | Metod eller egenskap | Description |
|---|---|---|
| TSqlModel | GetObjects() | Kör frågor mot modellen för objekt och är den viktigaste startpunkten för modell-API:et. Endast toppnivåtyper som Tabell eller Vy kan efterfrågas – typer som Kolumner kan bara hittas genom att bläddra i modellen. Om inga ModelTypeClass-filter anges returneras alla toppnivåtyper. |
| TSqlObject | GetReferencedRelationshipInstances() | Söker efter relationer till element som refereras av den aktuella TSqlObject. För en tabell returnerar detta till exempel objekt som tabellens kolumner. I det här fallet kan ett ModelRelationshipClass-filter användas för att ange exakta relationer att fråga efter (om du till exempel använder filtret Table.Columns ser du till att endast kolumner returneras). Det finns flera liknande metoder, till exempel GetReferencingRelationshipInstances, GetChildren och GetParent. Mer information finns i API-dokumentationen. |
Unikt identifiera din bidragsgivare
Under byggprocessen läses anpassade deltagare in från en standardtilläggskatalog. Build-deltagare identifieras av ett ExportBuildContributor--attribut. Det här attributet krävs för att deltagare ska kunna identifieras. Det här attributet bör se ut ungefär som följande kod:
[ExportBuildContributor("ExampleContributors.ModelStatistics", "1.0.0.0")]
I det här fallet ska den första parametern till attributet vara en unik identifierare – den används för att identifiera din deltagare i projektfiler. Bästa praxis är att kombinera bibliotekets namnområde (i den här genomgången, "ExampleContributors") med klassnamnet (i den här genomgången, "ModelStatistics") för att skapa identifieraren. Du ser hur det här namnområdet används för att ange att din komponent ska köras senare i genomgången.
Skapa en byggdeltagare
Om du vill skapa en byggdeltagare måste du utföra följande uppgifter:
Skapa ett klassbiblioteksprojekt och lägg till nödvändiga referenser.
Definiera en klass med namnet ModelStatistics som ärver från BuildContributor.
Åsidosätt metoden OnExecute.
Lägg till några privata hjälpmetoder.
Skapa den resulterande sammansättningen.
Skapa ett klassbiblioteksprojekt
Skapa ett Visual Basic- eller C#-klassbiblioteksprojekt med namnet MyBuildContributor.
Byt namn på filen "Class1.cs" till "ModelStatistics.cs".
Högerklicka på projektnoden i Solution Explorer och välj sedan Lägg till referens.
Välj posten System.ComponentModel.Composition och välj sedan OK.
Lägg till nödvändiga SQL-referenser: högerklicka på projektnoden och välj sedan Lägg till referens. Välj knappen Bläddra . Gå till mappen
C:\Program Files (x86)\Microsoft SQL Server\110\DAC\Bin. Välj posterna Microsoft.SqlServer.Dac.dll, Microsoft.SqlServer.Dac.Extensions.dll och Microsoft.Data.Tools.Schema.Sql.dll och välj sedan OK.Sedan börjar du lägga till kod i klassen.
Definiera klassen ModelStatistics
Klassen ModelStatistics bearbetar databasmodellen som skickas till metoden OnExecute och skapar och XML-rapport som beskriver innehållet i modellen.
Uppdatera filen ModelStatistics.cs i kodredigeraren så att den matchar följande kod:
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; namespace ExampleContributors { /// <summary> /// A BuildContributor that generates statistics about a model and saves this to the output directory. /// Only runs if a "GenerateModelStatistics=true" contributor argument is set in the project file, or a targets file. /// Statistics can be sorted by "none, "name" or "value", with "none" being the default sort behavior. /// /// To set contributor arguments in a project file, add: /// /// <PropertyGroup> /// <ContributorArguments Condition="'$(Configuration)' == 'Debug'"> /// $(ContributorArguments);ModelStatistics.GenerateModelStatistics=true;ModelStatistics.SortModelStatisticsBy="name"; /// </ContributorArguments> /// <PropertyGroup> /// /// This generates model statistics when building in Debug mode only - remove the condition to generate in all build modes. /// </summary> [ExportBuildContributor("ExampleContributors.ModelStatistics", "1.0.0.0")] public class ModelStatistics : BuildContributor { public const string GenerateModelStatistics = "ModelStatistics.GenerateModelStatistics"; public const string SortModelStatisticsBy = "ModelStatistics.SortModelStatisticsBy"; public const string OutDir = "ModelStatistics.OutDir"; public const string ModelStatisticsFilename = "ModelStatistics.xml"; 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; /// <summary> /// Override the OnExecute method to perform actions when you build a database project. /// </summary> protected override void OnExecute(BuildContributorContext context, IList<ExtensibilityError> errors) { // handle related arguments, passed in as part of // the context information. bool generateModelStatistics; ParseArguments(context.Arguments, errors, 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. StringBuilder statisticsMsg = new StringBuilder(); statisticsMsg.AppendLine(" ") .AppendLine("Model Statistics:") .AppendLine("===") .AppendLine(" "); errors.Add(new ExtensibilityError(statisticsMsg.ToString(), Severity.Message)); var model = context.Model; // Start building up the XML that is serialized later var xRoot = new XElement("ModelStatistics"); SummarizeModelInfo(model, xRoot, errors); // First, count the elements that are contained // in this model. IList<TSqlObject> elements = model.GetObjects(DacQueryScopes.UserDefined).ToList(); Summarize(elements, element => element.ObjectType.Name, "UserDefinedElements", xRoot, errors); // 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.GetObjects(DacQueryScopes.BuiltIn | DacQueryScopes.SameDatabase | DacQueryScopes.System).ToList(); Summarize(elements, element => element.ObjectType.Name, "OtherElements", xRoot, errors); // Now, count the number of each type // of relationship in the model. SurveyRelationships(model, xRoot, errors); // Determine where the user wants to save // the serialized XML file. string outDir; if (context.Arguments.TryGetValue(OutDir, out outDir) == false) { outDir = "."; } string filePath = Path.Combine(outDir, ModelStatisticsFilename); // Save the XML file and tell the user // where it was saved. xRoot.Save(filePath); ExtensibilityError resultArg = new ExtensibilityError("Result was saved to " + filePath, Severity.Message); errors.Add(resultArg); } } /// <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, IList<ExtensibilityError> errors, out bool generateModelStatistics) { // 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 ExtensibilityError invalidArg = new ExtensibilityError( GenerateModelStatistics + "=" + valueString + " was not valid. It can be true or false", Severity.Error); errors.Add(invalidArg); 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 ExtensibilityError invalidArg = new ExtensibilityError( SortModelStatisticsBy + "=" + valueString + " was not valid. It can be none, name, or value", Severity.Error); errors.Add(invalidArg); } } } } /// <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(TSqlModel model, XElement xContainer, IList<ExtensibilityError> errors) { // use a Dictionary to accumulate the information // that is later 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("Version", model.Version.ToString()); TSqlObject options = model.GetObjects(DacQueryScopes.UserDefined, DatabaseOptions.TypeClass).FirstOrDefault(); if (options != null) { info.Add("Collation", options.GetProperty<string>(DatabaseOptions.Collation)); } // Output the accumulated information and add it to // the XML. OutputResult("Basic model info", info, xContainer, errors); } /// <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, IList<ExtensibilityError> errors) { // 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 is 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, errors); } /// <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(TSqlModel model, XElement xContainer, IList<ExtensibilityError> errors) { // get a list that contains all elements in the model var elements = model.GetObjects(DacQueryScopes.All); // We are interested in all relationships that // reference each element. var entries = from element in elements from entry in element.GetReferencedRelationshipInstances(DacExternalQueryScopes.All) select entry; // initialize our counting buckets var composing = 0; var hierachical = 0; var peer = 0; // process each relationship, adding to the // appropriate bucket for style and type. foreach (var entry in entries) { switch (entry.Relationship.Type) { case RelationshipType.Composing: ++composing; break; case RelationshipType.Hierarchical: ++hierachical; break; case RelationshipType.Peer: ++peer; break; default: break; } } // build a dictionary of data to pass along // to the OutputResult method. var stat = new Dictionary<string, int> { {"Composing", composing}, {"Hierarchical", hierachical}, {"Peer", peer}, {"subtotal", entries.Count()} }; OutputResult("Relationships", stat, xContainer, errors); } /// <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, IList<ExtensibilityError> errors) { var maxLen = statistics.Max(stat => stat.Key.Length) + 2; var format = string.Format("{{0, {0}}}: {{1}}", maxLen); StringBuilder resultMessage = new StringBuilder(); //List<ExtensibilityError> args = new List<ExtensibilityError>(); resultMessage.AppendLine(category); resultMessage.AppendLine("-----------------"); // 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); resultMessage.AppendLine(entry); // Replace any blank spaces in the element key with // underscores. xCategory.Add(new XElement(item.Key.Replace(' ', '_'), item.Value)); } resultMessage.AppendLine(" "); errors.Add(new ExtensibilityError(resultMessage.ToString(), Severity.Message)); } } }Sedan skapar du klassbiblioteket.
Signera och skapa sammansättningen
På projektmenyn väljer du Egenskaper för MyBuildContributor.
Välj fliken Signering .
Välj Signera sammansättningen.
I Välj en nyckelfil med starkt namn väljer du <Nytt>.
I dialogrutan Skapa stark namnnyckel skriver du MyRefKeyi Nyckelfilnamn.
(valfritt) Du kan ange ett lösenord för din starka namnnyckelfil.
Välj OK.
På menyn Arkiv väljer du Spara alla.
På menyn Build väljer du Build Solution.
Därefter måste du installera sammansättningen så att den läses in när du skapar SQL-projekt.
Installera en byggbidragsgivare
För att installera en byggkomponent måste du kopiera assemblyn och den tillhörande .pdb-filen till extensions-mappen.
Installera sammansättningen MyBuildContributor
Därefter kopierar du sammansättningsinformationen till katalogen Tillägg. När Visual Studio startar identifierar den eventuella tillägg i
%ProgramFiles%\Microsoft SQL Server\110\DAC\Bin\Extensionskatalogen och underkatalogerna och gör dem tillgängliga för användning.Kopiera MyBuildContributor.dll sammansättningsfilen från utdatakatalogen till
%ProgramFiles%\Microsoft SQL Server\110\DAC\Bin\Extensions-katalogen.Not
Som standard är sökvägen till den kompilerade
.dllfilen YourSolutionPath\YourProjectPath\bin\Debug eller YourSolutionPath\YourProjectPath\bin\Release.
Kör eller testa din build-komponent
Om du vill köra eller testa din byggkonstruktör måste du utföra följande uppgifter:
Lägg till egenskaper i filen
.sqlprojsom du planerar att skapa.Skapa databasprojektet med hjälp av MSBuild och ange lämpliga parametrar.
Lägga till egenskaper i SQL-projektfilen (.sqlproj)
Du måste alltid uppdatera SQL-projektfilen för att ange ID för de deltagare som du vill köra. Eftersom den här kompileringsdeltagaren accepterar kommandoradsparametrar från MSBuild måste du ändra SQL-projektet så att användarna kan skicka dessa parametrar via MSBuild.
Du kan göra detta på något av två sätt:
Du kan ändra
.sqlprojfilen manuellt för att lägga till de argument som krävs. Du kan välja att göra detta om du inte tänker återanvända byggdeltagaren i ett stort antal projekt. Om du väljer det här alternativet lägger du till följande instruktioner i.sqlprojfilen efter den första importnoden i filen<PropertyGroup> <BuildContributors> $(BuildContributors);ExampleContributors.ModelStatistics </BuildContributors> <ContributorArguments Condition="'$(Configuration)' == 'Debug'"> $(ContributorArguments);ModelStatistics.GenerateModelStatistics=true;ModelStatistics.SortModelStatisticsBy=name; </ContributorArguments> </PropertyGroup>Den andra metoden är att skapa en målfil som innehåller de obligatoriska deltagarargumenten. Det här är användbart om du använder samma deltagare för flera projekt, eftersom det innehåller standardvärdena.
I det här fallet skapar du en målfil i sökvägen för MSBuild-tillägg:
Gå till
%ProgramFiles%\MSBuild.Skapa en ny mapp "MyContributors" där dina målfiler lagras.
Skapa en ny fil "MyContributors.targets" i den här katalogen, lägg till följande text i den och spara sedan filen:
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <BuildContributors>$(BuildContributors);ExampleContributors.ModelStatistics</BuildContributors> <ContributorArguments Condition="'$(Configuration)' == 'Debug'">$(ContributorArguments);ModelStatistics.GenerateModelStatistics=true;ModelStatistics.SortModelStatisticsBy=name;</ContributorArguments> </PropertyGroup> </Project>.sqlprojI filen för alla projekt som du vill köra deltagare importerar du målfilen genom att lägga till följande instruktion i.sqlprojfilen efter <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />node i filen:<Import Project="$(MSBuildExtensionsPath)\MyContributors\MyContributors.targets " />
När du har följt någon av dessa metoder kan du använda MSBuild för att skicka in parametrarna för kommandoradsversioner.
Not
Du måste alltid uppdatera egenskapen "BuildContributors" för att ange ditt deltagar-ID. Det här är samma ID som används i attributet "ExportBuildContributor" i din källfil för deltagare. Utan detta körs inte komponenten när projektet byggs. Egenskapen "ContributorArguments" får bara uppdateras om du har argument som krävs för att din deltagare ska kunna köras.
Skapa SQL-projektet
Återskapa databasprojektet med hjälp av MSBuild och generera statistik
Högerklicka på projektet i Visual Studio och välj Återskapa. Detta återskapar projektet och du bör se den modellstatistik som genereras, med utdata som ingår i byggutdata och sparas i ModelStatistics.xml. Du kan behöva välja Visa alla filer i Solution Explorer för att se XML-filen.
Öppna en Visual Studio-kommandotolk: Välj Alla program på Start-menyn, välj Microsoft Visual Studio Visual Studio <Version>, välj Visual Studio Tools och välj sedan Visual Studio Command Prompt (<Visual Studio Version>).
I kommandotolken navigerar du till mappen som innehåller ditt SQL-projekt.
I kommandotolken skriver du följande kommando:
MSBuild /t:Rebuild MyDatabaseProject.sqlproj /p:BuildContributors=$(BuildContributors);ExampleContributors.ModelStatistics /p:ContributorArguments=$(ContributorArguments);GenerateModelStatistics=true;SortModelStatisticsBy=name;OutDir=.\;Ersätt MyDatabaseProject- med namnet på det databasprojekt som du vill skapa. Om du hade ändrat projektet efter att du senast skapade det kan du använda
/t:Buildi stället/t:Rebuildför .I utdata bör du se bygginformation som i följande exempel:
Model Statistics: === Basic model info ----------------- Version: Sql110 Collation: SQL_Latin1_General_CP1_CI_AS UserDefinedElements ----------------- DatabaseOptions: 1 subtotal: 1 total items: 1 OtherElements ----------------- Assembly: 1 BuiltInServerRole: 9 ClrTypeMethod: 218 ClrTypeMethodParameter: 197 ClrTypeProperty: 20 Contract: 6 DataType: 34 Endpoint: 5 Filegroup: 1 MessageType: 14 Queue: 3 Role: 10 Schema: 13 Service: 3 User: 4 UserDefinedType: 3 subtotal: 541 total items: 16 Relationships ----------------- Composing: 477 Hierarchical: 6 Peer: 19 subtotal: 502Öppna ModelStatistics.xml och granska innehållet.
De resultat som rapporterades sparas också i XML-filen.