From 2639477f85519253c345dc082f8c5e4cfdbbf6f3 Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Fri, 28 Jun 2019 09:52:37 -0700 Subject: [PATCH] Simplify Add-EFProvider & Add-EFDefaultConnectionFactory Part of #231 --- .../AddDefaultConnectionFactoryCommand.cs | 38 --- .../AddProviderCommand.cs | 39 --- .../ConfigFileFinder.cs | 31 --- .../ConfigFileManipulator.cs | 224 ------------------ .../ConfigFileProcessor.cs | 53 ----- .../ConnectionFactorySpecification.cs | 35 --- .../Extensions/ProjectItemExtensions.cs | 33 --- .../Extensions/XContainerExtensions.cs | 25 -- .../tools/EntityFramework.psm1 | 130 ++++++---- 9 files changed, 81 insertions(+), 527 deletions(-) delete mode 100644 src/EntityFramework.PowerShell/ConnectionFactoryConfig/AddDefaultConnectionFactoryCommand.cs delete mode 100644 src/EntityFramework.PowerShell/ConnectionFactoryConfig/AddProviderCommand.cs delete mode 100644 src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileFinder.cs delete mode 100644 src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileManipulator.cs delete mode 100644 src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileProcessor.cs delete mode 100644 src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConnectionFactorySpecification.cs delete mode 100644 src/EntityFramework.PowerShell/Migrations/Extensions/ProjectItemExtensions.cs delete mode 100644 src/EntityFramework.PowerShell/Migrations/Extensions/XContainerExtensions.cs diff --git a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/AddDefaultConnectionFactoryCommand.cs b/src/EntityFramework.PowerShell/ConnectionFactoryConfig/AddDefaultConnectionFactoryCommand.cs deleted file mode 100644 index 0d9b70219d..0000000000 --- a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/AddDefaultConnectionFactoryCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace System.Data.Entity.ConnectionFactoryConfig -{ - using System.Data.Entity.Migrations; - using System.Data.Entity.Utilities; - using System.Xml.Linq; - - internal class AddDefaultConnectionFactoryCommand : MigrationsDomainCommand - { - public AddDefaultConnectionFactoryCommand(string typeName, string[] constructorArguments) - { - // Using check because this is effecitively public surface since - // it is called by a PowerShell command. - Check.NotEmpty(typeName, "typeName"); - - Execute(() => Execute(typeName, constructorArguments)); - } - - public void Execute(string typeName, string[] constructorArguments) - { - DebugCheck.NotEmpty(typeName); - - var manipulator = new ConfigFileManipulator(); - var processor = new ConfigFileProcessor(); - - new ConfigFileFinder().FindConfigFiles( - Project.ProjectItems, - i => processor.ProcessConfigFile( - i, new Func[] - { - c => manipulator.AddOrUpdateConfigSection(c, GetType().Assembly.GetName().Version), - c => manipulator.AddOrUpdateConnectionFactoryInConfig( - c, new ConnectionFactorySpecification(typeName, constructorArguments)) - })); - } - } -} diff --git a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/AddProviderCommand.cs b/src/EntityFramework.PowerShell/ConnectionFactoryConfig/AddProviderCommand.cs deleted file mode 100644 index 33729e830d..0000000000 --- a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/AddProviderCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace System.Data.Entity.ConnectionFactoryConfig -{ - using System.Data.Entity.Migrations; - using System.Data.Entity.Utilities; - using System.Xml.Linq; - - internal class AddProviderCommand : MigrationsDomainCommand - { - public AddProviderCommand(string invariantName, string typeName) - { - // Using check because this is effecitively public surface since - // it is called by a PowerShell command. - Check.NotEmpty(invariantName, "invariantName"); - Check.NotEmpty(typeName, "typeName"); - - Execute(() => Execute(invariantName, typeName)); - } - - public void Execute(string invariantName, string typeName) - { - DebugCheck.NotEmpty(invariantName); - DebugCheck.NotEmpty(typeName); - - var manipulator = new ConfigFileManipulator(); - var processor = new ConfigFileProcessor(); - - new ConfigFileFinder().FindConfigFiles( - Project.ProjectItems, - i => processor.ProcessConfigFile( - i, new Func[] - { - c => manipulator.AddOrUpdateConfigSection(c, GetType().Assembly.GetName().Version), - c => manipulator.AddProviderToConfig(c, invariantName, typeName) - })); - } - } -} diff --git a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileFinder.cs b/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileFinder.cs deleted file mode 100644 index cf8bb83c5f..0000000000 --- a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileFinder.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace System.Data.Entity.ConnectionFactoryConfig -{ - using System.Data.Entity.Migrations.Extensions; - using System.Data.Entity.Utilities; - using EnvDTE; - - // - // Finds Visual Studio project items that are .config files. - // - internal class ConfigFileFinder - { - // - // Finds any item called "app.config" or "web.config" in the given list of project items and performs the given action for each. - // - public virtual void FindConfigFiles(ProjectItems items, Action action) - { - DebugCheck.NotNull(items); - DebugCheck.NotNull(action); - - foreach (ProjectItem projectItem in items) - { - if (projectItem.IsConfig()) - { - action(projectItem); - } - } - } - } -} diff --git a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileManipulator.cs b/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileManipulator.cs deleted file mode 100644 index 62afd552bb..0000000000 --- a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileManipulator.cs +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace System.Data.Entity.ConnectionFactoryConfig -{ - using System.Data.Entity.Migrations.Extensions; - using System.Data.Entity.Utilities; - using System.Globalization; - using System.Linq; - using System.Xml.Linq; - - // - // Manipulates the XML of .config files to add Entity Framework "defaultConnectionFactory" entries - // and to ensure that the "entityFramework" section is up-to-date with the current EF assembly - // version. - // - internal class ConfigFileManipulator - { - public const string ConfigurationElementName = "configuration"; - public const string EntityFrameworkElementName = "entityFramework"; - public const string DefaultConnectionFactoryElementName = "defaultConnectionFactory"; - public const string ParametersElementName = "parameters"; - public const string ParameterElementName = "parameter"; - public const string ConfigSectionsElementName = "configSections"; - public const string SectionElementName = "section"; - public const string ProvidersElementName = "providers"; - public const string ProviderElementName = "provider"; - - // - // Checks whether or not the given XML document representing a .config file contains - // an EntityFramework "defaultConnectionFactory" entry or not. If no entry is found then one - // is added for the given connection factory specification. - // - // An XML document representing the config file. - // Specifies the connection factory and constructor arguments to use. - // True if the document was modified; false if no change was made. - public virtual bool AddConnectionFactoryToConfig(XDocument config, ConnectionFactorySpecification specification) - { - DebugCheck.NotNull(config); - DebugCheck.NotNull(specification); - - var entityFramework = config - .GetOrCreateElement(ConfigurationElementName) - .GetOrCreateElement(EntityFrameworkElementName); - - if (entityFramework.Elements(DefaultConnectionFactoryElementName).Any()) - { - return false; - } - - var factoryElement = entityFramework - .GetOrCreateElement( - DefaultConnectionFactoryElementName, - new XAttribute("type", specification.ConnectionFactoryName)); - - AddFactoryArguments(factoryElement, specification); - - return true; - } - - // - // Sets the EntityFramework "defaultConnectionFactory" in the given XML document representing a - // .config file to use th given specification. This method differs from AddConnectionFactoryToConfig - // in that it always sets the entry to use the given specification even if it was already present - // and set to something else. - // - // An XML document representing the config file. - // Specifies the connection factory and constructor arguments to use. - // True if the document was modified; false if no change was made. - public virtual bool AddOrUpdateConnectionFactoryInConfig(XDocument config, ConnectionFactorySpecification specification) - { - DebugCheck.NotNull(config); - - var connectionFactoryElement = config - .GetOrCreateElement(ConfigurationElementName) - .GetOrCreateElement(EntityFrameworkElementName) - .GetOrCreateElement(DefaultConnectionFactoryElementName); - - var currentFactoryAttribute = connectionFactoryElement.Attribute("type"); - if (currentFactoryAttribute != null - && specification.ConnectionFactoryName.Equals(currentFactoryAttribute.Value, StringComparison.OrdinalIgnoreCase) - && FactoryArgumentsMatch(connectionFactoryElement, specification)) - { - return false; - } - - connectionFactoryElement.RemoveAll(); - connectionFactoryElement.Add(new XAttribute("type", specification.ConnectionFactoryName)); - - AddFactoryArguments(connectionFactoryElement, specification); - - return true; - } - - private void AddFactoryArguments(XElement factoryElement, ConnectionFactorySpecification specification) - { - if (specification.ConstructorArguments.Any()) - { - var parametersElement = factoryElement.GetOrCreateElement(ParametersElementName); - specification.ConstructorArguments.Each( - a => parametersElement.Add(new XElement(ParameterElementName, new XAttribute("value", a)))); - } - } - - private bool FactoryArgumentsMatch(XElement factoryElement, ConnectionFactorySpecification specification) - { - var parametersElement = factoryElement.Element(ParametersElementName); - var currentParameters = parametersElement == null - ? new string[0] - : parametersElement.Elements(ParameterElementName) - .Select(e => e.Attribute("value").Value); - - return currentParameters.SequenceEqual(specification.ConstructorArguments); - } - - public virtual bool AddProviderToConfig(XDocument config, string invariantName, string typeName) - { - DebugCheck.NotNull(config); - - var providersElement = config - .GetOrCreateElement(ConfigurationElementName) - .GetOrCreateElement(EntityFrameworkElementName) - .GetOrCreateElement(ProvidersElementName); - - var invariantAttribute = new XAttribute("invariantName", invariantName); - var modificationMade = false; - - // Check if element exists at end - var providerElement = providersElement.Elements(ProviderElementName).LastOrDefault(); - if (providerElement == null - || providerElement.Attributes(invariantAttribute.Name).All(a => a.Value != invariantAttribute.Value)) - { - // Check if element exists and if so move it to end - providerElement = providersElement - .Elements(ProviderElementName) - .FirstOrDefault(e => e.Attributes(invariantAttribute.Name).Any(a => a.Value == invariantAttribute.Value)); - - if (providerElement != null) - { - providerElement.Remove(); - } - else - { - providerElement = new XElement(ProviderElementName, invariantAttribute); - } - - providersElement.Add(providerElement); - modificationMade = true; - } - - var currentTypeAttribute = providerElement.Attribute("type"); - if (currentTypeAttribute == null) - { - providerElement.Add(new XAttribute("type", typeName)); - modificationMade = true; - } - else if (!typeName.Equals(currentTypeAttribute.Value, StringComparison.OrdinalIgnoreCase)) - { - currentTypeAttribute.Value = typeName; - modificationMade = true; - } - - return modificationMade; - } - - // - // Ensures that the config file has a defined "entityFramework" section and that it references - // the current version of the EntityFramework.dll assembly. - // - // An XML document representing the config file. - // The version of EntityFramework.dll to use. - // True if the document was modified; false if no change was made. - public virtual bool AddOrUpdateConfigSection(XDocument config, Version entityFrameworkVersion) - { - DebugCheck.NotNull(config); - DebugCheck.NotNull(entityFrameworkVersion); - - var configSections = config - .GetOrCreateElement(ConfigurationElementName) - .GetOrCreateElement(ConfigSectionsElementName); - - var typeAttribute = configSections - .Elements(SectionElementName) - .Where(e => (string)e.Attribute("name") == EntityFrameworkElementName) - .SelectMany(e => e.Attributes("type")) - .FirstOrDefault(); - - // Hard coding this so that we don't need to load EntityFramework.dll to get it. - const string entityFrameworkSectionName = - "System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version={0}, Culture=neutral, PublicKeyToken=b77a5c561934e089"; - - var efSectionTypeName = string.Format( - CultureInfo.InvariantCulture, entityFrameworkSectionName, - entityFrameworkVersion); - - if (typeAttribute != null) - { - var sectionElement = typeAttribute.Parent; - var requirePermissionAttribute = sectionElement.Attribute("requirePermission"); - - if (efSectionTypeName.Equals(typeAttribute.Value, StringComparison.InvariantCultureIgnoreCase) - && requirePermissionAttribute != null) - { - return false; - } - - typeAttribute.Value = efSectionTypeName; - - if (requirePermissionAttribute == null) - { - sectionElement.Add(new XAttribute("requirePermission", false)); - } - } - else - { - configSections.Add( - new XElement( - SectionElementName, new XAttribute("name", EntityFrameworkElementName), - new XAttribute("type", efSectionTypeName), new XAttribute("requirePermission", false))); - } - - return true; - } - } -} diff --git a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileProcessor.cs b/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileProcessor.cs deleted file mode 100644 index f646252f66..0000000000 --- a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConfigFileProcessor.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace System.Data.Entity.ConnectionFactoryConfig -{ - using System.Collections.Generic; - using System.Data.Entity.Migrations.Resources; - using System.Data.Entity.Utilities; - using System.IO; - using System.Xml.Linq; - using EnvDTE; - - // - // Processes a .config file to possibly add an "defaultConnectionFactory" entry and then - // save the file, if possible. - // - internal class ConfigFileProcessor - { - // - // Loads XML from the .config file, manipulates it to possibly add an "defaultConnectionFactory" entry - // and then attempts to save the file. - // - // - // If the file cannot be saved then it is not saved and an exception is thrown. Under normal use this should not happen - // because NuGet will have ensured that the file is writable. It would be possible to try to do things like try check out - // the file from source control, but it doesn't seem like this is valuable enough to implement given it will not normally be used. - // - public virtual void ProcessConfigFile(ProjectItem configItem, IEnumerable> manipulators) - { - DebugCheck.NotNull(configItem); - - var fileName = configItem.FileNames[0]; - var config = XDocument.Load(fileName); - - var fileModified = false; - foreach (var manipulator in manipulators) - { - fileModified = manipulator(config) || fileModified; - } - - if (fileModified) - { - try - { - config.Save(fileName); - } - catch (Exception ex) - { - throw new IOException(Strings.SaveConnectionFactoryInConfigFailed(fileName), ex); - } - } - } - } -} diff --git a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConnectionFactorySpecification.cs b/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConnectionFactorySpecification.cs deleted file mode 100644 index 2f5f1e6530..0000000000 --- a/src/EntityFramework.PowerShell/ConnectionFactoryConfig/ConnectionFactorySpecification.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace System.Data.Entity.ConnectionFactoryConfig -{ - using System.Collections.Generic; - using System.Data.Entity.Utilities; - using System.Linq; - - // - // Represents a specification for the default connection factory to be set into a config file. - // - internal class ConnectionFactorySpecification - { - private readonly string _connectionFactoryName; - private readonly IEnumerable _constructorArguments; - - public ConnectionFactorySpecification(string connectionFactoryName, params string[] constructorArguments) - { - DebugCheck.NotEmpty(connectionFactoryName); - - _connectionFactoryName = connectionFactoryName; - _constructorArguments = constructorArguments ?? Enumerable.Empty(); - } - - public string ConnectionFactoryName - { - get { return _connectionFactoryName; } - } - - public IEnumerable ConstructorArguments - { - get { return _constructorArguments; } - } - } -} diff --git a/src/EntityFramework.PowerShell/Migrations/Extensions/ProjectItemExtensions.cs b/src/EntityFramework.PowerShell/Migrations/Extensions/ProjectItemExtensions.cs deleted file mode 100644 index 3dafdc0842..0000000000 --- a/src/EntityFramework.PowerShell/Migrations/Extensions/ProjectItemExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace System.Data.Entity.Migrations.Extensions -{ - using System.Data.Entity.Utilities; - using EnvDTE; - - // - // Extension methods for the Visual Studio ProjectItem interface. - // - internal static class ProjectItemExtensions - { - // - // Returns true if the project item is named either "app.config" or "web.config". - // - public static bool IsConfig(this ProjectItem item) - { - DebugCheck.NotNull(item); - - return IsNamed(item, "app.config") || IsNamed(item, "web.config"); - } - - // - // Returns true if the project item has the given name, with case ignored. - // - public static bool IsNamed(this ProjectItem item, string name) - { - DebugCheck.NotNull(item); - - return item.Name.Equals(name, StringComparison.OrdinalIgnoreCase); - } - } -} diff --git a/src/EntityFramework.PowerShell/Migrations/Extensions/XContainerExtensions.cs b/src/EntityFramework.PowerShell/Migrations/Extensions/XContainerExtensions.cs deleted file mode 100644 index 5b2bee284a..0000000000 --- a/src/EntityFramework.PowerShell/Migrations/Extensions/XContainerExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -namespace System.Data.Entity.Migrations.Extensions -{ - using System.Data.Entity.Utilities; - using System.Xml.Linq; - - internal static class XContainerExtensions - { - public static XElement GetOrCreateElement( - this XContainer container, string elementName, params XAttribute[] attributes) - { - DebugCheck.NotNull(container); - DebugCheck.NotEmpty(elementName); - - var element = container.Element(elementName); - if (element == null) - { - element = new XElement(elementName, attributes); - container.Add(element); - } - return element; - } - } -} diff --git a/src/NuGet/EntityFramework/tools/EntityFramework.psm1 b/src/NuGet/EntityFramework/tools/EntityFramework.psm1 index c93cebb575..ef59ce5ade 100644 --- a/src/NuGet/EntityFramework/tools/EntityFramework.psm1 +++ b/src/NuGet/EntityFramework/tools/EntityFramework.psm1 @@ -57,36 +57,25 @@ function Add-EFProvider [string] $TypeName ) - if (!(Check-Project $project)) - { - return - } + $configPath = GetConfigPath($Project) + if (!$configPath) + { + return + } - $runner = New-EFConfigRunner $Project + [xml] $configXml = Get-Content $configPath - try - { - Invoke-RunnerCommand $runner System.Data.Entity.ConnectionFactoryConfig.AddProviderCommand @( $InvariantName, $TypeName ) - $error = Get-RunnerError $runner + $providers = $configXml.configuration.entityFramework.providers - if ($error) - { - if ($knownExceptions -notcontains $error.TypeName) - { - Write-Host $error.StackTrace - } - else - { - Write-Verbose $error.StackTrace - } + $providers.provider | + ?{ $_.invariantName -eq $InvariantName } | + %{ $providers.RemoveChild($_) | Out-Null } - throw $error.Message - } - } - finally - { - Remove-Runner $runner - } + $provider = $providers.AppendChild($configXml.CreateElement('provider')) + $provider.SetAttribute('invariantName', $InvariantName) + $provider.SetAttribute('type', $TypeName) + + $configXml.Save($configPath) } <# @@ -133,36 +122,36 @@ function Add-EFDefaultConnectionFactory [string[]] $ConstructorArguments ) - if (!(Check-Project $project)) - { - return - } + $configPath = GetConfigPath($Project) + if (!$configPath) + { + return + } - $runner = New-EFConfigRunner $Project + [xml] $configXml = Get-Content $configPath - try + $entityFramework = $configXml.configuration.entityFramework + $defaultConnectionFactory = $entityFramework.defaultConnectionFactory + if ($defaultConnectionFactory) { - Invoke-RunnerCommand $runner System.Data.Entity.ConnectionFactoryConfig.AddDefaultConnectionFactoryCommand @( $TypeName, $ConstructorArguments ) - $error = Get-RunnerError $runner + $entityFramework.RemoveChild($defaultConnectionFactory) | Out-Null + } + $defaultConnectionFactory = $entityFramework.AppendChild($configXml.CreateElement('defaultConnectionFactory')) - if ($error) - { - if ($knownExceptions -notcontains $error.TypeName) - { - Write-Host $error.StackTrace - } - else - { - Write-Verbose $error.StackTrace - } + $defaultConnectionFactory.SetAttribute('type', $TypeName) - throw $error.Message + if ($ConstructorArguments) + { + $parameters = $defaultConnectionFactory.AppendChild($configXml.CreateElement('parameters')) + + foreach ($constructorArgument in $ConstructorArguments) + { + $parameter = $parameters.AppendChild($configXml.CreateElement('parameter')) + $parameter.SetAttribute('value', $constructorArgument) } } - finally - { - Remove-Runner $runner - } + + $configXml.Save($configPath) } <# @@ -963,4 +952,47 @@ function Hint-Downgrade ($name) { } } +function GetConfigPath($project) +{ + $solution = Get-VSService 'Microsoft.VisualStudio.Shell.Interop.SVsSolution' 'Microsoft.VisualStudio.Shell.Interop.IVsSolution' + + $hierarchy = $null + $hr = $solution.GetProjectOfUniqueName($project.UniqueName, [ref] $hierarchy) + [Runtime.InteropServices.Marshal]::ThrowExceptionForHR($hr) + + $aggregatableProject = Get-Interface $hierarchy 'Microsoft.VisualStudio.Shell.Interop.IVsAggregatableProject' + if (!$aggregatableProject) + { + $projectTypes = $project.Kind + } + else + { + $projectTypeGuids = $null + $hr = $aggregatableProject.GetAggregateProjectTypeGuids([ref] $projectTypeGuids) + [Runtime.InteropServices.Marshal]::ThrowExceptionForHR($hr) + + $projectTypes = $projectTypeGuids.Split(';') + } + + $configFileName = 'app.config' + foreach ($projectType in $projectTypes) + { + if ('{349C5851-65DF-11DA-9384-00065B846F21}', '{E24C65DC-7377-472B-9ABA-BC803B73C61A}' -contains $projectType) + { + $configFileName = 'web.config' + break + } + } + + try + { + $configPath = $project.ProjectItems.Item($configFileName).Properties.Item('FullPath').Value + } + catch + { + } + + return $configPath +} + Export-ModuleMember @( 'Enable-Migrations', 'Add-Migration', 'Update-Database', 'Get-Migrations', 'Add-EFProvider', 'Add-EFDefaultConnectionFactory') -Variable InitialDatabase