Przewodnik: rozszerzanie kompilacji projektu bazy danych w celu generowania statystyk modelu
Możesz utworzyć kontrybutora kompilacji, aby wykonywać niestandardowe działania podczas kompilowania projektu bazy danych. W tym przewodniku utworzysz współautora kompilacji o nazwie ModelStatistics, który generuje statystyki z modelu bazy danych SQL podczas tworzenia projektu bazy danych. Ponieważ ten współautor kompilacji przyjmuje parametry podczas kompilacji, wymagane są pewne dodatkowe kroki.
W tym przewodniku wykonasz następujące główne zadania:
Warunki wstępne
Do ukończenia tego przewodnika potrzebne są następujące składniki:
Musisz mieć zainstalowaną wersję programu Visual Studio, która zawiera narzędzia SQL Server Data Tools (SSDT) i obsługuje programowanie w języku C# lub VB.
Musisz mieć projekt SQL zawierający obiekty SQL.
Notatka
Ten przewodnik jest przeznaczony dla użytkowników, którzy znają już funkcje SQL programu SSDT. Oczekuje się również, że zapoznasz się z podstawowymi pojęciami programu Visual Studio, takimi jak tworzenie biblioteki klas i używanie edytora kodu do dodawania kodu do klasy.
Tworzenie profilu współautora
Współautorzy kompilacji są uruchamiani podczas kompilacji projektu, po wygenerowaniu modelu reprezentującego projekt, ale przed zapisaniem projektu na dysku. Mogą być używane w wielu scenariuszach, takich jak
Weryfikowanie zawartości modelu i zgłaszanie błędów walidacji do osoby wywołującej. Można to zrobić, dodając błędy do listy przekazanej jako parametr do metody OnExecute.
Generowanie statystyk modelu i raportowanie do użytkownika. Jest to przykład pokazany tutaj.
Głównym punktem wejścia dla współautorów kompilacji jest metoda OnExecute. Wszystkie klasy dziedziczone z klasy BuildContributor muszą zaimplementować tę metodę. Obiekt BuildContributorContext jest przekazywany do tej metody — zawiera wszystkie odpowiednie dane dla kompilacji, takie jak model bazy danych, właściwości kompilacji i argumenty/pliki, które mają być używane przez współautorów kompilacji.
TSqlModel i model bazy danych API
Najbardziej przydatnym obiektem będzie model bazy danych reprezentowany przez obiekt TSqlModel. Jest to logiczna reprezentacja bazy danych, w tym wszystkie tabele, widok i inne elementy oraz relacje między nimi. Istnieje silnie typizowany schemat, który może służyć do wykonywania zapytań dotyczących określonych typów elementów i nawigowania po interesujących relacjach. Zobaczysz przykłady użycia tego kodu w przewodniku.
Poniżej przedstawiono niektóre polecenia używane przez przykładowego współautora w tym przewodniku:
klasa | Metoda/Właściwość | Opis |
---|---|---|
TSqlModel | GetObjects() | Wysyła zapytanie do modelu dla obiektów i stanowi główny punkt wejścia do interfejsu API modelu. Można wykonywać zapytania tylko do typów najwyższego poziomu, takich jak Tabela lub Widok — typy, takie jak Kolumny, można znaleźć tylko przez przechodzenie do modelu. Jeśli nie określono filtrów ModelTypeClass, zostaną zwrócone wszystkie typy najwyższego poziomu. |
TSqlObject | GetReferencedRelationshipInstances() | Znajduje relacje z elementami, do których odwołuje się bieżący obiekt TSqlObject. Na przykład w przypadku tabeli zwraca to obiekty, takie jak kolumny tabeli. W takim przypadku filtr ModelRelationshipClass można użyć do określenia konkretnych relacji do zapytania (na przykład, użycie filtru "Tabela.Kolumny" zapewni, że zwracane będą tylko kolumny). Istnieje wiele podobnych metod, takich jak GetReferencingRelationshipInstances, GetChildren i GetParent. Aby uzyskać więcej informacji, zobacz dokumentację interfejsu API. |
wyjątkowe identyfikowanie twojego współautora
Podczas procesu kompilacji współautorzy niestandardowi są ładowani ze standardowego katalogu rozszerzeń. Współautorzy kompilacji są identyfikowani przez atrybut ExportBuildContributor. Ten atrybut jest wymagany, aby można było odnaleźć współautorów. Ten atrybut powinien wyglądać podobnie do następującego:
[ExportBuildContributor("ExampleContributors.ModelStatistics", "1.0.0.0")]
W takim przypadku pierwszy parametr atrybutu powinien być unikatowym identyfikatorem — służy do identyfikowania współautora w plikach projektu. Najlepszą praktyką jest połączenie przestrzeni nazw biblioteki (w tym przykładzie "ExampleContributors") z nazwą klasy (w tym przykładzie "ModelStatistics") w celu utworzenia identyfikatora. Zobaczysz, jak ta przestrzeń nazw jest używana do określenia, że komponent powinien zostać uruchomiony później w przewodniku.
Utwórz współtwórcę kompilacji
Aby utworzyć współautora kompilacji, należy wykonać następujące zadania:
Utwórz projekt biblioteki klas i dodaj wymagane odwołania.
Zdefiniuj klasę o nazwie ModelStatistics, która dziedziczy z BuildContributor.
Nadpisz metodę OnExecute.
Dodaj kilka prywatnych metod pomocniczych.
Skompiluj wynikowy zestaw.
Aby utworzyć projekt biblioteki klas
Utwórz projekt biblioteki klas Visual Basic lub Visual C# o nazwie MyBuildContributor.
Zmień nazwę pliku "Class1.cs" na "ModelStatistics.cs".
W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy węzeł projektu, a następnie kliknij Dodaj odwołanie.
Wybierz wpis System.ComponentModel.Composition, a następnie kliknij OK.
Dodaj wymagane odwołania SQL: kliknij prawym przyciskiem myszy węzeł projektu, a następnie kliknij Dodaj odwołanie. Kliknij przycisk Przeglądaj. Przejdź do folderu C:\Program Files (x86)\Microsoft SQL Server\110\DAC\Bin. Wybierz wpisy Microsoft.SqlServer.Dac.dll, Microsoft.SqlServer.Dac.Extensions.dlli Microsoft.Data.Tools.Schema.Sql.dll, a następnie kliknij przycisk OK.
Następnie zaczniesz dodawać kod do klasy.
Aby zdefiniować klasę ModelStatistics
Klasa ModelStatistics przetwarza model bazy danych przekazany do metody OnExecute i tworzy i raport XML zawierający szczegółowe informacje o zawartości modelu.
W edytorze kodu zaktualizuj plik ModelStatistics.cs, aby był zgodny z następującymi elementami:
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. /// Will only run 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 the following: /// /// <PropertyGroup> /// <ContributorArguments Condition="'$(Configuration)' == 'Debug'"> /// $(ContributorArguments);ModelStatistics.GenerateModelStatistics=true;ModelStatistics.SortModelStatisticsBy="name"; /// </ContributorArguments> /// <PropertyGroup> /// /// This will generate 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 will later // be serialized. 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 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("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 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, 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)); } } }
Następnie utworzysz bibliotekę klas.
Aby podpisać i skompilować zestaw
W menu Project kliknij pozycję MyBuildContributor Properties.
Kliknij kartę podpisywania.
Kliknij Podpisz zestaw.
W Wybierz plik klucza silnej nazwy, kliknij <Nowy>.
W oknie dialogowym Utwórz silny klucz nazwy, w polu Nazwa pliku klucza, wpisz MyRefKey.
(opcjonalnie) Możesz określić hasło dla pliku klucza silnej nazwy.
Kliknij przycisk OK.
W menu Plik, kliknij pozycję Zapisz wszystkie.
W menu Kompiluj kliknij pozycję Kompiluj rozwiązanie.
Następnie należy zainstalować zestaw, aby był ładowany podczas kompilowanie projektów SQL.
Instalowanie współautora kompilacji
Aby zainstalować współautora kompilacji, należy skopiować zestaw i skojarzony plik .pdb do folderu Extensions.
Aby zainstalować zestaw MyBuildContributor
Następnie skopiujesz informacje o zestawie do katalogu Extensions. Po uruchomieniu programu Visual Studio zidentyfikuje wszystkie rozszerzenia w %Program Files%\Microsoft SQL Server\110\DAC\Bin\Extensions katalogu i podkatalogów oraz udostępni je do użycia.
Skopiuj plik zestawu MyBuildContributor.dll z katalogu wyjściowego do katalogu %Program Files%\Microsoft SQL Server\110\DAC\Bin\Extensions.
Notatka
Domyślnie ścieżka skompilowanego pliku .dll to YourSolutionPath\YourProjectPath\bin\Debug lub YourSolutionPath\YourProjectPath\bin\Release.
Uruchamianie lub testowanie współautora kompilacji
Aby uruchomić lub przetestować współautora kompilacji, należy wykonać następujące zadania:
Dodaj właściwości do pliku .sqlproj, który planujesz skompilować.
Skompiluj projekt bazy danych przy użyciu programu MSBuild i podaj odpowiednie parametry.
Dodawanie właściwości do pliku projektu SQL (sqlproj)
Należy zawsze zaktualizować plik projektu SQL, aby określić identyfikator współautorów, których chcesz uruchomić. Ponadto, ponieważ ten współautor kompilacji akceptuje parametry wiersza polecenia z programu MSBuild, należy zmodyfikować projekt SQL, aby umożliwić użytkownikom przekazywanie tych parametrów za pośrednictwem programu MSBuild.
Można to zrobić na jeden z dwóch sposobów:
Możesz ręcznie zmodyfikować plik .sqlproj, aby dodać wymagane argumenty. Możesz to zrobić, jeśli nie zamierzasz ponownie używać współautora kompilacji w dużej liczbie projektów. Jeśli wybierzesz tę opcję, dodaj następujące instrukcje do pliku .sqlproj po pierwszym węźle Import w pliku.
<PropertyGroup> <BuildContributors> $(BuildContributors);ExampleContributors.ModelStatistics </BuildContributors> <ContributorArguments Condition="'$(Configuration)' == 'Debug'"> $(ContributorArguments);ModelStatistics.GenerateModelStatistics=true;ModelStatistics.SortModelStatisticsBy=name; </ContributorArguments> </PropertyGroup>
Druga metoda polega na utworzeniu pliku targets zawierającego wymagane argumenty współautora. Jest to przydatne, jeśli używasz tego samego współautora dla wielu projektów, ponieważ będzie zawierać wartości domyślne.
W takim przypadku utwórz plik targets w ścieżce rozszerzeń MSBuild:
Przejdź do %Program Files%\MSBuild\.
Utwórz nowy folder "MyContributors", w którym będą przechowywane pliki docelowe.
Utwórz nowy plik "MyContributors.targets" w tym katalogu, dodaj do niego następujący tekst, a następnie zapisz plik:
<?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>
Wewnątrz pliku .sqlproj dla dowolnego projektu, w którym chcesz zastosować współautorów, zaimportuj plik targets, dodając następującą instrukcję do pliku .sqlproj po węźle <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" /> w pliku:
<Import Project="$(MSBuildExtensionsPath)\MyContributors\MyContributors.targets " />
Po wykonaniu jednego z tych podejść możesz użyć programu MSBuild, aby przekazać parametry kompilacji wiersza polecenia.
Notatka
Aby określić identyfikator współautora, należy zawsze zaktualizować właściwość "BuildContributors". Jest to ten sam identyfikator używany w atrybucie "ExportBuildContributor" w pliku źródłowym współautora. Bez tego współautor nie będzie uruchamiany podczas kompilowania projektu. Właściwość "ContributorArguments" musi zostać zaktualizowana tylko wtedy, gdy masz argumenty wymagane do uruchomienia przez współautora.
Kompilowanie projektu SQL
Aby ponownie skompilować projekt bazy danych przy użyciu programu MSBuild i wygenerować statystyki
W programie Visual Studio kliknij prawym przyciskiem myszy na swój projekt i wybierz opcję "Odbuduj". Spowoduje to odbudowanie projektu, zostaną wyświetlone wygenerowane statystyki modelu, a dane wyjściowe zostaną uwzględnione w wyniku kompilacji i zapisane w ModelStatistics.xml. Pamiętaj, że może być konieczne wybranie opcji "Pokaż wszystkie pliki" w Eksploratorze rozwiązań, aby wyświetlić plik XML.
Otwórz wiersz polecenia Visual Studio: w menu Start kliknij Wszystkie programy, kliknij Microsoft Visual Studio <Wersja Visual Studio>, kliknij Narzędzia Visual Studio, a następnie kliknij Wiersz polecenia Visual Studio (<Wersja Visual Studio>).
W wierszu polecenia przejdź do folderu zawierającego projekt SQL.
W wierszu polecenia wpisz następujące polecenie:
MSBuild /t:Rebuild MyDatabaseProject.sqlproj /p:BuildContributors=$(BuildContributors);ExampleContributors.ModelStatistics /p:ContributorArguments=$(ContributorArguments);GenerateModelStatistics=true;SortModelStatisticsBy=name;OutDir=.\;
Zastąp MyDatabaseProject nazwą projektu bazy danych, który chcesz skompilować. Jeśli projekt został zmieniony po jego ostatniej kompilacji, można użyć polecenia /t:Build zamiast /t:Rebuild.
W danych wyjściowych powinny zostać wyświetlone informacje kompilacji podobne do następujących:
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
Otwórz ModelStatistics.xml i sprawdź zawartość.
Zgłoszone wyniki są również utrwalane w pliku XML.
Następne kroki
Możesz utworzyć dodatkowe narzędzia do przetwarzania wyjściowego pliku XML. Jest to tylko jeden przykład współautora kompilacji. Możesz na przykład utworzyć współautora kompilacji, aby wygenerować plik słownika danych w ramach kompilacji.
Zobacz też
Dostosowywanie Kompilacji i Wdrażania Bazy Danych przy Użyciu Kontrybutorów Kompilacji i Wdrażania
przewodnik : rozszerzanie wdrożenia projektu bazy danych w celu przeanalizowania planu wdrożenia