diff --git a/ChangeLog/7.1.2_dev.txt b/ChangeLog/7.1.2_dev.txt index 78b15d4fc6..68137d651a 100644 --- a/ChangeLog/7.1.2_dev.txt +++ b/ChangeLog/7.1.2_dev.txt @@ -1 +1,9 @@ -[main] Addressed issue when cycles in Entity dependency graph were not detected \ No newline at end of file +[main] Addressed issue when cycles in Entity dependency graph were not detected +[main] Intoduced DomainConfuguration.ExtensionConfigurations as unified configuration storage for extensions' configurations +[main] DomainConfiguration.Load() method now has overloads which gets abstractions from Microsoft.Extensions.Configuration API +[localization] LocalizationConfiguration.Load() method now has overloads which gets abstractions from Microsoft.Extensions.Configuration API +[localization] ConfigureLocalizationExtension() extensions for DomainConfiguration are added, check ReadMe/documentation for examples of usage +[reprocessing] ReprocessingConfiguration.Load() method now has overloads which gets abstractions from Microsoft.Extensions.Configuration API +[reprocessing] ConfigureReprocessingExtension() extensions for DomainConfiguration are added, check ReadMe/documentation for examples of usage +[security] SecurityConfiguration.Load() method now has overloads which gets abstractions from Microsoft.Extensions.Configuration API +[security] ConfigureSecurityExtension() extensions for DomainConfiguration are added, check ReadMe/documentation for examples of usage \ No newline at end of file diff --git a/Extensions/TestCommon/ModernConfigurationTestBase.cs b/Extensions/TestCommon/ModernConfigurationTestBase.cs new file mode 100644 index 0000000000..a0faf76c24 --- /dev/null +++ b/Extensions/TestCommon/ModernConfigurationTestBase.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using NUnit.Framework; + +namespace TestCommon +{ + public abstract class MicrosoftConfigurationTestBase + { + protected enum ConfigTypes + { + Json, + Xml, + } + + protected IConfigurationRoot configurationRoot; + + protected abstract ConfigTypes ConfigFormat { get; } + + protected abstract void AddConfigurationFile(IConfigurationBuilder configurationBuilder); + + [OneTimeSetUp] + public virtual void BeforeAllTestsSetUp() + { + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.SetBasePath(Directory.GetCurrentDirectory()); + AddConfigurationFile(configurationBuilder); + configurationRoot = (ConfigurationRoot) configurationBuilder.Build(); + } + + protected void IgnoreIfXml() + { + if (ConfigFormat == ConfigTypes.Xml) + throw new IgnoreException("Not valid for Xml format"); + } + protected void IgnoreIfJson() + { + if (ConfigFormat == ConfigTypes.Json) + throw new IgnoreException("Not valid for JSON format"); + } + + protected IConfigurationSection GetAndCheckConfigurationSection(string sectionName) + { + var section = configurationRoot.GetSection(sectionName); + Assert.That(section, Is.Not.Null); + return section; + } + } +} diff --git a/Extensions/TestCommon/TestCommon.csproj b/Extensions/TestCommon/TestCommon.csproj index e23f0c4aef..16a3f08891 100644 --- a/Extensions/TestCommon/TestCommon.csproj +++ b/Extensions/TestCommon/TestCommon.csproj @@ -8,16 +8,13 @@ $(ExtensionsKeyFile) - - - - - - + + + diff --git a/Extensions/Xtensive.Orm.BulkOperations/NugetContent/ReadMe.md b/Extensions/Xtensive.Orm.BulkOperations/NugetContent/ReadMe.md index 9fccda4329..2500340342 100644 --- a/Extensions/Xtensive.Orm.BulkOperations/NugetContent/ReadMe.md +++ b/Extensions/Xtensive.Orm.BulkOperations/NugetContent/ReadMe.md @@ -8,7 +8,7 @@ to server-side UPDATE or DELETE commands. Prerequisites ------------- -DataObjects.Net 7.0.x (http://dataobjects.net) +DataObjects.Net 7.1.x (http://dataobjects.net) Examples of usage diff --git a/Extensions/Xtensive.Orm.Localization.Tests/LocalizationSettings.config b/Extensions/Xtensive.Orm.Localization.Tests/LocalizationSettings.config new file mode 100644 index 0000000000..8f439a9e13 --- /dev/null +++ b/Extensions/Xtensive.Orm.Localization.Tests/LocalizationSettings.config @@ -0,0 +1,135 @@ + + + + + + + + + + + + + es-ES + + + + ololo + + + + + + + + + es-ES + + + + es-ES + + + + es-ES + + + + es-ES + + + + + + + + + es-ES + + + + es-ES + + + + es-ES + + + + es-ES + + + + + + + + + + + + es-ES + + + + + + ololo + + + + + + es-ES + + + + + + es-ES + + + + + + es-ES + + + + + + es-ES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Localization.Tests/MicrosoftConfigurationTests.cs b/Extensions/Xtensive.Orm.Localization.Tests/MicrosoftConfigurationTests.cs new file mode 100644 index 0000000000..9bb8d2b77c --- /dev/null +++ b/Extensions/Xtensive.Orm.Localization.Tests/MicrosoftConfigurationTests.cs @@ -0,0 +1,347 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System.Globalization; +using System.Threading; +using Microsoft.Extensions.Configuration; +using NUnit.Framework; +using Xtensive.Orm.Localization.Configuration; + +namespace Xtensive.Orm.Localization.Tests.Configuration +{ + public sealed class JsonConfigurationTest : MicrosoftConfigurationTest + { + protected override ConfigTypes ConfigFormat => ConfigTypes.Json; + + protected override void AddConfigurationFile(IConfigurationBuilder configurationBuilder) + { + _ = configurationBuilder.AddJsonFile("localizationsettings.json"); + } + } + + public sealed class XmlConfigurationTest : MicrosoftConfigurationTest + { + protected override ConfigTypes ConfigFormat => ConfigTypes.Xml; + + protected override void AddConfigurationFile(IConfigurationBuilder configurationBuilder) + { + _ = configurationBuilder.AddXmlFile("LocalizationSettings.config"); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeEmptyNameTest(bool useRoot) + { + var locConfig = LoadConfiguration("Xtensive.Orm.Localization.NameAttribute.NameEmpty", useRoot); + CheckConfigurationIsDefault(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeAbsentTest(bool useRoot) + { + var locConfig = LoadConfiguration("Xtensive.Orm.Localization.NameAttribute.None", useRoot); + CheckConfigurationIsDefault(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeDefinedTest(bool useRoot) + { + var locConfig = LoadConfiguration("Xtensive.Orm.Localization.NameAttribute.Defined", useRoot); + Assert.That(locConfig, Is.Not.Null); + Assert.That(locConfig.DefaultCulture, Is.EqualTo(expectedCulture)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeFaultyValueTest(bool useRoot) + { + var locConfig = LoadConfiguration("Xtensive.Orm.Localization.NameAttribute.FaultyValue", useRoot); + CheckConfigurationIsDefault(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeLowCase(bool useRoot) + { + var locConfig = LoadConfiguration("Xtensive.Orm.Localization.NameAttribute.LC", useRoot); + Assert.That(locConfig, Is.Not.Null); + Assert.That(locConfig.DefaultCulture, Is.EqualTo(expectedCulture)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeUpperCase(bool useRoot) + { + var locConfig = LoadConfiguration("Xtensive.Orm.Localization.NameAttribute.UC", useRoot); + Assert.That(locConfig, Is.Not.Null); + Assert.That(locConfig.DefaultCulture, Is.EqualTo(expectedCulture)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributePascalCase(bool useRoot) + { + var locConfig = LoadConfiguration("Xtensive.Orm.Localization.NameAttribute.PC", useRoot); + Assert.That(locConfig, Is.Not.Null); + Assert.That(locConfig.DefaultCulture, Is.EqualTo(expectedCulture)); + } + } + + [TestFixture] + public abstract class MicrosoftConfigurationTest : TestCommon.MicrosoftConfigurationTestBase + { + protected readonly CultureInfo defaultCulture = new CultureInfo("en-US"); + protected readonly CultureInfo expectedCulture = new CultureInfo("es-ES"); + + private CultureInfo resetCulture; + + public override void BeforeAllTestsSetUp() + { + base.BeforeAllTestsSetUp(); + + } + + protected LocalizationConfiguration LoadConfiguration(string sectionName, bool useRoot) + { + return useRoot + ? LocalizationConfiguration.Load(configurationRoot, sectionName) + : LocalizationConfiguration.Load(configurationRoot.GetSection(sectionName)); + } + + [SetUp] + public void SetUp() + { + resetCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + } + + [TearDown] + public void TearDown() + { + Thread.CurrentThread.CurrentCulture = resetCulture; + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptySectionCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.Empty", useRoot); + CheckConfigurationIsDefault(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptyName(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameEmpty", useRoot); + CheckConfigurationIsDefault(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameDefined(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameDefined", useRoot); + Assert.That(locConfig, Is.Not.Null); + Assert.That(locConfig.DefaultCulture, Is.EqualTo(expectedCulture)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void FaultyValue(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.FaultyValue", useRoot); + CheckConfigurationIsDefault(locConfig); + } + + #region Naming + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInLowCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.Naming.LC", useRoot); + ValidateNamingConfigurationResults(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInUpperCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.Naming.UC", useRoot); + ValidateNamingConfigurationResults(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInCamelCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.Naming.CC", useRoot); + ValidateNamingConfigurationResults(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInPascalCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.Naming.PC", useRoot); + ValidateNamingConfigurationResults(locConfig); + } + + private void ValidateNamingConfigurationResults(LocalizationConfiguration locConfig) + { + Assert.That(locConfig, Is.Not.Null); + Assert.That(locConfig.DefaultCulture, Is.EqualTo(expectedCulture)); + } + + #endregion + + #region mistype cases + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInLowCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.Mistype.LC", useRoot); + ValidateMistypeConfigurationResults(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInUpperCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.Mistype.UC", useRoot); + ValidateMistypeConfigurationResults(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInCamelCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.Mistype.CC", useRoot); + ValidateMistypeConfigurationResults(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInPascalCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.Mistype.PC", useRoot); + ValidateMistypeConfigurationResults(locConfig); + } + + private void ValidateMistypeConfigurationResults(LocalizationConfiguration locConfig) + { + CheckConfigurationIsDefault(locConfig); + } + + #endregion + + #region Name as node + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NoNameNodes(bool useRoot) + { + IgnoreIfXml(); + + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameNode.Empty", useRoot); + CheckConfigurationIsDefault(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodeIsEmpty(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameNode.NameEmpty", useRoot); + CheckConfigurationIsDefault(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DefinedNameNode(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameNode.Espaniol", useRoot); + Assert.That(locConfig, Is.Not.Null); + Assert.That(locConfig.DefaultCulture, Is.EqualTo(expectedCulture)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void FaultyNameNodeValue(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameNode.FaultyValue", useRoot); + CheckConfigurationIsDefault(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodeInLowCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameNode.Naming.LC", useRoot); + ValidateNamingConfigurationResults(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodeInUpperCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameNode.Naming.UC", useRoot); + ValidateNamingConfigurationResults(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodeInCamelCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameNode.Naming.CC", useRoot); + ValidateNamingConfigurationResults(locConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodeInPascalCase(bool useRoot) + { + var locConfig = LoadConfiguration($"Xtensive.Orm.Localization.{ConfigFormat}.NameNode.Naming.PC", useRoot); + ValidateNamingConfigurationResults(locConfig); + } + + #endregion + + protected void CheckConfigurationIsDefault(LocalizationConfiguration locConfig) + { + Assert.That(locConfig, Is.Not.Null); + Assert.That(locConfig.DefaultCulture, Is.EqualTo(defaultCulture)); + } + } +} diff --git a/Extensions/Xtensive.Orm.Localization.Tests/Xtensive.Orm.Localization.Tests.csproj b/Extensions/Xtensive.Orm.Localization.Tests/Xtensive.Orm.Localization.Tests.csproj index e0eba70610..79c0f9f637 100644 --- a/Extensions/Xtensive.Orm.Localization.Tests/Xtensive.Orm.Localization.Tests.csproj +++ b/Extensions/Xtensive.Orm.Localization.Tests/Xtensive.Orm.Localization.Tests.csproj @@ -8,6 +8,9 @@ + + + @@ -20,4 +23,16 @@ + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Localization.Tests/localizationsettings.json b/Extensions/Xtensive.Orm.Localization.Tests/localizationsettings.json new file mode 100644 index 0000000000..9fe0bc835b --- /dev/null +++ b/Extensions/Xtensive.Orm.Localization.Tests/localizationsettings.json @@ -0,0 +1,80 @@ +{ + "Xtensive.Orm.Localization.Json.Empty": { + + }, + + "Xtensive.Orm.Localization.Json.NameEmpty": { + "DefaultCulture": "" + }, + + "Xtensive.Orm.Localization.Json.NameDefined": { + "DefaultCulture": "es-ES" + }, + + "Xtensive.Orm.Localization.Json.FaultyValue": { + "DefaultCulture": "ololo" + }, + + "Xtensive.Orm.Localization.Json.Naming.LC": { + "defaultculture": "es-ES" + }, + "Xtensive.Orm.Localization.Json.Naming.UC": { + "DEFAULTCULTURE": "es-ES" + }, + "Xtensive.Orm.Localization.Json.Naming.CC": { + "defaultCulture": "es-ES" + }, + "Xtensive.Orm.Localization.Json.Naming.PC": { + "DefaultCulture": "es-ES" + }, + + "Xtensive.Orm.Localization.Json.Mistype.LC": { + "defailtculture": "es-ES" + }, + "Xtensive.Orm.Localization.Json.Mistype.UC": { + "DEFAILTCULTURE": "es-ES" + }, + "Xtensive.Orm.Localization.Json.Mistype.CC": { + "defailtCulture": "es-ES" + }, + "Xtensive.Orm.Localization.Json.Mistype.PC": { + "DefailtCulture": "es-ES" + }, + + + "Xtensive.Orm.Localization.Json.NameNode.Empty": { + "DefaultCulture": { + } + }, + + "Xtensive.Orm.Localization.Json.NameNode.NameEmpty": { + "DefaultCulture": { + "Name": "" + } + }, + + "Xtensive.Orm.Localization.Json.NameNode.Espaniol": { + "DefaultCulture": { + "Name": "es-ES" + } + }, + + "Xtensive.Orm.Localization.Json.NameNode.FaultyValue": { + "DefaultCulture": { + "Name": "ololo" + } + }, + + "Xtensive.Orm.Localization.Json.NameNode.Naming.LC": { + "DefaultCulture": { "name": "es-ES" } + }, + "Xtensive.Orm.Localization.Json.NameNode.Naming.UC": { + "DefaultCulture": { "NAME": "es-ES" } + }, + "Xtensive.Orm.Localization.Json.NameNode.Naming.CC": { + "DefaultCulture": { "naMe": "es-ES" } + }, + "Xtensive.Orm.Localization.Json.NameNode.Naming.PC": { + "DefaultCulture": { "NaMe": "es-ES" } + } +} diff --git a/Extensions/Xtensive.Orm.Localization/Configuration/Elements/ConfigurationSection.cs b/Extensions/Xtensive.Orm.Localization/Configuration/Elements/ConfigurationSection.cs index c13ec91716..8349378aed 100644 --- a/Extensions/Xtensive.Orm.Localization/Configuration/Elements/ConfigurationSection.cs +++ b/Extensions/Xtensive.Orm.Localization/Configuration/Elements/ConfigurationSection.cs @@ -18,6 +18,7 @@ public class ConfigurationSection : System.Configuration.ConfigurationSection /// Gets default section name for security configuration. /// Value is "Xtensive.Orm.Localization". /// + [Obsolete("Use Localization.DefaultSectionName instead.")] public static readonly string DefaultSectionName = "Xtensive.Orm.Localization"; private const string DefaultCultureElementName = "defaultCulture"; diff --git a/Extensions/Xtensive.Orm.Localization/Configuration/LocalizationConfiguration.cs b/Extensions/Xtensive.Orm.Localization/Configuration/LocalizationConfiguration.cs index aab701d997..6b84a40dd2 100644 --- a/Extensions/Xtensive.Orm.Localization/Configuration/LocalizationConfiguration.cs +++ b/Extensions/Xtensive.Orm.Localization/Configuration/LocalizationConfiguration.cs @@ -1,13 +1,17 @@ -// Copyright (C) 2011 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2012-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Dmitri Maximov // Created: 2012.07.06 using System; using System.Configuration; using System.Globalization; +using System.Linq; using System.Threading; +using Microsoft.Extensions.Configuration; +using Xtensive.Core; +using Xtensive.Orm.Configuration; namespace Xtensive.Orm.Localization.Configuration { @@ -15,7 +19,7 @@ namespace Xtensive.Orm.Localization.Configuration /// The configuration of the localization extension. /// [Serializable] - public class LocalizationConfiguration + public class LocalizationConfiguration : ConfigurationBase { /// /// Default SectionName value: @@ -23,11 +27,35 @@ public class LocalizationConfiguration /// public const string DefaultSectionName = "Xtensive.Orm.Localization"; + private CultureInfo culture; + /// /// Gets or sets the default culture. /// /// The default culture. - public CultureInfo DefaultCulture { get; private set; } + public CultureInfo DefaultCulture { + get => culture; + internal set { + EnsureNotLocked(); + culture = value; + } + } + + /// + protected override LocalizationConfiguration CreateClone() => new LocalizationConfiguration(); + + /// + protected override void CopyFrom(ConfigurationBase source) + { + base.CopyFrom(source); + + var nativeConfig = source as LocalizationConfiguration; + nativeConfig.DefaultCulture = DefaultCulture; + } + + /// + public override LocalizationConfiguration Clone() => (LocalizationConfiguration) base.Clone(); + /// /// Loads the @@ -88,8 +116,8 @@ private static LocalizationConfiguration GetConfigurationFromSection(Configurati var result = new LocalizationConfiguration(); result.DefaultCulture = Thread.CurrentThread.CurrentCulture; - string cultureName = configurationSection==null - ? string.Empty + string cultureName = configurationSection == null + ? string.Empty : configurationSection.DefaultCulture.Name; if (string.IsNullOrEmpty(cultureName)) return result; @@ -98,10 +126,58 @@ private static LocalizationConfiguration GetConfigurationFromSection(Configurati var culture = new CultureInfo(cultureName); result.DefaultCulture = culture; } - catch (CultureNotFoundException) - { + catch (CultureNotFoundException) { } return result; } + + /// + /// Loads from given configuration section of . + /// If section name is not provided is used. + /// + /// of sections. + /// Custom section name to load from. + /// Loaded configuration or default configuration if loading failed for some reason. + public static LocalizationConfiguration Load(IConfiguration configuration, string sectionName = null) + { + ArgumentValidator.EnsureArgumentNotNull(configuration, nameof(configuration)); + + if (configuration is IConfigurationRoot configurationRoot) { + return new LocalizationConfigurationReader().Read(configurationRoot, sectionName ?? DefaultSectionName); + } + else if (configuration is IConfigurationSection configurationSection) { + return sectionName.IsNullOrEmpty() + ? new LocalizationConfigurationReader().Read(configurationSection) + : new LocalizationConfigurationReader().Read(configurationSection.GetSection(sectionName)); + } + + throw new NotSupportedException("Type of configuration is not supported."); + } + + /// + /// Loads from given configuration section. + /// + /// to load from. + /// Loaded configuration or default configuration if loading failed for some reason. + public static LocalizationConfiguration Load(IConfigurationSection configurationSection) + { + ArgumentValidator.EnsureArgumentNotNull(configurationSection, nameof(configurationSection)); + + return new LocalizationConfigurationReader().Read(configurationSection); + } + + /// + /// Loads from given configuration section of . + /// If section name is not provided is used. + /// + /// of sections. + /// Custom section name to load from. + /// Loaded configuration or default configuration if loading failed for some reason. + public static LocalizationConfiguration Load(IConfigurationRoot configurationRoot, string sectionName = null) + { + ArgumentValidator.EnsureArgumentNotNull(configurationRoot, nameof(configurationRoot)); + + return new LocalizationConfigurationReader().Read(configurationRoot, sectionName ?? DefaultSectionName); + } } } \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Localization/Configuration/LocalizationConfigurationReader.cs b/Extensions/Xtensive.Orm.Localization/Configuration/LocalizationConfigurationReader.cs new file mode 100644 index 0000000000..0b7fce4531 --- /dev/null +++ b/Extensions/Xtensive.Orm.Localization/Configuration/LocalizationConfigurationReader.cs @@ -0,0 +1,67 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Globalization; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Configuration; +using Xtensive.Core; +using Xtensive.Orm.Configuration; + +namespace Xtensive.Orm.Localization.Configuration +{ + internal sealed class LocalizationConfigurationReader : IConfigurationSectionReader + { + private const string DefaultCultureElementName = "DefaultCulture"; + private const string CultureNameAttributeName = "name"; + + public LocalizationConfiguration Read(IConfigurationSection configurationSection) => ReadInternal(configurationSection); + + public LocalizationConfiguration Read(IConfigurationSection configurationSection, string nameOfConfiguration) => + throw new NotSupportedException(); + + public LocalizationConfiguration Read(IConfigurationRoot configurationRoot) => + Read(configurationRoot, LocalizationConfiguration.DefaultSectionName); + + public LocalizationConfiguration Read(IConfigurationRoot configurationRoot, string sectionName) + { + var section = configurationRoot.GetSection(sectionName); + return ReadInternal(section); + } + + public LocalizationConfiguration Read(IConfigurationRoot configurationRoot, string sectionName, string nameOfConfiguration) => + throw new NotSupportedException(); + + private LocalizationConfiguration ReadInternal(IConfigurationSection configurationSection) + { + var defaultCultureSection = configurationSection.GetSection(DefaultCultureElementName); + var defaultCulture = Thread.CurrentThread.CurrentCulture; + if (defaultCultureSection == null) { + return new LocalizationConfiguration() { DefaultCulture = defaultCulture }; + } + + var cultureName = defaultCultureSection.Value; + if (cultureName == null) { + cultureName = defaultCultureSection.GetSection(CultureNameAttributeName)?.Value; + if (cultureName == null) { + var children = defaultCultureSection.GetChildren().ToList(); + if (children.Count > 0) { + cultureName = children[0].GetSection(CultureNameAttributeName).Value; + } + } + } + if(!cultureName.IsNullOrEmpty()) { + try { + defaultCulture = CultureInfo.GetCultureInfo(cultureName); + } + catch(CultureNotFoundException) { + // swallow it, this is mark wrong culture name; + } + } + + return new LocalizationConfiguration() { DefaultCulture = defaultCulture }; + } + } +} \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Localization/DomainConfugurationExtensions.cs b/Extensions/Xtensive.Orm.Localization/DomainConfugurationExtensions.cs new file mode 100644 index 0000000000..e63b2b0c74 --- /dev/null +++ b/Extensions/Xtensive.Orm.Localization/DomainConfugurationExtensions.cs @@ -0,0 +1,130 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using Microsoft.Extensions.Configuration; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Localization.Configuration; + +namespace Xtensive.Orm.Localization +{ + /// + /// Contains extensions for DomainConfiguration that help to configure the extension. + /// + public static class DomainConfugurationLocalizationExtensions + { + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration) + => ConfigureLocalizationExtension(domainConfiguration, LocalizationConfiguration.Load()); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Section name. + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration, + string configurationSectionName) => + ConfigureLocalizationExtension(domainConfiguration, LocalizationConfiguration.Load(configurationSectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration, + System.Configuration.Configuration configuration) => + ConfigureLocalizationExtension(domainConfiguration, LocalizationConfiguration.Load(configuration)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// Section in + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration, + System.Configuration.Configuration configuration, string sectionName) => + ConfigureLocalizationExtension(domainConfiguration, LocalizationConfiguration.Load(configuration, sectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration, + IConfiguration configuration) => + ConfigureLocalizationExtension(domainConfiguration, LocalizationConfiguration.Load(configuration)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// Section in + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration, + IConfiguration configuration, string sectionName = null) => + ConfigureLocalizationExtension(domainConfiguration, LocalizationConfiguration.Load(configuration, sectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration, + IConfigurationRoot configurationRoot) => + ConfigureLocalizationExtension(domainConfiguration, LocalizationConfiguration.Load(configurationRoot)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// Section in + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration, + IConfigurationRoot configurationRoot, string sectionName) => + ConfigureLocalizationExtension(domainConfiguration, LocalizationConfiguration.Load(configurationRoot, sectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration section to load from. + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration, + IConfigurationSection configurationSection) => + ConfigureLocalizationExtension(domainConfiguration, LocalizationConfiguration.Load(configurationSection)); + + /// + /// Configures the extension with given localization configuration instance. + /// + /// Domain configuration. + /// Localization configuration instance. + /// instance with configured extension. + public static DomainConfiguration ConfigureLocalizationExtension(this DomainConfiguration domainConfiguration, + LocalizationConfiguration localizationConfiguration) + { + domainConfiguration.ExtensionConfigurations.Set(localizationConfiguration); + domainConfiguration.Types.Register(typeof(DomainConfugurationLocalizationExtensions).Assembly); + return domainConfiguration; + } + } +} \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Localization/Internals/TypeLocalizationMap.cs b/Extensions/Xtensive.Orm.Localization/Internals/TypeLocalizationMap.cs index 51e17ff140..2ac7db18cd 100644 --- a/Extensions/Xtensive.Orm.Localization/Internals/TypeLocalizationMap.cs +++ b/Extensions/Xtensive.Orm.Localization/Internals/TypeLocalizationMap.cs @@ -10,6 +10,7 @@ using Xtensive.Orm.Model; using Xtensive.Orm; using Xtensive.Reflection; +using Xtensive.Core; namespace Xtensive.Orm.Localization { @@ -21,13 +22,19 @@ internal class TypeLocalizationMap public static void Initialize(Domain domain) { - if (domain == null) - throw new ArgumentNullException("domain"); - if (domain.Extensions.Get() != null) + ArgumentValidator.EnsureArgumentNotNull(domain, nameof(domain)); + + var existing = domain.Extensions.Get(); + if (existing != null) { return; + } + + var configFromNewSource = domain.Configuration.ExtensionConfigurations.Get(); var map = new TypeLocalizationMap() { - Configuration = LocalizationConfiguration.Load() + Configuration = (configFromNewSource != null) + ? configFromNewSource + : LocalizationConfiguration.Load()// config from old source. }; foreach (var localizableTypeInfo in domain.Model.Types.Entities) { var type = localizableTypeInfo.UnderlyingType; diff --git a/Extensions/Xtensive.Orm.Localization/NugetContent/ReadMe.md b/Extensions/Xtensive.Orm.Localization/NugetContent/ReadMe.md index 87434ac868..58ec4541c9 100644 --- a/Extensions/Xtensive.Orm.Localization/NugetContent/ReadMe.md +++ b/Extensions/Xtensive.Orm.Localization/NugetContent/ReadMe.md @@ -8,7 +8,7 @@ This implies that localizable resources are a part of domain model so they are s Prerequisites ------------- -DataObjects.Net 7.0.x or later (http://dataobjects.net) +DataObjects.Net 7.1.x or later (http://dataobjects.net) Implementation -------------- @@ -50,6 +50,7 @@ Define corresponding localizations, e.g.: } ``` + Examples of usage ----------------- @@ -106,4 +107,267 @@ Examples of usage where p.Title=="Bienvenido" select p; Assert.AreEqual(1, query.Count()); +``` + + +Examples of how to configure extension +-------------------------------------- + +Following examples show different ways to configure extension in configuration files of various types. + +**Example #1** Confugure default culture in App.config/Web.config + +```xml + + +
+
+ + + + + + + + +``` + +Such configuration is usually read with ```System.Configuration.ConfigurationManager```. +If project still supports such configurations then Localization configuration will be read automatically when it needs to be read. +Sometimes a work-around is needed to read such configuration, for more read Example #2 and Example #3 + + +**Example #2** Reading old-style configuration of an assembly in NET 5 and newer. + +Due to new architecture without AppDomain (which among the other things was in charge of gathering configuration files of loaded assemblies +as it would be one configuration file) ```System.Configuration.ConfigurationManager``` now reads only configuration file of actual executable, loaded +assemblies' configuration files stay unreachable by default, though there is need to read some data from them. +A great example is test projects which are usually get loaded by test runner executable, and the only configuration accessible in this case +is test runner one. + +Extra step is required to read configuration files in such cases. Thankfully, ```ConfigurationManager``` has methods to get access to assemblies' configuration files. + +To get access to an assembly configuration file it should be opened explicitly by + +```csharp + var configuration = ConfigurationManager.OpenExeConfiguration(typeof(SomeTypeInConfigOwnerAssembly).Assembly.Location); +``` + +The instance returned from ```OpenExeConfiguration``` provides access to sections of the assembly configuration. DataObjects.Net configurations +(```DomainConfiguration```, ```LocalizationConfiguration```, etc.) have ```Load()``` methods that can recieve this instance. +```LocalizationConfiguration``` can be read like so + +```csharp + var configuration = ConfigurationManager.OpenExeConfiguration(typeof(SomeTypeInConfigOwnerAssembly).Assembly.Location); + var localizationConfig = LocalizationConfiguration.Load(configuration); + + // loaded configuration should be manually placed to + domainConfiguration.ExtensionConfigurations.Set(localizationConfig); +``` + +The ```domainConfiguration.ExtensionConfigurations``` is a new unified place from which the extension will try to get its configuration +instead of calling default parameterless ```Load()``` method, which has not a lot of sense now, though the method is kept as a second source +for backwards compatibility. + +For more convenience, ```DomainConfiguration``` extensions are provided, which make code neater. +For instance, + +```csharp + var configuration = ConfigurationManager.OpenExeConfiguration(typeof(SomeTypeInConfigOwnerAssembly).Assembly.Location); + + // the extension hides getting configuration with LocalizationConfiguration.Load(configuration) + // and also putting it to ExtensionConfigurations collection. + domainConfiguration.ConfigureLocalizationExtension(configuration); +``` + +Custom section names are also supported if for some reason default section name is not used. + + +**Example #3** Reading old-style configuration of an assembly in a project that uses appsettings.json file. + +If for some reason there is need to keep the old-style configuration then there is a work-around as well. +Static configuration manager provides method ```OpenMappedExeConfiguration()``` which allows to get +any *.config file as ```System.Configuration.Configuration``` instance. For example, + +```csharp + ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap(); + configFileMap.ExeConfigFilename = "Orm.config"; //or other file name, the file should exist bin folder + var configuration = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None); +``` + +After that, as in previous example, the instance can be passed to ```Load``` method of ```LocalizationConfiguration``` to read extension configuration +and later put it to ```DomainConfiguration.ExtensionConfigurations``` + +```csharp + var localizationConfiguration = LocalizationConfiguration.Load(configuration); + + domainConfiguration.ExtensionConfigurations.Set(localizationConfiguration); +``` + +Extension usage will look like + +```csharp + domainConfiguration.ConfigureLocalizationExtension(configuration); +``` + + +**Example #4** Configure using Microsoft.Extensions.Configuration API. + +This API allows to have configurations in various forms including JSON and XML formats. +Loading of such files may differ depending on .NET version, check Microsoft manuals for instructions. + +Allowed JSON and XML configuration definition look like below + +```xml + + + es-ES + + +``` + +```json +{ + "Xtensive.Orm.Localization": { + "DefaultCulture": "es-ES" + } +} +``` + +The API has certain issues with XML elements with attributes so it is recommended to use +more up-to-date attributeless nodes. +For JSON it is pretty clear, almost averyone knows its format. + +```LocalizationConfiguration.Load``` method can accept different types of abstractions from the API, including +- ```Microsoft.Extensions.Configuration.IConfiguration```; +- ```Microsoft.Extensions.Configuration.IConfigurationRoot```; +- ```Microsoft.Extensions.Configuration.IConfigurationSection```. + +Loading of configuration may look like + +```csharp + + var app = builder.Build(); + + //... + + // tries to load from default section "Xtensive.Orm.Localization" + var localizationConfig = LocalizationConfiguration.Load(app.Configuration); + + domainConfiguration.ExtensionConfigurations.Set(localizationConfig); +``` + +or, with use of extension, like + + +```csharp + + var app = builder.Build(); + + //... + + // tries to load from default section "Xtensive.Orm.Localization" + // and additionally adds Xtensive.Orm.Localization assembly to domain types. + + domainConfiguration.ConfigureLocalizationExtension(app.Configuration); +``` + + + +**Example #5** Configure using Microsoft.Extensions.Configuration API from section with non-default name. + +For configurations like + +```xml + + + es-ES + + +``` + +```json +{ + "Orm.Localization": { + "DefaultCulture": "es-ES" + } +} +``` + +Loading of configuration may look like + +```csharp + + var app = builder.Build(); + + //... + + var localizationConfig = LocalizationConfiguration.Load(app.Configuration, "Orm.Localization"); + + domainConfiguration.ExtensionConfigurations.Set(localizationConfig); +``` + +or, with use of extension, like + +```csharp + var app = builder.Build(); + domainConfiguration.ConfigureLocalizationExtension(app.Configuration, "Orm.Localization"); +``` + + +**Example #6** Configure using Microsoft.Extensions.Configuration API from sub-section deeper in section tree. + +If for some reason extension configuration should be moved deeper in section tree like something below + +```xml + + + + es-ES + + + +``` + +or in JSON + +```json +{ + "Orm.Extensions": { + "Xtensive.Orm.Localization": { + "DefaultCulture": "es-ES" + } + } +} +``` + +Then section must be provided manually, code may look like + +```csharp + + var app = builder.Build(); + + //... + + var configurationRoot = app.Configuration; + var extensionsGroupSection = configurationRoot.GetSection("Orm.Extensions"); + var localizationSection = extensionsGroupSection.GetSection("Xtensive.Orm.Localization"); + var localizationConfig = LocalizationConfiguration.Load(localizationSection); + + domainConfiguration.ExtensionConfigurations.Set(localizationConfig); +``` + +or, with use of extension method, like + +```csharp + + var app = builder.Build(); + + //... + + var configurationRoot = app.Configuration; + var extensionsGroupSection = configurationRoot.GetSection("Orm.Extensions"); + var localizationSection = extensionsGroupSection.GetSection("Xtensive.Orm.Localization"); + + domainConfiguration.ConfigureLocalizationExtension(localizationSection); ``` \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Localization/Xtensive.Orm.Localization.csproj b/Extensions/Xtensive.Orm.Localization/Xtensive.Orm.Localization.csproj index 404de113d2..318bb83b12 100644 --- a/Extensions/Xtensive.Orm.Localization/Xtensive.Orm.Localization.csproj +++ b/Extensions/Xtensive.Orm.Localization/Xtensive.Orm.Localization.csproj @@ -27,4 +27,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Logging.NLog/NugetContent/ReadMe.md b/Extensions/Xtensive.Orm.Logging.NLog/NugetContent/ReadMe.md index 3fb46089a9..450daa9b68 100644 --- a/Extensions/Xtensive.Orm.Logging.NLog/NugetContent/ReadMe.md +++ b/Extensions/Xtensive.Orm.Logging.NLog/NugetContent/ReadMe.md @@ -1,4 +1,4 @@ -Xtensive.Orm.Logging.NLog +Xtensive.Orm.Logging.NLog ========================= Summary @@ -8,7 +8,7 @@ The extension provides integration points between DataObjects.Net internal loggi Prerequisites ------------- -DataObjects.Net 7.0.x (http://dataobjects.net) +DataObjects.Net 7.1.x (http://dataobjects.net) NLog 4.5 or later (http://nlog-project.org) Implementation diff --git a/Extensions/Xtensive.Orm.Logging.log4net/NugetContent/ReadMe.md b/Extensions/Xtensive.Orm.Logging.log4net/NugetContent/ReadMe.md index d5169400ed..664c522177 100644 --- a/Extensions/Xtensive.Orm.Logging.log4net/NugetContent/ReadMe.md +++ b/Extensions/Xtensive.Orm.Logging.log4net/NugetContent/ReadMe.md @@ -1,4 +1,4 @@ -Xtensive.Orm.Logging.log4net +Xtensive.Orm.Logging.log4net ============================ Summary @@ -7,7 +7,7 @@ The extension provides integration points between DataObjects.Net internal loggi Prerequisites ------------- -DataObjects.Net 7.0.x (http://dataobjects.net) +DataObjects.Net 7.1.x (http://dataobjects.net) log4net 2.0.10 or later (http://logging.apache.org/log4net/) Implementation diff --git a/Extensions/Xtensive.Orm.Reprocessing.Tests/App.config b/Extensions/Xtensive.Orm.Reprocessing.Tests/App.config index cfe235c4cc..b088293b47 100644 --- a/Extensions/Xtensive.Orm.Reprocessing.Tests/App.config +++ b/Extensions/Xtensive.Orm.Reprocessing.Tests/App.config @@ -1,4 +1,4 @@ - +
diff --git a/Extensions/Xtensive.Orm.Reprocessing.Tests/ReprocessingSettings.config b/Extensions/Xtensive.Orm.Reprocessing.Tests/ReprocessingSettings.config new file mode 100644 index 0000000000..25567e878d --- /dev/null +++ b/Extensions/Xtensive.Orm.Reprocessing.Tests/ReprocessingSettings.config @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + Auto + + + New + + + Default + + + + + + + + Xtensive.Orm.Reprocessing.HandleReprocessableExceptionStrategy, Xtensive.Orm.Reprocessing + + + + Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing + + + + Xtensive.Orm.Reprocessing.DummyStrategy, Xtensive.Orm.Reprocessing + + + + + + + + + + Auto + Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing + + + + Auto + Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing + + + + Auto + Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing + + + + Auto + Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing + + + + + + + + + Auto + Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing + + + + Auto + Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing + + + + Auto + Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing + + + + Auto + Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Reprocessing.Tests/Tests/MicrosoftConfugurationTests.cs b/Extensions/Xtensive.Orm.Reprocessing.Tests/Tests/MicrosoftConfugurationTests.cs new file mode 100644 index 0000000000..d6221b7018 --- /dev/null +++ b/Extensions/Xtensive.Orm.Reprocessing.Tests/Tests/MicrosoftConfugurationTests.cs @@ -0,0 +1,346 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using Microsoft.Extensions.Configuration; +using NUnit.Framework; +using Xtensive.Orm.Reprocessing.Configuration; + +namespace Xtensive.Orm.Reprocessing.Tests.Configuration +{ + public sealed class JsonConfigurationTest : MicrosoftConfigurationTest + { + protected override ConfigTypes ConfigFormat => ConfigTypes.Json; + + protected override void AddConfigurationFile(IConfigurationBuilder configurationBuilder) + { + _ = configurationBuilder.AddJsonFile("reprocessingsettings.json"); + } + } + + public sealed class XmlConfigurationTest : MicrosoftConfigurationTest + { + protected override ConfigTypes ConfigFormat => ConfigTypes.Xml; + + protected override void AddConfigurationFile(IConfigurationBuilder configurationBuilder) + { + _ = configurationBuilder.AddXmlFile("ReprocessingSettings.config"); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptyNodesTest(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Xml.EmptyNodes", useRoot); + CheckConfigIsDefault(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptyValuesTest(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Attributes.EmptyValues", useRoot); + CheckConfigIsDefault(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptyTransactionModeOnlyTest(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Attributes.EmptyTMOnly", useRoot); + CheckConfigIsDefault(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptyStrategyOnlyTest(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Attributes.EmptyStrategyOnly", useRoot); + CheckConfigIsDefault(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void AllAttributesHasValuesTest(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Attributes.All", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.Auto)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleUniqueConstraintViolationStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyTMAttributeHasValueTest(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Attributes.OnlyTM", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.Auto)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleReprocessableExceptionStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyStrategyAttributeHasValueTest(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Attributes.OnlyStrategy", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.New)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleUniqueConstraintViolationStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void AttributesInLowCase(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Attributes.LC", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.Auto)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleUniqueConstraintViolationStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void AttributesInUpperCase(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Attributes.UC", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.Auto)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleUniqueConstraintViolationStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void AttributesInPascalCase(bool useRoot) + { + var repConfig = LoadConfiguration("Xtensive.Orm.Reprocessing.Attributes.PC", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.Auto)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleUniqueConstraintViolationStrategy))); + } + } + + public abstract class MicrosoftConfigurationTest : TestCommon.MicrosoftConfigurationTestBase + { + protected ReprocessingConfiguration LoadConfiguration(string sectionName, bool useRoot) + { + return useRoot + ? ReprocessingConfiguration.Load(configurationRoot, sectionName) + : ReprocessingConfiguration.Load(configurationRoot.GetSection(sectionName)); + } + + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptySectionCase(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.Empty", useRoot); + CheckConfigIsDefault(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptyNames(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.AllEmpty", useRoot); + CheckConfigIsDefault(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyTransactionOpenModeAndEmpty(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.OnlyTM.Empty", useRoot); + CheckConfigIsDefault(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyTransactionOpenModeAuto(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.OnlyTM.Auto", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.Auto)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleReprocessableExceptionStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyTransactionOpenModeNew(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.OnlyTM.New", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.New)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleReprocessableExceptionStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyTransactionOpenModeDefault(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.OnlyTM.Default", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.Default)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleReprocessableExceptionStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyStrategyAndEmpty(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.OnlyStrategy.Empty", useRoot); + CheckConfigIsDefault(repConfig); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.New)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleReprocessableExceptionStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyStrategyHandleReprocessible(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.OnlyStrategy.HandleReprocessible", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.New)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleReprocessableExceptionStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyStrategyHandleUnique(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.OnlyStrategy.HandleUnique", useRoot); + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.New)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleUniqueConstraintViolationStrategy))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyStrategyNonExistent(bool useRoot) + { + _ = Assert.Throws( + () => LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.OnlyStrategy.NonExistent", useRoot)); + } + + + #region Naming + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInLowCase(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.Naming.LC", useRoot); + ValidateNamingConfigurationResults(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInUpperCase(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.Naming.UC", useRoot); + ValidateNamingConfigurationResults(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInCamelCase(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.Naming.CC", useRoot); + ValidateNamingConfigurationResults(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInPascalCase(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.Naming.PC", useRoot); + ValidateNamingConfigurationResults(repConfig); + } + + private static void ValidateNamingConfigurationResults(ReprocessingConfiguration repConfig) + { + Assert.That(repConfig, Is.Not.Null); + Assert.That(repConfig.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.Auto)); + Assert.That(repConfig.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleUniqueConstraintViolationStrategy))); + } + #endregion + + #region mistype cases + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInLowCase(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.Mistype.LC", useRoot); + ValidateMistypeConfigurationResults(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInUpperCase(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.Mistype.UC", useRoot); + ValidateMistypeConfigurationResults(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInCamelCase(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.Mistype.CC", useRoot); + ValidateMistypeConfigurationResults(repConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInPascalCase(bool useRoot) + { + var repConfig = LoadConfiguration($"Xtensive.Orm.Reprocessing.{ConfigFormat}.Mistype.PC", useRoot); + ValidateMistypeConfigurationResults(repConfig); + } + + private static void ValidateMistypeConfigurationResults(ReprocessingConfiguration repConfig) + { + CheckConfigIsDefault(repConfig); + } + + #endregion + + protected static void CheckConfigIsDefault(ReprocessingConfiguration configuration) + { + Assert.That(configuration, Is.Not.Null); + Assert.That(configuration.DefaultTransactionOpenMode, Is.EqualTo(TransactionOpenMode.New)); + Assert.That(configuration.DefaultExecuteStrategy, Is.EqualTo(typeof(HandleReprocessableExceptionStrategy))); + } + } +} diff --git a/Extensions/Xtensive.Orm.Reprocessing.Tests/Xtensive.Orm.Reprocessing.Tests.csproj b/Extensions/Xtensive.Orm.Reprocessing.Tests/Xtensive.Orm.Reprocessing.Tests.csproj index 29421c8206..3c995ffcc5 100644 --- a/Extensions/Xtensive.Orm.Reprocessing.Tests/Xtensive.Orm.Reprocessing.Tests.csproj +++ b/Extensions/Xtensive.Orm.Reprocessing.Tests/Xtensive.Orm.Reprocessing.Tests.csproj @@ -8,6 +8,9 @@ + + + @@ -20,4 +23,12 @@ + + + PreserveNewest + + + PreserveNewest + + \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Reprocessing.Tests/reprocessingsettings.json b/Extensions/Xtensive.Orm.Reprocessing.Tests/reprocessingsettings.json new file mode 100644 index 0000000000..9aa57971ef --- /dev/null +++ b/Extensions/Xtensive.Orm.Reprocessing.Tests/reprocessingsettings.json @@ -0,0 +1,72 @@ +{ + "Xtensive.Orm.Reprocessing.Json.Empty": { + + }, + + "Xtensive.Orm.Reprocessing.Json.AllEmpty": { + "DefaultTransactionOpenMode": "", + "DefaultExecuteStrategy": "" + }, + + + "Xtensive.Orm.Reprocessing.Json.OnlyTM.Empty": { + "DefaultTransactionOpenMode": "" + }, + "Xtensive.Orm.Reprocessing.Json.OnlyTM.Auto": { + "DefaultTransactionOpenMode": "Auto" + }, + "Xtensive.Orm.Reprocessing.Json.OnlyTM.New": { + "DefaultTransactionOpenMode": "New" + }, + "Xtensive.Orm.Reprocessing.Json.OnlyTM.Default": { + "DefaultTransactionOpenMode": "Default" + }, + + "Xtensive.Orm.Reprocessing.Json.OnlyStrategy.Empty": { + "DefaultExecuteStrategy": "" + }, + "Xtensive.Orm.Reprocessing.Json.OnlyStrategy.HandleReprocessible": { + "DefaultExecuteStrategy": "Xtensive.Orm.Reprocessing.HandleReprocessableExceptionStrategy, Xtensive.Orm.Reprocessing" + }, + "Xtensive.Orm.Reprocessing.Json.OnlyStrategy.HandleUnique": { + "DefaultExecuteStrategy": "Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing" + }, + "Xtensive.Orm.Reprocessing.Json.OnlyStrategy.NonExistent": { + "DefaultExecuteStrategy": "Xtensive.Orm.Reprocessing.DummyStrategy, Xtensive.Orm.Reprocessing" + }, + + + "Xtensive.Orm.Reprocessing.Json.Naming.LC": { + "defaulttransactionopenmode": "Auto", + "defaultexecutestrategy": "Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing" + }, + "Xtensive.Orm.Reprocessing.Json.Naming.UC": { + "DEFAULTTRANSACTIONOPENMODE": "Auto", + "DEFAULTEXECUTESTRATEGY": "Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing" + }, + "Xtensive.Orm.Reprocessing.Json.Naming.CC": { + "defaultTransactionOpenMode": "Auto", + "defaultExecuteStrategy": "Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing" + }, + "Xtensive.Orm.Reprocessing.Json.Naming.PC": { + "DefaultTransactionOpenMode": "Auto", + "DefaultExecuteStrategy": "Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing" + }, + + "Xtensive.Orm.Reprocessing.Json.Mistype.LC": { + "defaultttransactionopenmode": "Auto", + "defaulttexecutestrategy": "Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing" + }, + "Xtensive.Orm.Reprocessing.Json.Mistype.UC": { + "DEFAULTTTRANSACTIONOPENMODE": "Auto", + "DEFAULTTEXECUTESTRATEGY": "Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing" + }, + "Xtensive.Orm.Reprocessing.Json.Mistype.CC": { + "defaultTtransactionOpenMode": "Auto", + "defaulttExecuteStrategy": "Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing" + }, + "Xtensive.Orm.Reprocessing.Json.Mistype.PC": { + "DefaultTtransactionOpenMode": "Auto", + "DefaulttExecuteStrategy": "Xtensive.Orm.Reprocessing.HandleUniqueConstraintViolationStrategy, Xtensive.Orm.Reprocessing" + } +} diff --git a/Extensions/Xtensive.Orm.Reprocessing/Configuration/ConfigurationSection.cs b/Extensions/Xtensive.Orm.Reprocessing/Configuration/ConfigurationSection.cs index d93d13802c..6b8d0d2718 100644 --- a/Extensions/Xtensive.Orm.Reprocessing/Configuration/ConfigurationSection.cs +++ b/Extensions/Xtensive.Orm.Reprocessing/Configuration/ConfigurationSection.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Configuration; @@ -11,8 +11,9 @@ public class ConfigurationSection : System.Configuration.ConfigurationSection { /// /// Gets default section name for reprocessing configuration. - /// Value is "Xtensive.Reprocessing". + /// Value is "Xtensive.Orm.Reprocessing". /// + [Obsolete("Use ReprocessingConfiguration.DefaultSectionName instead")] public static readonly string DefaultSectionName = "Xtensive.Orm.Reprocessing"; /// diff --git a/Extensions/Xtensive.Orm.Reprocessing/Configuration/ReprocessingConfiguration.cs b/Extensions/Xtensive.Orm.Reprocessing/Configuration/ReprocessingConfiguration.cs index 12b6ec5612..6e7adbc458 100644 --- a/Extensions/Xtensive.Orm.Reprocessing/Configuration/ReprocessingConfiguration.cs +++ b/Extensions/Xtensive.Orm.Reprocessing/Configuration/ReprocessingConfiguration.cs @@ -1,14 +1,22 @@ -using System; +using System; using System.Configuration; +using Microsoft.Extensions.Configuration; using Xtensive.Core; +using Xtensive.Orm.Configuration; namespace Xtensive.Orm.Reprocessing.Configuration { /// /// The configuration of the reprocessing system. /// - public class ReprocessingConfiguration + public class ReprocessingConfiguration : ConfigurationBase { + /// + /// Gets default section name for reprocessing configuration. + /// Value is "Xtensive.Orm.Reprocessing". + /// + public static readonly string DefaultSectionName = "Xtensive.Orm.Reprocessing"; + /// /// Gets default value of the property. /// @@ -19,15 +27,46 @@ public class ReprocessingConfiguration /// public static readonly Type DefaultDefaultExecuteStrategy = typeof (HandleReprocessableExceptionStrategy); + private TransactionOpenMode defaultTransactionOpenMode; + private Type defaultExecuteStrategy; + /// /// Gets or sets default value of the parameter. /// - public TransactionOpenMode DefaultTransactionOpenMode { get; set; } + public TransactionOpenMode DefaultTransactionOpenMode { + get => defaultTransactionOpenMode; + set { + EnsureNotLocked(); + defaultTransactionOpenMode = value; + } + } /// /// Gets or sets default value of the parameter. /// - public Type DefaultExecuteStrategy { get; set; } + public Type DefaultExecuteStrategy { + get => defaultExecuteStrategy; + set { + EnsureNotLocked(); + defaultExecuteStrategy = value; + } + } + + /// + protected override ReprocessingConfiguration CreateClone() => new ReprocessingConfiguration(); + + /// + protected override void CopyFrom(ConfigurationBase source) + { + base.CopyFrom(source); + + var configuration = (ReprocessingConfiguration) source; + configuration.DefaultTransactionOpenMode = configuration.DefaultTransactionOpenMode; + configuration.DefaultExecuteStrategy = configuration.DefaultExecuteStrategy; + } + + /// + public override ReprocessingConfiguration Clone() => (ReprocessingConfiguration) base.Clone(); /// /// Loads the reprocessing configuration from default section in application configuration file. @@ -35,7 +74,7 @@ public class ReprocessingConfiguration /// The reprocessing configuration. public static ReprocessingConfiguration Load() { - return Load(ConfigurationSection.DefaultSectionName); + return Load(DefaultSectionName); } /// @@ -56,7 +95,7 @@ public static ReprocessingConfiguration Load(string sectionName) /// The reprocessing configuration. public static ReprocessingConfiguration Load(System.Configuration.Configuration configuration) { - return Load(configuration, ConfigurationSection.DefaultSectionName); + return Load(configuration, DefaultSectionName); } /// @@ -81,6 +120,52 @@ private static ReprocessingConfiguration GetConfigurationFromSection(Configurati }; } + /// + /// Loads the from specified section of configuration root. + /// + /// to load section from. + /// Name of the section where configuration is stored. Not applied if + /// Loaded configuration or configuration with default settings. + public static ReprocessingConfiguration Load(IConfiguration configuration, string sectionName = null) + { + ArgumentValidator.EnsureArgumentNotNull(configuration, nameof(configuration)); + + if (configuration is IConfigurationRoot configurationRoot) { + return new ReprocessingConfigurationReader().Read(configurationRoot, sectionName ?? DefaultSectionName); + } + else if (configuration is IConfigurationSection configurationSection) { + return new ReprocessingConfigurationReader().Read(configurationSection); + } + + throw new NotSupportedException("Type of configuration is not supported."); + } + + + /// + /// Loads the from specified section of configuration root. + /// + /// to load section from. + /// Name of the section where configuration is stored. + /// Loaded configuration or configuration with default settings. + public static ReprocessingConfiguration Load(IConfigurationRoot configurationRoot, string sectionName = null) + { + ArgumentValidator.EnsureArgumentNotNull(configurationRoot, nameof(configurationRoot)); + + return new ReprocessingConfigurationReader().Read(configurationRoot, sectionName ?? DefaultSectionName); + } + + /// + /// Loads the from given configuration section. + /// + /// to load from. + /// Loaded configuration or configuration with default settings. + public static ReprocessingConfiguration Load(IConfigurationSection configurationSection) + { + ArgumentValidator.EnsureArgumentNotNull(configurationSection, nameof(configurationSection)); + + return new ReprocessingConfigurationReader().Read(configurationSection); + } + /// /// Initializes a new instance of the class. /// diff --git a/Extensions/Xtensive.Orm.Reprocessing/Configuration/ReprocessingConfigurationReader.cs b/Extensions/Xtensive.Orm.Reprocessing/Configuration/ReprocessingConfigurationReader.cs new file mode 100644 index 0000000000..36a553b34d --- /dev/null +++ b/Extensions/Xtensive.Orm.Reprocessing/Configuration/ReprocessingConfigurationReader.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using Microsoft.Extensions.Configuration; +using Xtensive.Orm.Configuration; + +namespace Xtensive.Orm.Reprocessing.Configuration +{ + internal sealed class ReprocessingConfigurationReader : IConfigurationSectionReader + { + // intermediate class for reading section + private class ReprocessingOptions + { + public TransactionOpenMode? DefaultTransactionOpenMode { get; set; } + + public string DefaultExecuteStrategy { get; set; } + } + + public ReprocessingConfiguration Read(IConfigurationSection configurationSection) => ReadInternal(configurationSection); + + public ReprocessingConfiguration Read(IConfigurationSection configurationSection, string nameOfConfiguration) => + throw new NotSupportedException(); + + public ReprocessingConfiguration Read(IConfigurationRoot configurationRoot) => + Read(configurationRoot, ReprocessingConfiguration.DefaultSectionName); + + public ReprocessingConfiguration Read(IConfigurationRoot configurationRoot, string sectionName) + { + var section = configurationRoot.GetSection(sectionName); + return ReadInternal(section); + } + + public ReprocessingConfiguration Read(IConfigurationRoot configurationRoot, string sectionName, string nameOfConfiguration) => + throw new NotSupportedException(); + + private ReprocessingConfiguration ReadInternal(IConfigurationSection section) + { + var reprocessingOptions = section.Get(); + + if (reprocessingOptions == default) { + return new ReprocessingConfiguration(); + } + + if (reprocessingOptions.DefaultTransactionOpenMode == default + && reprocessingOptions.DefaultExecuteStrategy == default) { + // that means instance is default. probably invalid + return new ReprocessingConfiguration(); + } + + var result = new ReprocessingConfiguration(); + if (reprocessingOptions.DefaultTransactionOpenMode != default) { + result.DefaultTransactionOpenMode = reprocessingOptions.DefaultTransactionOpenMode.Value; + } + if (!string.IsNullOrEmpty(reprocessingOptions.DefaultExecuteStrategy)) { + var type = Type.GetType(reprocessingOptions.DefaultExecuteStrategy, false); + if (type == null) + throw new InvalidOperationException($"Can't resolve type '{reprocessingOptions.DefaultExecuteStrategy}'. Note that DefaultExecuteStrategy value should be in form of Assembly Qualified Name"); + result.DefaultExecuteStrategy = type; + } + return result; + } + } +} \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Reprocessing/DomainConfigurationExtensions.cs b/Extensions/Xtensive.Orm.Reprocessing/DomainConfigurationExtensions.cs new file mode 100644 index 0000000000..ca17256a8c --- /dev/null +++ b/Extensions/Xtensive.Orm.Reprocessing/DomainConfigurationExtensions.cs @@ -0,0 +1,108 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using Microsoft.Extensions.Configuration; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Reprocessing.Configuration; + +namespace Xtensive.Orm.Reprocessing +{ + /// + /// Contains extensions for DomainConfiguration that help to configure the extension. + /// + public static class DomainConfigurationReprocessingExtensions + { + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// instance with configured extension. + public static DomainConfiguration ConfigureReprocessingExtension(this DomainConfiguration domainConfiguration) => + ConfigureReprocessingExtension(domainConfiguration, ReprocessingConfiguration.Load()); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Section name. + /// instance with configured extension. + public static DomainConfiguration ConfigureReprocessingExtension(this DomainConfiguration domainConfiguration, + string configurationSectionName) => + ConfigureReprocessingExtension(domainConfiguration, ReprocessingConfiguration.Load(configurationSectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// instance with configured extension. + public static DomainConfiguration ConfigureReprocessingExtension(this DomainConfiguration domainConfiguration, + System.Configuration.Configuration configuration) => + ConfigureReprocessingExtension(domainConfiguration, ReprocessingConfiguration.Load(configuration)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// Section in + /// instance with configured extension. + public static DomainConfiguration ConfigureReprocessingExtension(this DomainConfiguration domainConfiguration, + System.Configuration.Configuration configuration, string sectionName) => + ConfigureReprocessingExtension(domainConfiguration, ReprocessingConfiguration.Load(configuration, sectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// Section in + /// instance with configured extension. + public static DomainConfiguration ConfigureReprocessingExtension(this DomainConfiguration domainConfiguration, + IConfiguration configuration, string sectionName = null) => + ConfigureReprocessingExtension(domainConfiguration, ReprocessingConfiguration.Load(configuration, sectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration to load from. + /// Section in + /// instance with configured extension. + public static DomainConfiguration ConfigureReprocessingExtension(this DomainConfiguration domainConfiguration, + IConfigurationRoot configurationRoot, string sectionName = null) => + ConfigureReprocessingExtension(domainConfiguration, ReprocessingConfiguration.Load(configurationRoot, sectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration section to load from. + /// instance with configured extension. + public static DomainConfiguration ConfigureReprocessingExtension(this DomainConfiguration domainConfiguration, + IConfigurationSection configurationSection) => + ConfigureReprocessingExtension(domainConfiguration, ReprocessingConfiguration.Load(configurationSection)); + + /// + /// Configures the extension with given reprocessing configuration instance. + /// + /// Domain configuration. + /// Security configuration instance. + /// instance with configured extension. + public static DomainConfiguration ConfigureReprocessingExtension(this DomainConfiguration domainConfiguration, + ReprocessingConfiguration reprocessingConfiguration) + { + domainConfiguration.ExtensionConfigurations.Set(reprocessingConfiguration); + domainConfiguration.Types.Register(typeof(DomainConfigurationReprocessingExtensions).Assembly); + return domainConfiguration; + } + } +} \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Reprocessing/DomainExtensions.cs b/Extensions/Xtensive.Orm.Reprocessing/DomainExtensions.cs index 5e50e81184..dc14da20a0 100644 --- a/Extensions/Xtensive.Orm.Reprocessing/DomainExtensions.cs +++ b/Extensions/Xtensive.Orm.Reprocessing/DomainExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Transactions; +using Xtensive.Orm.Configuration; using Xtensive.Orm.Reprocessing.Configuration; namespace Xtensive.Orm.Reprocessing @@ -190,7 +191,8 @@ internal static T ExecuteInternal( IExecuteActionStrategy strategy, Func func) { - var config = domain.GetReprocessingConfiguration(); + var config = domain.Configuration.ExtensionConfigurations.Get() + ?? domain.GetReprocessingConfiguration(); if (strategy == null) { strategy = ExecuteActionStrategy.GetSingleton(config.DefaultExecuteStrategy); } @@ -208,7 +210,8 @@ internal static T ExecuteInternal( IExecuteActionStrategy strategy, Func func) { - var config = domain.GetReprocessingConfiguration(); + var config = domain.Configuration.ExtensionConfigurations.Get() + ?? domain.GetReprocessingConfiguration(); if (strategy == null) { strategy = ExecuteActionStrategy.GetSingleton(config.DefaultExecuteStrategy); } diff --git a/Extensions/Xtensive.Orm.Reprocessing/NugetContent/ReadMe.md b/Extensions/Xtensive.Orm.Reprocessing/NugetContent/ReadMe.md index 8129b98f32..678a948360 100644 --- a/Extensions/Xtensive.Orm.Reprocessing/NugetContent/ReadMe.md +++ b/Extensions/Xtensive.Orm.Reprocessing/NugetContent/ReadMe.md @@ -8,7 +8,7 @@ should represent a separate block of logic, usually a delegate of a method and b Prerequisites ------------- -DataObjects.Net 7.0.x (http://dataobjects.net) +DataObjects.Net 7.1.x (http://dataobjects.net) Examples of usage ----------------- @@ -40,10 +40,16 @@ To indicate that a particular strategy should be used, use the following syntax: }); ``` -**Expample #3**. Confugure reprocessing in configuration file. To omit setting up the strategy each time consider configuring it in -application configuration file, e.g.: + +Examples of how to configure extension +-------------------------------------- + +Following examples show different ways to configure extension in configuration files of various types. + +**Example #1** Confugure in App.config/Web.config ```xml +
+ +``` + +Such configuration is usually read with ```System.Configuration.ConfigurationManager```. +If project still supports such configurations then Reprocessing configuration will be read automatically when it needs to be read. +Sometimes a work-around is needed to read such configuration, for more read Example #2 and Example #3 + + +**Example #2** Reading old-style configuration of an assembly in NET 5 and newer. + +Due to new architecture without AppDomain (which among the other things was in charge of gathering configuration files of loaded assemblies +as it would be one configuration file) ```System.Configuration.ConfigurationManager``` now reads only configuration file of actual executable, loaded +assemblies' configuration files stay unreachable by default, though there is need to read some data from them. +A great example is test projects which are usually get loaded by test runner executable, and the only configuration accessible in this case +is test runner one. + +Extra step is required to read configuration files in such cases. Thankfully, ```ConfigurationManager``` has methods to get access to assemblies' configuration files. + +To get access to an assembly configuration file it should be opened explicitly by + +```csharp + var configuration = ConfigurationManager.OpenExeConfiguration(typeof(SomeTypeInConfigOwnerAssembly).Assembly.Location); +``` + +The instance returned from ```OpenExeConfiguration``` provides access to sections of the assembly configuration. DataObjects.Net configurations +(```DomainConfiguration```, ```ReprocessingConfiguration```, etc.) have ```Load()``` methods that can recieve this instance. +```ReprocessingConfiguration``` can be read like so + +```csharp + var configuration = ConfigurationManager.OpenExeConfiguration(typeof(SomeTypeInConfigOwnerAssembly).Assembly.Location); + var reprocessingConfig = ReprocessingConfiguration.Load(configuration); + + // loaded configuration should be manually placed to + domainConfiguration.ExtensionConfigurations.Set(reprocessingConfig); +``` + +The ```domainConfiguration.ExtensionConfigurations``` is a new unified place from which the extension will try to get its configuration +instead of calling default parameterless ```Load()``` method, which has not a lot of sense now, though the method is kept as a second source +for backwards compatibility. + +For more convenience, ```DomainConfiguration``` extensions are provided, which make code more neater. +For instance, + +```csharp + var configuration = ConfigurationManager.OpenExeConfiguration(typeof(SomeTypeInConfigOwnerAssembly).Assembly.Location); + + // the extension hides getting configuration with ReprocessingConfiguration.Load(configuration) + // and also putting it to ExtensionConfigurations collection. + domainConfiguration.ConfigureReprocessingExtension(configuration); +``` + +Custom section names are also supported if for some reason default section name is not used. + + +**Example #3** Reading old-style configuration of an assembly in a project that uses appsettings.json file. + +If for some reason there is need to keep the old-style configuration then there is a work-around as well. +Static configuration manager provides method ```OpenMappedExeConfiguration()``` which allows to get +any *.config file as ```System.Configuration.Configuration``` instance. For example, + +```csharp + ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap(); + configFileMap.ExeConfigFilename = "Orm.config"; //or other file name, the file should exist bin folder + var configuration = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None); +``` + +After that, as in previous example, the instance can be passed to ```Load``` method of ```ReprocessingConfiguration``` to read extension configuration +and later put it to ```DomainConfiguration.ExtensionConfigurations``` + +```csharp + var reprocessingConfiguration = ReprocessingConfiguration.Load(configuration); + + domainConfiguration.ExtensionConfigurations.Set(reprocessingConfiguration); +``` + +Extension usage will look like + +```csharp + domainConfiguration.ConfigureReprocessingExtension(configuration); +``` + + +**Example #4** Configure using Microsoft.Extensions.Configuration API. + +This API allows to have configurations in various forms including JSON and XML formats. +Loading of such files may differ depending on .NET version, check Microsoft manuals for instructions. + +Allowed JSON and XML configuration definition look like below + +```xml + + + New + Xtensive.Orm.Reprocessing.HandleReprocessableExceptionStrategy, Xtensive.Orm.Reprocessing + + +``` + +```json +{ + "Xtensive.Orm.Reprocessing": { + "DefaultTransactionOpenMode" : "New", + "DefaultExecuteStrategy" : "Xtensive.Orm.Reprocessing.HandleReprocessableExceptionStrategy, Xtensive.Orm.Reprocessing" + } +} ``` -Having that done, in scenarios with no strategy specified, the extension will automatically use -the strategy from the configuration. \ No newline at end of file +The API has certain issues with Xml elements with attributes so it is recommended to use +more up-to-date attributeless nodes. +For JSON it is pretty clear, almost averyone knows its format. + +```ReprocessingConfiguration.Load``` method can accept different types of abstractions from the API, including +- ```Microsoft.Extensions.Configuration.IConfiguration```; +- ```Microsoft.Extensions.Configuration.IConfigurationRoot```; +- ```Microsoft.Extensions.Configuration.IConfigurationSection```. + +Loading of configuration may look like + +```csharp + + var app = builder.Build(); + + //... + + // tries to load from default section "Xtensive.Orm.Reprocessing" + var reprocessingConfig = ReprocessingConfiguration.Load(app.Configuration); + + domainConfiguration.ExtensionConfigurations.Set(reprocessingConfig); +``` + +or, with use of extension, like + + +```csharp + + var app = builder.Build(); + + //... + + // tries to load from default section "Xtensive.Orm.Reprocessing" + // and put it into domainConfiguration.ExtensionConfigurations + + domainConfiguration.ConfigureReprocessingExtension(app.Configuration); +``` + + + +**Example #5** Configure using Microsoft.Extensions.Configuration API from section with non-default name. + +For configurations like + +```xml + + + New + Xtensive.Orm.Reprocessing.HandleReprocessableExceptionStrategy, Xtensive.Orm.Reprocessing + + +``` + +```json +{ + "Orm.Reprocessing": { + "DefaultTransactionOpenMode" : "New", + "DefaultExecuteStrategy" : "Xtensive.Orm.Reprocessing.HandleReprocessableExceptionStrategy, Xtensive.Orm.Reprocessing" + } +} +``` + +Loading of configuration may look like + +```csharp + + var app = builder.Build(); + + //... + + var reprocessingConfig = ReprocessingConfiguration.Load(app.Configuration, "Orm.Reprocessing"); + + domainConfiguration.ExtensionConfigurations.Set(reprocessingConfig); +``` + +or, with use of extension, like + +```csharp + + var app = builder.Build(); + + //... + + domainConfiguration.ConfigureReprocessingExtension(app.Configuration, "Orm.Reprocessing"); +``` + + +**Example #6** Configure using Microsoft.Extensions.Configuration API from sub-section deeper in section tree. + +If for some reason extension configuration should be moved deeper in section tree like something below + +```xml + + + + New + Xtensive.Orm.Reprocessing.HandleReprocessableExceptionStrategy, Xtensive.Orm.Reprocessing + + + +``` + +or in JSON + +```json +{ + "Orm.Extensions": { + "Xtensive.Orm.Reprocessing": { + "DefaultTransactionOpenMode" : "New", + "DefaultExecuteStrategy" : "Xtensive.Orm.Reprocessing.HandleReprocessableExceptionStrategy, Xtensive.Orm.Reprocessing" + } + } +} +``` + +Then section must be provided manually, code may look like + +```csharp + + var app = builder.Build(); + + //... + + var configurationRoot = app.Configuration; + var extensionsGroupSection = configurationRoot.GetSection("Orm.Extensions"); + var reprocessingSection = extensionsGroupSection.GetSection("Xtensive.Orm.Reprocessing"); + + var reprocessingConfig = ReprocessingConfiguration.Load(reprocessingSection); + + domainConfiguration.ExtensionConfigurations.Set(reprocessingConfig); +``` + +or, with use of extension method, like + +```csharp + + var app = builder.Build(); + + //... + + var configurationRoot = app.Configuration; + var extensionsGroupSection = configurationRoot.GetSection("Orm.Extensions"); + var reprocessingSection = extensionsGroupSection.GetSection("Xtensive.Orm.Reprocessing"); + + domainConfiguration.ConfigureReprocessingExtension(reprocessingSection); +``` diff --git a/Extensions/Xtensive.Orm.Reprocessing/Xtensive.Orm.Reprocessing.csproj b/Extensions/Xtensive.Orm.Reprocessing/Xtensive.Orm.Reprocessing.csproj index 99a2631fc2..dcc997498e 100644 --- a/Extensions/Xtensive.Orm.Reprocessing/Xtensive.Orm.Reprocessing.csproj +++ b/Extensions/Xtensive.Orm.Reprocessing/Xtensive.Orm.Reprocessing.csproj @@ -21,6 +21,10 @@ + + + + . diff --git a/Extensions/Xtensive.Orm.Security.Tests/App.config b/Extensions/Xtensive.Orm.Security.Tests/App.config index 5d3d3d31f0..8e2061897b 100644 --- a/Extensions/Xtensive.Orm.Security.Tests/App.config +++ b/Extensions/Xtensive.Orm.Security.Tests/App.config @@ -1,9 +1,10 @@ - +
+
@@ -16,7 +17,13 @@ + + + + + + \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Security.Tests/SecuritySettings.config b/Extensions/Xtensive.Orm.Security.Tests/SecuritySettings.config new file mode 100644 index 0000000000..e5ec7efdad --- /dev/null +++ b/Extensions/Xtensive.Orm.Security.Tests/SecuritySettings.config @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + sha1 + + + sha256 + + + sha384 + + + sha512 + + + md5 + + + + + + + + NotDefault + + + + + + + + + + + + + + sha1 + NotDefault + + + + sha1 + NotDefault + + + + sha1 + NotDefault + + + + sha1 + NotDefault + + + + + + + + + sha1 + NotDefault + + + + sha1 + NotDefault + + + + sha1 + NotDefault + + + + sha1 + NotDefault + + + + + + + + + + + + + + + sha1 + + + + + sha256 + + + + + sha384 + + + + + sha512 + + + + + md5 + + + + + + + + + + + NotDefault + + + + + + + + + + + sha1 + + + NotDefault + + + + + + sha1 + + + NotDefault + + + + + + sha1 + + + NotDefault + + + + + + sha1 + + + NotDefault + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Security.Tests/Tests/ConfigurationTests.cs b/Extensions/Xtensive.Orm.Security.Tests/Tests/ConfigurationTests.cs index 885724895a..acb56731ae 100644 --- a/Extensions/Xtensive.Orm.Security.Tests/Tests/ConfigurationTests.cs +++ b/Extensions/Xtensive.Orm.Security.Tests/Tests/ConfigurationTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2021 Xtensive LLC. +// Copyright (C) 2011-2024 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Dmitri Maximov @@ -17,7 +17,7 @@ public class ConfigurationTests : HasConfigurationAccessTest [Test] public void HashingServiceNameTest() { - var section = (Configuration.ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security.WithName"); + var section = (ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security.WithName"); Assert.That(section, Is.Not.Null); Assert.That(section.HashingService, Is.Not.Null); Assert.That(section.HashingService.Name, Is.Not.Null); @@ -28,10 +28,27 @@ public void HashingServiceNameTest() Assert.That(config.HashingServiceName, Is.EqualTo("md5")); } + [Test] + public void HashingServiceAndAuthenticationServiceNameTest() + { + var section = (ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security.AllDeclared"); + Assert.That(section, Is.Not.Null); + Assert.That(section.HashingService, Is.Not.Null); + Assert.That(section.HashingService.Name, Is.Not.Null); + Assert.That(section.HashingService.Name, Is.EqualTo("sha1")); + Assert.That(section.AuthenticationService.Name, Is.Not.Null); + Assert.That(section.AuthenticationService.Name, Is.EqualTo("notdefault")); + + var config = SecurityConfiguration.Load(Configuration, "Xtensive.Orm.Security.AllDeclared"); + Assert.That(config, Is.Not.Null); + Assert.That(config.HashingServiceName, Is.EqualTo("sha1")); + Assert.That(config.AuthenticationServiceName, Is.EqualTo("notdefault")); + } + [Test] public void HashingServiceEmptyTest() { - var section = (Configuration.ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security.WithoutName"); + var section = (ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security.WithoutName"); Assert.That(section, Is.Not.Null); Assert.That(section.HashingService, Is.Not.Null); Assert.That(section.HashingService.Name, Is.Null.Or.Empty); @@ -44,7 +61,7 @@ public void HashingServiceEmptyTest() [Test] public void HashingServiceAbsentTest() { - var section = (Configuration.ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security.Empty"); + var section = (ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security.Empty"); Assert.That(section, Is.Not.Null); Assert.That(section.HashingService, Is.Not.Null); Assert.That(section.HashingService.Name, Is.Null.Or.Empty); @@ -57,7 +74,7 @@ public void HashingServiceAbsentTest() [Test] public void HashingServiceNoConfigTest() { - var section = (Configuration.ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security.XXX"); + var section = (ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security.XXX"); Assert.That(section, Is.Null); var config = SecurityConfiguration.Load(Configuration, "Xtensive.Orm.Security.XXX"); @@ -68,7 +85,7 @@ public void HashingServiceNoConfigTest() [Test] public void HashingServiceDefaultTest() { - var section = (Configuration.ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security"); + var section = (ConfigurationSection) Configuration.GetSection("Xtensive.Orm.Security"); Assert.That(section, Is.Not.Null); Assert.That(section.HashingService, Is.Not.Null); Assert.That(section.HashingService.Name, Is.Not.Null.Or.Empty); diff --git a/Extensions/Xtensive.Orm.Security.Tests/Tests/MicrosoftConfigurationTests.cs b/Extensions/Xtensive.Orm.Security.Tests/Tests/MicrosoftConfigurationTests.cs new file mode 100644 index 0000000000..2a1fe93dd1 --- /dev/null +++ b/Extensions/Xtensive.Orm.Security.Tests/Tests/MicrosoftConfigurationTests.cs @@ -0,0 +1,480 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using NUnit.Framework; +using Microsoft.Extensions.Configuration; +using Xtensive.Orm.Security.Configuration; + +namespace Xtensive.Orm.Security.Tests.Configuration +{ + public sealed class JsonConfigurationTests : MicrosoftConfigurationTests + { + protected override ConfigTypes ConfigFormat => ConfigTypes.Json; + + protected override void AddConfigurationFile(IConfigurationBuilder configurationBuilder) + { + _ = configurationBuilder.AddJsonFile("securitysettings.json"); + } + } + + public sealed class XmlConfigurationTests : MicrosoftConfigurationTests + { + protected override ConfigTypes ConfigFormat => ConfigTypes.Xml; + + protected override void AddConfigurationFile(IConfigurationBuilder configurationBuilder) + { + _ = configurationBuilder.AddXmlFile("SecuritySettings.config"); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeEmptyNamesTest(bool useRoot) + { + var secConfig = LoadConfiguration("Xtensive.Orm.Security.NameAttribute.NamesEmpty", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeEmptyAndNonExistentNameTest1(bool useRoot) + { + var secConfig = LoadConfiguration("Xtensive.Orm.Security.NameAttribute.NameExistPartially1", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeEmptyAndNonExistentNameTest2(bool useRoot) + { + var secConfig = LoadConfiguration("Xtensive.Orm.Security.NameAttribute.NameExistPartially2", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeAllNamesTest(bool useRoot) + { + var secConfig = LoadConfiguration("Xtensive.Orm.Security.NameAttribute.AllNames", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha1")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("notdefault")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeOnlyHashingServiceTest(bool useRoot) + { + var secConfig = LoadConfiguration("Xtensive.Orm.Security.NameAttribute.OnlyHashing", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha1")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeOnlyAuthServiceTest(bool useRoot) + { + var secConfig = LoadConfiguration("Xtensive.Orm.Security.NameAttribute.OnlyAuth", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("plain")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("notdefault")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeLowCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration("Xtensive.Orm.Security.NameAttribute.LC", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha1")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("notdefault")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributeUpperCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration("Xtensive.Orm.Security.NameAttribute.UC", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha1")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("notdefault")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameAttributePascalCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration("Xtensive.Orm.Security.NameAttribute.PC", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha1")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("notdefault")); + } + } + + + [TestFixture] + public abstract class MicrosoftConfigurationTests : TestCommon.MicrosoftConfigurationTestBase + { + protected SecurityConfiguration LoadConfiguration(string sectionName, bool useRoot) + { + return useRoot + ? SecurityConfiguration.Load(configurationRoot, sectionName) + : SecurityConfiguration.Load(configurationRoot.GetSection(sectionName)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptySectionCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.Empty", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EmptyNamesTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.AllEmpty", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceEmptyTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.OnlyHashing.Empty", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceMd5Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.OnlyHashing.Md5", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("md5")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceSha1Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.OnlyHashing.Sha1", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha1")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceSha256Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.OnlyHashing.Sha256", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha256")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceSha384Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.OnlyHashing.Sha384", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha384")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceSha512Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.OnlyHashing.Sha512", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha512")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyAuthenticationServiceEmptyNameTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.OnlyAuth.Empty", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyAuthenticationServiceNotDefaultTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.OnlyAuth", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("plain")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("notdefault")); + } + + #region Naming + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInLowCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.Naming.LC", useRoot); + ValidateNamingConfigurationResults(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInUpperCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.Naming.UC", useRoot); + ValidateNamingConfigurationResults(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInCamelCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.Naming.CC", useRoot); + ValidateNamingConfigurationResults(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingInPascalCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.Naming.PC", useRoot); + ValidateNamingConfigurationResults(secConfig); + } + + private static void ValidateNamingConfigurationResults(SecurityConfiguration secConfig) + { + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha1")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("notdefault")); + } + + #endregion + + #region mistype cases + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInLowCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.Mistype.LC", useRoot); + ValidateMistypeConfigurationResults(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInUpperCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.Mistype.UC", useRoot); + ValidateMistypeConfigurationResults(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInCamelCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.Mistype.CC", useRoot); + ValidateMistypeConfigurationResults(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MistypeInPascalCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.Mistype.PC", useRoot); + ValidateMistypeConfigurationResults(secConfig); + } + + private static void ValidateMistypeConfigurationResults(SecurityConfiguration secConfig) + { + CheckConfigurationIsDefault(secConfig); + } + + #endregion + + #region Name as node + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NoNameNodesTest(bool useRoot) + { + IgnoreIfXml(); + + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.Json.NameNode.AllEmpty", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodesAreEmptyTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.Json.NameNode.NamesEmpty", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceNameIsEmptyTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.OnlyHashing.Empty", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceWithNameNodeMd5Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.OnlyHashing.Md5", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("md5")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceWithNameNodeSha1Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.OnlyHashing.Sha1", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha1")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceWithNameNodeSha256Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.OnlyHashing.Sha256", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha256")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceWithNameNodeSha384Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.OnlyHashing.Sha384", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha384")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyHashingServiceWithNameNodeSha512Test(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.OnlyHashing.Sha512", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("sha512")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyAuthenticationServiceWithNameNodeEmptyNameTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.OnlyAuth.Empty", useRoot); + CheckConfigurationIsDefault(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OnlyAuthenticationServiceWithNameNodeNotDefaultTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.OnlyAuth", useRoot); + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("plain")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("notdefault")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodeInLowCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.Naming.LC", useRoot); + ValidateNamingConfigurationResults(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodeInUpperCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.Naming.UC", useRoot); + ValidateNamingConfigurationResults(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodeInCamelCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.Naming.CC", useRoot); + ValidateNamingConfigurationResults(secConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NameNodeInPascalCaseTest(bool useRoot) + { + var secConfig = LoadConfiguration($"Xtensive.Orm.Security.{ConfigFormat}.NameNode.Naming.PC", useRoot); + ValidateNamingConfigurationResults(secConfig); + } + + #endregion + + protected static void CheckConfigurationIsDefault(SecurityConfiguration secConfig) + { + Assert.That(secConfig, Is.Not.Null); + Assert.That(secConfig.HashingServiceName, Is.EqualTo("plain")); + Assert.That(secConfig.AuthenticationServiceName, Is.EqualTo("default")); + } + } +} diff --git a/Extensions/Xtensive.Orm.Security.Tests/Xtensive.Orm.Security.Tests.csproj b/Extensions/Xtensive.Orm.Security.Tests/Xtensive.Orm.Security.Tests.csproj index 2d10b5ce07..b40c1f9f11 100644 --- a/Extensions/Xtensive.Orm.Security.Tests/Xtensive.Orm.Security.Tests.csproj +++ b/Extensions/Xtensive.Orm.Security.Tests/Xtensive.Orm.Security.Tests.csproj @@ -8,6 +8,9 @@ + + + @@ -20,4 +23,12 @@ + + + PreserveNewest + + + PreserveNewest + + \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Security.Tests/securitysettings.json b/Extensions/Xtensive.Orm.Security.Tests/securitysettings.json new file mode 100644 index 0000000000..a7f9eff766 --- /dev/null +++ b/Extensions/Xtensive.Orm.Security.Tests/securitysettings.json @@ -0,0 +1,147 @@ +{ + "Xtensive.Orm.Security.Json.Empty": { + + }, + + "Xtensive.Orm.Security.Json.AllEmpty": { + "HashingService": "", + "AuthenticationService": "" + }, + + "Xtensive.Orm.Security.Json.OnlyHashing.Sha1": { + "HashingService": "sha1" + }, + "Xtensive.Orm.Security.Json.OnlyHashing.Sha256": { + "HashingService": "sha256" + }, + "Xtensive.Orm.Security.Json.OnlyHashing.Sha384": { + "HashingService": "sha384" + }, + "Xtensive.Orm.Security.Json.OnlyHashing.Sha512": { + "HashingService": "sha512" + }, + "Xtensive.Orm.Security.Json.OnlyHashing.Md5": { + "HashingService": "md5" + }, + "Xtensive.Orm.Security.Json.OnlyHashing.Empty": { + "HashingService": "" + }, + + "Xtensive.Orm.Security.Json.OnlyAuth": { + "AuthenticationService": "NotDefault" + }, + "Xtensive.Orm.Security.Json.OnlyAuth.Empty": { + "AuthenticationService": "" + }, + + "Xtensive.Orm.Security.Json.Naming.LC": { + "hashingservice": "sha1", + "authenticationservice": "NotDefault" + }, + "Xtensive.Orm.Security.Json.Naming.UC": { + "HASHINGSERVICE": "sha1", + "AUTHENTICATIONSERVICE": "NotDefault" + }, + "Xtensive.Orm.Security.Json.Naming.CC": { + "hashingService": "sha1", + "authenticationService": "NotDefault" + }, + "Xtensive.Orm.Security.Json.Naming.PC": { + "HashingService": "sha1", + "AuthenticationService": "NotDefault" + }, + + "Xtensive.Orm.Security.Json.Mistype.LC": { + "hashiingservice": "sha1", + "authentticationservice": "NotDefault" + }, + "Xtensive.Orm.Security.Json.Mistype.UC": { + "HASHIINGSERVICE": "sha1", + "AUTHENTTICATIONSERVICE": "NotDefault" + }, + "Xtensive.Orm.Security.Json.Mistype.CC": { + "hashiingService": "sha1", + "authentticationService": "NotDefault" + }, + "Xtensive.Orm.Security.Json.Mistype.PC": { + "HashiingSeervice": "sha1", + "AuthentticationService": "NotDefault" + }, + + + "Xtensive.Orm.Security.Json.NameNode.AllEmpty": { + "HashingService": { + }, + "AuthenticationService": { + } + }, + + "Xtensive.Orm.Security.Json.NameNode.NamesEmpty": { + "HashingService": { + "Name": "" + }, + "AuthenticationService": { + "Name": "" + } + }, + + "Xtensive.Orm.Security.Json.NameNode.OnlyHashing.Sha1": { + "HashingService": { + "Name": "sha1" + } + }, + "Xtensive.Orm.Security.Json.NameNode.OnlyHashing.Sha256": { + "HashingService": { + "Name": "sha256" + } + }, + "Xtensive.Orm.Security.Json.NameNode.OnlyHashing.Sha384": { + "HashingService": { + "Name": "sha384" + } + }, + "Xtensive.Orm.Security.Json.NameNode.OnlyHashing.Sha512": { + "HashingService": { + "Name": "sha512" + } + }, + "Xtensive.Orm.Security.Json.NameNode.OnlyHashing.Md5": { + "HashingService": { + "Name": "md5" + } + }, + "Xtensive.Orm.Security.Json.NameNode.OnlyHashing.Empty": { + "HashingService": { + "Name": "" + } + }, + + "Xtensive.Orm.Security.Json.NameNode.OnlyAuth": { + "AuthenticationService": { + "Name": "NotDefault" + } + }, + "Xtensive.Orm.Security.Json.NameNode.OnlyAuth.Empty": { + "AuthenticationService": { + "Name": "" + } + }, + + + "Xtensive.Orm.Security.Json.NameNode.Naming.LC": { + "HashingService": { "name": "sha1" }, + "AuthenticationService": { "name": "NotDefault" } + }, + "Xtensive.Orm.Security.Json.NameNode.Naming.UC": { + "HashingService": { "NAME": "sha1" }, + "AuthenticationService": { "NAME": "NotDefault" } + }, + "Xtensive.Orm.Security.Json.NameNode.Naming.CC": { + "HashingService": { "naMe": "sha1" }, + "AuthenticationService": { "naMe": "NotDefault" } + }, + "Xtensive.Orm.Security.Json.NameNode.Naming.PC": { + "HashingService": { "NaMe": "sha1" }, + "AuthenticationService": { "NaMe": "NotDefault" } + } +} diff --git a/Extensions/Xtensive.Orm.Security/Configuration/Elements/ConfigurationSection.cs b/Extensions/Xtensive.Orm.Security/Configuration/Elements/ConfigurationSection.cs index ca3fe8cf06..86899f68d4 100644 --- a/Extensions/Xtensive.Orm.Security/Configuration/Elements/ConfigurationSection.cs +++ b/Extensions/Xtensive.Orm.Security/Configuration/Elements/ConfigurationSection.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011 Xtensive LLC. +// Copyright (C) 2011-2024 Xtensive LLC. // All rights reserved. // For conditions of distribution and use, see license. // Created by: Dmitri Maximov @@ -18,6 +18,7 @@ public class ConfigurationSection : System.Configuration.ConfigurationSection /// Gets default section name for security configuration. /// Value is "Xtensive.Orm.Security". ///
+ [Obsolete("Use SecurityConfiguration.DefaultSectionName instead")] public static readonly string DefaultSectionName = "Xtensive.Orm.Security"; private const string HashingServiceElementName = "hashingService"; diff --git a/Extensions/Xtensive.Orm.Security/Configuration/SecurityConfiguration.cs b/Extensions/Xtensive.Orm.Security/Configuration/SecurityConfiguration.cs index 8346a6497c..2ecd46f8cb 100644 --- a/Extensions/Xtensive.Orm.Security/Configuration/SecurityConfiguration.cs +++ b/Extensions/Xtensive.Orm.Security/Configuration/SecurityConfiguration.cs @@ -1,11 +1,16 @@ -// Copyright (C) 2011 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2011-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Dmitri Maximov // Created: 2011.06.10 using System; using System.Configuration; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Configuration; +using Xtensive.Core; +using Xtensive.Orm.Configuration; namespace Xtensive.Orm.Security.Configuration { @@ -13,25 +18,48 @@ namespace Xtensive.Orm.Security.Configuration /// The configuration of the security system. ///
[Serializable] - public class SecurityConfiguration + public class SecurityConfiguration : ConfigurationBase { /// - /// Default SectionName value: - /// "". + /// Default section in configuration. Used if custom name is not provided. /// public const string DefaultSectionName = "Xtensive.Orm.Security"; + internal const string HashingServiceElementName = "HashingService"; + internal const string AuthenticationServiceElementName = "AuthenticationService"; + + internal const string DefaultHashingServiceName = "plain"; + internal const string DefaultAuthenticationServiceName = "default"; + /// /// Gets or sets the name of the hashing service. /// /// The name of the hashing service. - public string HashingServiceName { get; private set; } + [ConfigurationKeyName(HashingServiceElementName)] + public string HashingServiceName { get; set; } /// /// Gets or sets the name of the authentication service. /// /// The name of the authentication service. - public string AuthenticationServiceName { get; private set; } + [ConfigurationKeyName(AuthenticationServiceElementName)] + public string AuthenticationServiceName { get; set; } + + /// + protected override SecurityConfiguration CreateClone() => new SecurityConfiguration(); + + /// + protected override void CopyFrom(ConfigurationBase source) + { + base.CopyFrom(source); + + var configuration = (SecurityConfiguration) source; + configuration.HashingServiceName = configuration.HashingServiceName; + configuration.AuthenticationServiceName = configuration.AuthenticationServiceName; + } + + /// + public override SecurityConfiguration Clone() => (SecurityConfiguration) base.Clone(); /// /// Loads the @@ -79,29 +107,105 @@ public static SecurityConfiguration Load(System.Configuration.Configuration conf /// The . public static SecurityConfiguration Load(System.Configuration.Configuration configuration, string sectionName) { - var configurationSection = (ConfigurationSection)configuration.GetSection(sectionName); + var configurationSection = (ConfigurationSection) configuration.GetSection(sectionName); return GetConfigurationFromSection(configurationSection); } private static SecurityConfiguration GetConfigurationFromSection(ConfigurationSection configurationSection) { - var result = new SecurityConfiguration(); + var result = new SecurityConfiguration(true); - string hashingService = configurationSection==null - ? string.Empty + var hashingService = configurationSection == null + ? string.Empty : configurationSection.HashingService.Name; - if (string.IsNullOrEmpty(hashingService)) - hashingService = "plain"; - result.HashingServiceName = hashingService.ToLowerInvariant(); + if (!string.IsNullOrEmpty(hashingService)) { + result.HashingServiceName = hashingService.ToLowerInvariant(); + } - string authenticationService = configurationSection==null - ? string.Empty + var authenticationService = configurationSection == null + ? string.Empty : configurationSection.AuthenticationService.Name; - if (string.IsNullOrEmpty(authenticationService)) - authenticationService = "default"; - result.AuthenticationServiceName = authenticationService.ToLowerInvariant(); + if (!string.IsNullOrEmpty(authenticationService)) { + result.AuthenticationServiceName = authenticationService.ToLowerInvariant(); + } return result; } + + /// + /// Loads the from given configuration section. + /// + /// to load section from. + /// Name of the section where configuration is stored. + /// Loaded configuration or configuration with default settings. + public static SecurityConfiguration Load(IConfiguration configuration, string sectionName = null) + { + ArgumentValidator.EnsureArgumentNotNull(configuration, nameof(configuration)); + + if (configuration is IConfigurationRoot configurationRoot) { + return Load(configurationRoot, sectionName); + } + else if (configuration is IConfigurationSection configurationSection) { + return Load(configurationSection); + } + + throw new NotSupportedException("Type of configuration is not supported."); + } + + + /// + /// Loads the from given configuration section. + /// + /// to load section from. + /// Name of the section where configuration is stored. + /// Loaded configuration or configuration with default settings. + public static SecurityConfiguration Load(IConfigurationRoot configurationRoot, string sectionName = null) + { + ArgumentValidator.EnsureArgumentNotNull(configurationRoot, nameof(configurationRoot)); + + var configuration = new NamelessFormatSecurityConfigurationReader().Read(configurationRoot, sectionName ?? DefaultSectionName); + if (configuration != null) { + return configuration; + } + + configuration = new BasedOnNamesFormatSecurityConfigurationReader().Read(configurationRoot, sectionName ?? DefaultSectionName); + return configuration ?? new SecurityConfiguration(true); + } + + /// + /// Loads the from given configuration section. + /// + /// to load from. + /// Loaded configuration or configuration with default settings. + public static SecurityConfiguration Load(IConfigurationSection configurationSection) + { + ArgumentValidator.EnsureArgumentNotNull(configurationSection, nameof(configurationSection)); + + var configuration = new NamelessFormatSecurityConfigurationReader().Read(configurationSection); + if (configuration != null) { + return configuration; + } + + configuration = new BasedOnNamesFormatSecurityConfigurationReader().Read(configurationSection); + return configuration ?? new SecurityConfiguration(true); + } + + /// + /// Creates instance of with no properties initialized. + /// + public SecurityConfiguration() + { + } + + /// + /// Creates instance of with properties initialized on demand. + /// + internal SecurityConfiguration(bool initWithDefaults) + { + if (initWithDefaults) { + HashingServiceName = DefaultHashingServiceName; + AuthenticationServiceName = DefaultAuthenticationServiceName; + } + } } -} \ No newline at end of file +} diff --git a/Extensions/Xtensive.Orm.Security/Configuration/SecurityConfigurationReaders.cs b/Extensions/Xtensive.Orm.Security/Configuration/SecurityConfigurationReaders.cs new file mode 100644 index 0000000000..8597af2750 --- /dev/null +++ b/Extensions/Xtensive.Orm.Security/Configuration/SecurityConfigurationReaders.cs @@ -0,0 +1,106 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Xtensive.Core; +using Xtensive.Orm.Configuration; + +namespace Xtensive.Orm.Security.Configuration +{ + internal sealed class NamelessFormatSecurityConfigurationReader : SecurityConfigurationReader + { + protected override SecurityConfiguration ReadInternal(IConfigurationSection configuration) + { + try { + var configAsIs = configuration.Get(); + if (configAsIs != null && (configAsIs.AuthenticationServiceName ?? configAsIs.HashingServiceName) != null) { + configAsIs.HashingServiceName = string.IsNullOrEmpty(configAsIs.HashingServiceName) + ? SecurityConfiguration.DefaultHashingServiceName + : configAsIs.HashingServiceName.ToLowerInvariant(); + configAsIs.AuthenticationServiceName = string.IsNullOrEmpty(configAsIs.AuthenticationServiceName) + ? SecurityConfiguration.DefaultAuthenticationServiceName + : (configAsIs.AuthenticationServiceName?.ToLowerInvariant()); + return configAsIs; + } + } + catch { + return null; + } + + var children = configuration.GetChildren(); + return !children.Any() + ? new SecurityConfiguration(true) + : null; + } + } + + internal sealed class BasedOnNamesFormatSecurityConfigurationReader : SecurityConfigurationReader + { + private const string ServiceNameAttributeName = "name"; + + protected override SecurityConfiguration ReadInternal(IConfigurationSection configuration) + { + + var hashingServiceSection = configuration.GetSection(SecurityConfiguration.HashingServiceElementName); + var authenticationServiceSection = configuration.GetSection(SecurityConfiguration.AuthenticationServiceElementName); + + if (hashingServiceSection == null && authenticationServiceSection == null) { + return null; + } + + var hashingServiceName = hashingServiceSection.GetSection(ServiceNameAttributeName)?.Value; + if (hashingServiceName == null) { + var children = hashingServiceSection.GetChildren().ToList(); + if (children.Count > 0) { + hashingServiceName = children[0].GetSection(ServiceNameAttributeName).Value; + } + } + + var authenticationServiceName = authenticationServiceSection.GetSection(ServiceNameAttributeName)?.Value; + if (authenticationServiceName == null) { + var children = authenticationServiceSection.GetChildren().ToList(); + if (children.Count > 0) { + authenticationServiceName = children[0].GetSection(ServiceNameAttributeName).Value; + } + } + if ((hashingServiceName ?? authenticationServiceName) != null) { + var securityConfiguration = new SecurityConfiguration(true); + if (!hashingServiceName.IsNullOrEmpty()) { + securityConfiguration.HashingServiceName = hashingServiceName.ToLowerInvariant(); + } + + if (!authenticationServiceName.IsNullOrEmpty()) { + securityConfiguration.AuthenticationServiceName = authenticationServiceName.ToLowerInvariant(); + } + + return securityConfiguration; + } + return null; + } + } + + internal abstract class SecurityConfigurationReader : IConfigurationSectionReader + { + public SecurityConfiguration Read(IConfigurationSection configurationSection) => ReadInternal(configurationSection); + + public SecurityConfiguration Read(IConfigurationSection configurationSection, string nameOfConfiguration) => + throw new NotSupportedException(); + + public SecurityConfiguration Read(IConfigurationRoot configurationRoot) => + Read(configurationRoot, SecurityConfiguration.DefaultSectionName); + + public SecurityConfiguration Read(IConfigurationRoot configurationRoot, string sectionName) + { + var section = configurationRoot.GetSection(sectionName); + return ReadInternal(section); + } + + public SecurityConfiguration Read(IConfigurationRoot configurationRoot, string sectionName, string nameOfConfiguration) => + throw new NotSupportedException(); + + protected abstract SecurityConfiguration ReadInternal(IConfigurationSection section); + } +} diff --git a/Extensions/Xtensive.Orm.Security/DomainConfugurationExtensions.cs b/Extensions/Xtensive.Orm.Security/DomainConfugurationExtensions.cs new file mode 100644 index 0000000000..d480dc4995 --- /dev/null +++ b/Extensions/Xtensive.Orm.Security/DomainConfugurationExtensions.cs @@ -0,0 +1,108 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using Microsoft.Extensions.Configuration; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Security.Configuration; + +namespace Xtensive.Orm.Security +{ + /// + /// Contains extensions for DomainConfiguration that help to configure the extension. + /// + public static class DomainConfugurationSecurityExtensions + { + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// instance with configured extension. + public static DomainConfiguration ConfigureSecurityExtension(this DomainConfiguration domainConfiguration) => + ConfigureSecurityExtension(domainConfiguration, SecurityConfiguration.Load()); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Section name. + /// instance with configured extension. + public static DomainConfiguration ConfigureSecurityExtension(this DomainConfiguration domainConfiguration, + string configurationSectionName) => + ConfigureSecurityExtension(domainConfiguration, SecurityConfiguration.Load(configurationSectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuraton to load from. + /// instance with configured extension. + public static DomainConfiguration ConfigureSecurityExtension(this DomainConfiguration domainConfiguration, + System.Configuration.Configuration configuration) => + ConfigureSecurityExtension(domainConfiguration, SecurityConfiguration.Load(configuration)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuraton to load from. + /// Section in + /// instance with configured extension. + public static DomainConfiguration ConfigureSecurityExtension(this DomainConfiguration domainConfiguration, + System.Configuration.Configuration configuration, string sectionName) => + ConfigureSecurityExtension(domainConfiguration, SecurityConfiguration.Load(configuration, sectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuraton to load from. + /// Section in + /// instance with configured extension. + public static DomainConfiguration ConfigureSecurityExtension(this DomainConfiguration domainConfiguration, + IConfiguration configuration, string sectionName = null) => + ConfigureSecurityExtension(domainConfiguration, SecurityConfiguration.Load(configuration, sectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuraton to load from. + /// Section in + /// instance with configured extension. + public static DomainConfiguration ConfigureSecurityExtension(this DomainConfiguration domainConfiguration, + IConfigurationRoot configurationRoot, string sectionName = null) => + ConfigureSecurityExtension(domainConfiguration, SecurityConfiguration.Load(configurationRoot, sectionName)); + + /// + /// Loads configuration by calling + /// and uses it to configure the extension. + /// + /// Domain configuration. + /// Configuration section to load from. + /// instance with configured extension. + public static DomainConfiguration ConfigureSecurityExtension(this DomainConfiguration domainConfiguration, + IConfigurationSection configurationSection) => + ConfigureSecurityExtension(domainConfiguration, SecurityConfiguration.Load(configurationSection)); + + /// + /// Configures the extension with given security configuration instance. + /// + /// Domain configuration. + /// Security configuration instance. + /// instance with configured extension. + public static DomainConfiguration ConfigureSecurityExtension(this DomainConfiguration domainConfiguration, + SecurityConfiguration securityConfiguration) + { + domainConfiguration.ExtensionConfigurations.Set(securityConfiguration); + domainConfiguration.Types.Register(typeof(DomainConfugurationSecurityExtensions).Assembly); + return domainConfiguration; + } + } +} \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Security/NugetContent/ReadMe.md b/Extensions/Xtensive.Orm.Security/NugetContent/ReadMe.md index 143b111b76..2b5f0f72d8 100644 --- a/Extensions/Xtensive.Orm.Security/NugetContent/ReadMe.md +++ b/Extensions/Xtensive.Orm.Security/NugetContent/ReadMe.md @@ -8,7 +8,7 @@ There are 2 main parts that can also be used separately: authentication services Prerequisites ------------- -DataObjects.Net 7.0.x or later (http://dataobjects.net) +DataObjects.Net 7.1.x or later (http://dataobjects.net) How to use ---------- @@ -43,6 +43,8 @@ and set up the desired hashing service: ``` +Other examples of how to configure the extension are in section below + Examples -------- @@ -311,4 +313,263 @@ customers that have IsAutomobileIndustry property set to true, e.g.: transaction.Complete(); } } -``` \ No newline at end of file +``` + + + +Examples of how to configure extension +-------------------------------------- + +Additionally to "How to use" section it provides extra examples of how to configure and/or read extension configuration. + +The example in "How to use" section uses old fasioned API of configuration files, yet usable in many applications. But +there are some cases which may require usage of different API or work-around certain cases with existing one. + +**Example #1** Reading old-style configuration of an assembly in NET 5 and newer. + +Due to new architecture without AppDomain (which among the other things was in charge of gathering configuration files of loaded assemblies +as it would be one configuration file) ```System.Configuration.ConfigurationManager``` now reads only configuration file of actual executable, loaded +assemblies' configuration files stay unreachable by default, though there is need to read some data from them. +A great example is test projects which are usually get loaded by test runner executable, and the only configuration accessible in this case +is test runner one. + +Extra step is required to read configuration files in such cases. Thankfully, ```ConfigurationManager``` has methods to get access to assemblies' configurations. + +To get access to an assembly configuration file it should be opened explicitly by + +```csharp + var configuration = ConfigurationManager.OpenExeConfiguration(typeof(SomeTypeInConfigOwnerAssembly).Assembly.Location); +``` + +The instance returned from ```OpenExeConfiguration``` provides access to sections of the assembly configuration. DataObjects.Net configurations +(```DomainConfiguration```, ```SecurityConfiguration```, etc.) have ```Load()``` methods that can recieve this instance. +```SecurityConfiguration``` can be read like so + +```csharp + var configuration = ConfigurationManager.OpenExeConfiguration(typeof(SomeTypeInConfigOwnerAssembly).Assembly.Location); + var securityConfig = SecurityConfiguration.Load(configuration); + + // loaded configuration should be manually placed to + domainConfiguration.ExtensionConfigurations.Set(securityConfig); +``` + +The ```domainConfiguration.ExtensionConfigurations``` is a new unified place from which the extension will try to get its configuration +instead of calling default parameterless ```Load()``` method, which has not a lot of sense now, though the method is kept as a second source +for backwards compatibility. + +For more convenience, ```DomainConfiguration``` extensions are provided, which make code more neater. +For instance, + +```csharp + var configuration = ConfigurationManager.OpenExeConfiguration(typeof(SomeTypeInConfigOwnerAssembly).Assembly.Location); + + var domainConfiguration = DomainConfiguration.Load(configuration); + + // the extension hides getting configuration with SecurityConfiguration.Load(configuration) + // and also putting it to ExtensionConfigurations collection. + domainConfiguration.ConfigureSecurityExtension(configuration); +``` + +Remember the requirement to register ```Xtensive.Orm.Security``` to domain? The extension tries to register this assembly to ```DomainConfiguration.Types``` collection +so even if you miss registration but called extension method required types of Security extension will be registered in Domain types. + +Custom section names are also supported if for some reason default section name is not used. + + +**Example #2** Reading old-style configuration of an assembly in a project that uses appsettings.json file. + +If for some reason there is need to keep the old-style configuration then there is a work-around as well. +Static configuration manager provides method ```OpenMappedExeConfiguration()``` which allows to get access to +any *.config file as ```System.Configuration.Configuration``` instance. For example, + +```csharp + ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap(); + configFileMap.ExeConfigFilename = "Orm.config"; //or other file name, the file should exist bin folder + var configuration = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None); +``` + +After that, as in previous example, the instance can be passed to ```Load``` method of ```SecurityConfiguration``` to read extension configuration +and later put it to ```DomainConfiguration.ExtensionConfigurations``` + +```csharp + var securityConfiguration = SecurityConfiguration.Load(configuration); + + domainConfiguration.ExtensionConfigurations.Set(securityConfiguration); +``` + +Extension usage will look like + +```csharp + domainConfiguration.ConfigureSecurityExtension(configuration); +``` + + +**Example #3** Configure using Microsoft.Extensions.Configuration API. + +This API allows to have configurations in various forms including JSON and XML formats. +Loading of such files may differ depending on .NET version, check Microsoft manuals for instructions. + +Allowed JSON and XML configuration definition look like below + +```xml + + + sha512 + CustomAuthenticationService + + +``` + +```json +{ + "Xtensive.Orm.Security": { + "HashingService" : "sha512", + "AuthenticationService" : "CustomAuthenticationService" + } +} +``` + +The API has certain issues with XML elements with attributes so it is recommended to use +more up-to-date attributeless nodes. +For JSON it is pretty clear, almost averyone knows its format. + +```SecurityConfiguration.Load``` method can accept different types of abstractions from the API, including +- ```Microsoft.Extensions.Configuration.IConfiguration```; +- ```Microsoft.Extensions.Configuration.IConfigurationRoot```; +- ```Microsoft.Extensions.Configuration.IConfigurationSection```. + +Loading of configuration may look like + +```csharp + + var app = builder.Build(); + + //... + + // tries to load from default section "Xtensive.Orm.Security" + var securityConfig = SecurityConfiguration.Load(app.Configuration); + + domainConfiguration.ExtensionConfigurations.Set(securityConfig); +``` + +or, with use of extension, like + + +```csharp + + var app = builder.Build(); + + //... + + // Tries to load from default section "Xtensive.Orm.Security" + // and put it into domainConfiguration.ExtensionConfigurations. + // Additionally, registers types of "Xtensive.Orm.Security" assembly. + + domainConfiguration.ConfigureSecurityExtension(app.Configuration); +``` + + + +**Example #4** Configure using Microsoft.Extensions.Configuration API from section with non-default name. + +For configurations like + +```xml + + + sha512 + CustomAuthenticationService + + +``` + +```json +{ + "Orm.Security": { + "HashingService" : "sha512", + "AuthenticationService" : "CustomAuthenticationService" + } +} + +Loading of configuration may look like + +```csharp + var app = builder.Build(); + + //... + + var securityConfig = SecurityConfiguration.Load(app.Configuration, "Orm.Security"); + + domainConfiguration.ExtensionConfigurations.Set(securityConfig); +``` + +or, with use of extension, like + +```csharp + var app = builder.Build(); + + //... + + domainConfiguration.ConfigureSecurityExtension(app.Configuration, "Orm.Security"); +``` + + +**Example #5** Configure using Microsoft.Extensions.Configuration API from sub-section deeper in section tree. + +If for some reason extension configuration should be moved deeper in section tree like something below + +```xml + + + + sha512 + CustomAuthenticationService + + + +``` + +or in JSON + +```json +{ + "Orm.Extensions": { + "Xtensive.Orm.Security": { + "HashingService" : "sha512", + "AuthenticationService" : "CustomAuthenticationService" + } + } +} +``` + +Then section must be provided manually, code may look like + +```csharp + + var app = builder.Build(); + + //... + + var configurationRoot = app.Configuration; + var extensionsGroupSection = configurationRoot.GetSection("Orm.Extensions"); + var securitySection = extensionsGroupSection.GetSection("Xtensive.Orm.Security"); + + var securityConfig = SecurityConfiguration.Load(securitySection); + + domainConfiguration.ExtensionConfigurations.Set(securityConfig); +``` + +or, with use of extension method, like + +```csharp + + var app = builder.Build(); + + //... + + var configurationRoot = app.Configuration; + var extensionsGroupSection = configurationRoot.GetSection("Orm.Extensions"); + var securitySection = extensionsGroupSection.GetSection("Xtensive.Orm.Security"); + + domainConfiguration.ConfigureSecurityExtension(securitySection); +``` diff --git a/Extensions/Xtensive.Orm.Security/SessionExtensions.cs b/Extensions/Xtensive.Orm.Security/SessionExtensions.cs index 4df2ecefb4..39f02e4c89 100644 --- a/Extensions/Xtensive.Orm.Security/SessionExtensions.cs +++ b/Extensions/Xtensive.Orm.Security/SessionExtensions.cs @@ -4,6 +4,7 @@ // Created by: Dmitri Maximov // Created: 2011.05.22 +using System; using System.Security.Principal; using Xtensive.Core; using Xtensive.Orm.Security; @@ -12,6 +13,7 @@ namespace Xtensive.Orm { + /// /// Session extension methods for security-related stuff. /// @@ -24,12 +26,16 @@ public static class SessionExtensions /// instance. public static SecurityConfiguration GetSecurityConfiguration(this Session session) { - var result = session.Domain.Extensions.Get(); - if (result == null) { - result = SecurityConfiguration.Load(); - session.Domain.Extensions.Set(result); + var fromNewSource = session.Domain.Configuration.ExtensionConfigurations.Get(); + if (fromNewSource!=null) + return fromNewSource; + + var fromOldSource = session.Domain.Extensions.Get(); + if (fromOldSource == null) { + fromOldSource = SecurityConfiguration.Load(); + session.Domain.Extensions.Set(fromOldSource); } - return result; + return fromOldSource; } /// diff --git a/Extensions/Xtensive.Orm.Security/Xtensive.Orm.Security.csproj b/Extensions/Xtensive.Orm.Security/Xtensive.Orm.Security.csproj index 91e131059a..4e15c012c7 100644 --- a/Extensions/Xtensive.Orm.Security/Xtensive.Orm.Security.csproj +++ b/Extensions/Xtensive.Orm.Security/Xtensive.Orm.Security.csproj @@ -22,6 +22,10 @@ + + + + diff --git a/Extensions/Xtensive.Orm.Tracking/NugetContent/ReadMe.md b/Extensions/Xtensive.Orm.Tracking/NugetContent/ReadMe.md index baefbb2de6..81bf58b227 100644 --- a/Extensions/Xtensive.Orm.Tracking/NugetContent/ReadMe.md +++ b/Extensions/Xtensive.Orm.Tracking/NugetContent/ReadMe.md @@ -1,4 +1,4 @@ -Xtensive.Orm.Tracking +Xtensive.Orm.Tracking ===================== Summary @@ -7,7 +7,7 @@ Provides tracking/auditing funtionality on Session/Domain level. Prerequisites ------------- -DataObjects.Net 7.0.x or later (http://dataobjects.net) +DataObjects.Net 7.1.x or later (http://dataobjects.net) Implementation -------------- diff --git a/Extensions/Xtensive.Orm.Web/NugetContent/ReadMe.md b/Extensions/Xtensive.Orm.Web/NugetContent/ReadMe.md index d19d1779f1..08223c0fb1 100644 --- a/Extensions/Xtensive.Orm.Web/NugetContent/ReadMe.md +++ b/Extensions/Xtensive.Orm.Web/NugetContent/ReadMe.md @@ -1,4 +1,4 @@ -Xtensive.Orm.Web +Xtensive.Orm.Web ================ Summary @@ -11,7 +11,7 @@ by default unless an exeption appeared. (more info on https://dataobjects.net) Prerequisites ------------- -DataObjects.Net 7 or later (https://dataobjects.net) +DataObjects.Net 7.1 or later (https://dataobjects.net) Usage of action filter ---------------------- diff --git a/Orm/Xtensive.Orm.Tests/Configuration/MicrosoftConfigurationTests.cs b/Orm/Xtensive.Orm.Tests/Configuration/MicrosoftConfigurationTests.cs new file mode 100644 index 0000000000..c47023207a --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Configuration/MicrosoftConfigurationTests.cs @@ -0,0 +1,2331 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Transactions; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.Extensions.Configuration; +using NUnit.Framework; +using Xtensive.Core; +using Xtensive.Orm.Configuration; + +namespace Xtensive.Orm.Tests.Configuration.TypesToUseInTests +{ + namespace NestedNamespace + { + [HierarchyRoot] + public class DummyNestedEntity1 : Entity + { + [Key, Field] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + public class DummyNestedEntity2 : Entity + { + [Key, Field] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } + } + + [HierarchyRoot] + public class DummyEntity1 : Entity + { + [Key, Field] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + public class DummyEntity2 : Entity + { + [Key, Field] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + public class DummyEntity3 : Entity + { + [Key, Field] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } +} + +namespace Xtensive.Orm.Tests.Configuration +{ + [TestFixture] + public sealed class AppConfigStyleConfigurationTest : MicrosoftConfigurationTestBase + { + protected override string Postfix => "AppConfig"; + + protected override bool NameAttributeUnique => false; + + protected override void RegisterConfigurationFile(ConfigurationBuilder builder) + { + _ = builder.AddXmlFile("domainSettings.config"); + } + } + + [TestFixture] + public sealed class XmlConfigurationTest : MicrosoftConfigurationTestBase + { + protected override string Postfix => "Xml"; + + protected override void RegisterConfigurationFile(ConfigurationBuilder builder) + { + _ = builder.AddXmlFile("domainSettings.config"); + } + } + + [TestFixture] + public sealed class JsonConfigurationTest : MicrosoftConfigurationTestBase + { + protected override string Postfix => "Json"; + + protected override void RegisterConfigurationFile(ConfigurationBuilder builder) + { + _ = builder.AddJsonFile("domainSettings.json"); + } + } + + public abstract class MicrosoftConfigurationTestBase + { + private IConfigurationRoot configuration; + private IConfigurationSection configurationSection; + + protected abstract string Postfix { get; } + protected virtual bool NameAttributeUnique => true; + + protected virtual string DefaultSectionName => $"Xtensive.Orm.{Postfix}"; + + [OneTimeSetUp] + public void OneTimeSetup() + { + var configurationBuilder = new ConfigurationBuilder(); + _ = configurationBuilder.SetBasePath(Directory.GetCurrentDirectory()); + + RegisterConfigurationFile(configurationBuilder); + configuration = configurationBuilder.Build(); + + configurationSection = configuration.GetSection(DefaultSectionName); + if (!configurationSection.GetChildren().Any()) { + throw new InconclusiveException($"{DefaultSectionName} section seems to be empty. Check registered file with domain settings"); + } + } + + protected abstract void RegisterConfigurationFile(ConfigurationBuilder builder); + + private DomainConfiguration LoadDomainConfiguration(string domainName, bool useRoot) + { + return useRoot + ? DomainConfiguration.Load(configuration, DefaultSectionName, domainName) + : DomainConfiguration.Load(configurationSection, domainName); + } + private LoggingConfiguration LoadLoggingConfiguration(IConfigurationSection customConfigurationSection = null) + { + var loggingConfiguration = LoggingConfiguration.Load(customConfigurationSection ?? configurationSection); + return loggingConfiguration; + } + + + #region Simple Domain settings that used to be attributes + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ProviderAndConnectionStringTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithProviderAndConnectionString", useRoot); + Assert.That(domainConfig.ConnectionInfo.Provider, Is.EqualTo(WellKnown.Provider.Sqlite)); + Assert.That(domainConfig.ConnectionInfo.ConnectionString, Is.EqualTo("Data Source=DO-Testsaaa.db3")); + Assert.That(domainConfig.ConnectionInfo.ConnectionUrl, Is.Null); + + ValidateAllDefault(domainConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ConnectionUrlTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithConnectionUrl", useRoot); + Assert.That(domainConfig.ConnectionInfo.Provider, Is.EqualTo(WellKnown.Provider.Sqlite)); + Assert.That(domainConfig.ConnectionInfo.ConnectionString, Is.Null); + Assert.That(domainConfig.ConnectionInfo.ConnectionUrl.Url, Is.EqualTo("sqlite:///DO-Tests.db3")); + + ValidateAllDefault(domainConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomValidKeyCacheSizeTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithCustomValidKeyCacheSize", useRoot); + + ValidateAllDefaultExcept(domainConfig, ((d) => d.KeyCacheSize, 192)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomInvalidKeyCacheSizeTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithCustomInvalidKeyCacheSize", useRoot)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomValidKeyGeneratorCacheSizeTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithCustomValidKeyGeneratorCacheSize", useRoot); + + ValidateAllDefaultExcept(domainConfig, ((d) => d.KeyGeneratorCacheSize, 192)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomInvalidKeyGeneratorCacheSizeTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithCustomInvalidKeyGeneratorCacheSize", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomValidQueryCacheSizeTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithCustomValidQueryCacheSize", useRoot); + + ValidateAllDefaultExcept(domainConfig, ((d) => d.QueryCacheSize, 192)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomInvalidQueryCacheSizeTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithCustomInvalidQueryCacheSize", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomValidRecordSetMappingCacheSizeTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithCustomValidRecordSetMappingCacheSize", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.RecordSetMappingCacheSize, 192)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomInvalidRecordSetMappingCacheSizeTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithCustomInvalidRecordSetMappingCacheSize", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomDefaultDatabaseTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithCustomDatabase", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.DefaultDatabase, "MyFancyDatabase"), + ((d) => d.IsMultidatabase, true), + ((d) => d.IsMultischema, true) + ); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CustomDefaultSchemaTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithCustomSchema", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.DefaultSchema, "MyFancySchema"), + ((d) => d.IsMultidatabase, false), + ((d) => d.IsMultischema, true)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void UpgradeModesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithUpgradeMode1", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.UpgradeMode, DomainUpgradeMode.Default)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithUpgradeMode2", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.UpgradeMode, DomainUpgradeMode.Recreate)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithUpgradeMode3", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.UpgradeMode, DomainUpgradeMode.Perform)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithUpgradeMode4", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.UpgradeMode, DomainUpgradeMode.PerformSafely)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithUpgradeMode5", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.UpgradeMode, DomainUpgradeMode.Validate)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithUpgradeMode6", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.UpgradeMode, DomainUpgradeMode.LegacyValidate)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithUpgradeMode7", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.UpgradeMode, DomainUpgradeMode.Skip)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithUpgradeMode8", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.UpgradeMode, DomainUpgradeMode.LegacySkip)); + domainConfig.Lock(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void WrongUpgradeModeTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithWrongUpgradeMode", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ForeighKeyModesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithForeignKeyMode1", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.ForeignKeyMode, ForeignKeyMode.None)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithForeignKeyMode2", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.ForeignKeyMode, ForeignKeyMode.Hierarchy)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithForeignKeyMode3", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.ForeignKeyMode, ForeignKeyMode.Reference)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithForeignKeyMode4", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.ForeignKeyMode, ForeignKeyMode.All)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithForeignKeyMode5", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.ForeignKeyMode, ForeignKeyMode.Default)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithForeignKeyMode6", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.ForeignKeyMode, ForeignKeyMode.Hierarchy | ForeignKeyMode.Reference)); + domainConfig.Lock(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidForeighKeyModeTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidForeignKeyMode", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ChangeTrackingModesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithChangeTrackingMode1", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.FullTextChangeTrackingMode, FullTextChangeTrackingMode.Off)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithChangeTrackingMode2", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.FullTextChangeTrackingMode, FullTextChangeTrackingMode.Auto)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithChangeTrackingMode3", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.FullTextChangeTrackingMode, FullTextChangeTrackingMode.Manual)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithChangeTrackingMode4", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.FullTextChangeTrackingMode, FullTextChangeTrackingMode.OffWithNoPopulation)); + domainConfig.Lock(); + + domainConfig = LoadDomainConfiguration("DomainWithChangeTrackingMode5", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.FullTextChangeTrackingMode, FullTextChangeTrackingMode.Default)); + domainConfig.Lock(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidChangeTrackingModeTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidChangeTrackingMode", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DomainOptionsTest1(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithDomainOptionsValid1", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.Options, DomainOptions.Default)); + domainConfig.Lock(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DomainOptionsTest2(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithDomainOptionsValid2", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.Options, DomainOptions.None)); + domainConfig.Lock(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidDomainOptionsTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithDomainOptionsInvalid", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CollationTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithColation", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.Collation, "generalci")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void BriefSchemaSyncExceptionsTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithBriefSchemaSyncExceptions", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.SchemaSyncExceptionFormat, SchemaSyncExceptionFormat.Brief)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DetailedSchemaSyncExceptionsTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithDetailedSchemaSyncExceptions", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.SchemaSyncExceptionFormat, SchemaSyncExceptionFormat.Detailed)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DefaultSchemaSyncExceptionsTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithDefaultSchemaSyncExceptions", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.SchemaSyncExceptionFormat, SchemaSyncExceptionFormat.Default)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidSchemaSyncExceptionsTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidSchemaSyncExceptions", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TagsLocationNowhereTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithTagsLocationNowhere", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.TagsLocation, TagsLocation.Nowhere)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TagsLocationBeforeTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithTagsLocationBefore", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.TagsLocation, TagsLocation.BeforeStatement)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TagsLocationWithinTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithTagsLocationWithin", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.TagsLocation, TagsLocation.WithinStatement)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TagsLocationAfterTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithTagsLocationAfter", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.TagsLocation, TagsLocation.AfterStatement)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TagsLocationDefaultTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithTagsLocationDefault", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.TagsLocation, TagsLocation.Default)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidTagsLocationTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithTagsLocationInvalid", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ForcedServerVersionTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithForcedServerVersion", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.ForcedServerVersion, "10.0.0.0")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InitializationSqlTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithInitSql", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.ConnectionInitializationSql, "use [OtherDb]")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void IncludeSqlInExceptionsTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("IncludeSqlInExceptionsTrue", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.IncludeSqlInExceptions, true)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DontIncludeSqlInExceptionsTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("IncludeSqlInExceptionsFalse", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.IncludeSqlInExceptions, false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void AllowCyclicDatabaseDependanciesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("AllowCyclicDatabaseDependenciesTrue", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.AllowCyclicDatabaseDependencies, true)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DisallowCyclicDatabaseDependanciesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("AllowCyclicDatabaseDependenciesFalse", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.AllowCyclicDatabaseDependencies, false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void BuildInParallelTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("BuildInParallelTrue", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.BuildInParallel, true)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DontBuildInParallelTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("BuildInParallelFalse", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.BuildInParallel, false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void AllowMultidatabaseKeysTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("MultidatabaseKeysTrue", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.MultidatabaseKeys, true)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DisallowMultidatabaseKeysTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("MultidatabaseKeysFalse", useRoot); + ValidateAllDefaultExcept(domainConfig, ((d) => d.MultidatabaseKeys, false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ShareStorageSchemaOverNodesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("SharedStorageSchemaOn", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.ShareStorageSchemaOverNodes, true)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DontShareStorageSchemaOverNodesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("SharedStorageSchemaOff", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.ShareStorageSchemaOverNodes, false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void EnsureConnectionIsAliveTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("EnableConnectionIsAliveTrue", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.EnsureConnectionIsAlive, true)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DontCheckConnectionIsAliveTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("EnableConnectionIsAliveFalse", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.EnsureConnectionIsAlive, false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void PreferTypeIdAsQueryParameterTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("PreferTypeIdsAsQueryParametersTrue", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.PreferTypeIdsAsQueryParameters, true)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DontPreferTypeIdAsQueryParameterTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("PreferTypeIdsAsQueryParametersFalse", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.PreferTypeIdsAsQueryParameters, false)); + } + #endregion + + #region NamingConvention + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest01(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention1", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Uppercase)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Synonymize)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.UnderscoreHyphens | NamingRules.RemoveDots)); + + var synonyms = namingConvention.NamespaceSynonyms; + Assert.That(synonyms.Count, Is.EqualTo(2)); + Assert.That(synonyms["Xtensive.Orm"], Is.EqualTo("system")); + Assert.That(synonyms["Xtensive.Orm.Tests"], Is.EqualTo("theRest")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest02(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention2", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Lowercase)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Synonymize)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.UnderscoreHyphens | NamingRules.RemoveDots)); + + var synonyms = namingConvention.NamespaceSynonyms; + Assert.That(synonyms.Count, Is.EqualTo(2)); + Assert.That(synonyms["Xtensive.Orm"], Is.EqualTo("system")); + Assert.That(synonyms["Xtensive.Orm.Tests"], Is.EqualTo("theRest")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest03(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention3", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.AsIs)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Synonymize)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.UnderscoreHyphens | NamingRules.RemoveDots)); + + var synonyms = namingConvention.NamespaceSynonyms; + Assert.That(synonyms.Count, Is.EqualTo(2)); + Assert.That(synonyms["Xtensive.Orm"], Is.EqualTo("system")); + Assert.That(synonyms["Xtensive.Orm.Tests"], Is.EqualTo("theRest")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest04(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention4", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Default)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Synonymize)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.UnderscoreHyphens | NamingRules.RemoveDots)); + + var synonyms = namingConvention.NamespaceSynonyms; + Assert.That(synonyms.Count, Is.EqualTo(2)); + Assert.That(synonyms["Xtensive.Orm"], Is.EqualTo("system")); + Assert.That(synonyms["Xtensive.Orm.Tests"], Is.EqualTo("theRest")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest05(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention5", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Uppercase)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.AsIs)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.UnderscoreHyphens | NamingRules.RemoveDots)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest06(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention6", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Uppercase)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Hash)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.UnderscoreHyphens | NamingRules.RemoveDots)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest07(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention7", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Uppercase)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Omit)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.UnderscoreHyphens | NamingRules.RemoveDots)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest08(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention8", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Uppercase)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Default)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.UnderscoreHyphens | NamingRules.RemoveDots)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest09(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention9", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Uppercase)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Hash)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.UnderscoreDots | NamingRules.RemoveHyphens)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest10(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention10", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Uppercase)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Hash)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.None)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionSettingsTest11(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithNamingConvention11", useRoot); + ValidateAllDefault(domainConfig); + var namingConvention = domainConfig.NamingConvention; + Assert.That(namingConvention.LetterCasePolicy, Is.EqualTo(LetterCasePolicy.Uppercase)); + Assert.That(namingConvention.NamespacePolicy, Is.EqualTo(NamespacePolicy.Hash)); + Assert.That(namingConvention.NamingRules, Is.EqualTo(NamingRules.Default)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionInvalidSettingsTest1(bool useRoot) + { + _ = Assert.Throws (() => LoadDomainConfiguration("DomainWithInvalidNamingConvention1", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionInvalidSettingsTest2(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidNamingConvention2", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NamingConventionInvalidSettingsTest3(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidNamingConvention3", useRoot)); + } + + #endregion + + #region VersioningConvention + + [Test] + [TestCase(true)] + [TestCase(false)] + public void VersioningConventionPessimisticTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithVersioningConvention1", useRoot); + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.VersioningConvention.EntityVersioningPolicy, Is.EqualTo(EntityVersioningPolicy.Pessimistic)); + Assert.That(domainConfig.VersioningConvention.DenyEntitySetOwnerVersionChange, Is.EqualTo(false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void VersioningConventionOptimisticTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithVersioningConvention2", useRoot); + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.VersioningConvention.EntityVersioningPolicy, Is.EqualTo(EntityVersioningPolicy.Optimistic)); + Assert.That(domainConfig.VersioningConvention.DenyEntitySetOwnerVersionChange, Is.EqualTo(false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void VersioningConventionDefaultTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithVersioningConvention3", useRoot); + ValidateAllDefault(domainConfig); + + Assert.That(domainConfig.VersioningConvention.EntityVersioningPolicy, Is.EqualTo(EntityVersioningPolicy.Default)); + Assert.That(domainConfig.VersioningConvention.DenyEntitySetOwnerVersionChange, Is.EqualTo(false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void VersioningConventionDenyEntitySetChangeVersionTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithVersioningConvention4", useRoot); + ValidateAllDefault(domainConfig); + + Assert.That(domainConfig.VersioningConvention.EntityVersioningPolicy, Is.EqualTo(EntityVersioningPolicy.Optimistic)); + Assert.That(domainConfig.VersioningConvention.DenyEntitySetOwnerVersionChange, Is.EqualTo(true)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void VersioningConventionAllowEntitySetChangeVersionTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithVersioningConvention5", useRoot); + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.VersioningConvention.EntityVersioningPolicy, Is.EqualTo(EntityVersioningPolicy.Optimistic)); + Assert.That(domainConfig.VersioningConvention.DenyEntitySetOwnerVersionChange, Is.EqualTo(false)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void VersioningConventionInvalidTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidVersioningConvention1", useRoot)); + } + + #endregion + + #region Types registration + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TypesRegistrationAsTypesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithTypes", useRoot); + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.DummyEntity1)), Is.True); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.DummyEntity2)), Is.True); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.DummyEntity3)), Is.False); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TypesRegistrationAsAssembliesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithAssemblies", useRoot); + ValidateAllDefault(domainConfig); + var ormAssembly = typeof(DomainConfiguration).Assembly; + Assert.That(domainConfig.Types.Count, Is.GreaterThan(0)); + Assert.That(domainConfig.Types.All((t) => t.Assembly == ormAssembly), Is.True); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TypesRegistrationAsAssembliesWithNamespace(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithAssembliesAndNamespaces", useRoot); + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.NestedNamespace.DummyNestedEntity1)), Is.True); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.NestedNamespace.DummyNestedEntity2)), Is.True); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MixedTypeRegistration(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithMixedRegistrations", useRoot); + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.DummyEntity1)), Is.True); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.DummyEntity2)), Is.True); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.DummyEntity3)), Is.False); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.NestedNamespace.DummyNestedEntity1)), Is.True); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.NestedNamespace.DummyNestedEntity2)), Is.True); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidTypeRegistration1(bool useRoot) + { + // same type twice + + var domainConfig = LoadDomainConfiguration("DomainWithInvalidRegistrations1", useRoot); + + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.DummyEntity1)), Is.True); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.DummyEntity2)), Is.False); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.DummyEntity3)), Is.False); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidTypeRegistration2(bool useRoot) + { + // same Assembly + var domainConfig = LoadDomainConfiguration("DomainWithInvalidRegistrations2", useRoot); + + ValidateAllDefault(domainConfig); + var ormAssembly = typeof(DomainConfiguration).Assembly; + Assert.That(domainConfig.Types.Count, Is.GreaterThan(0)); + Assert.That(domainConfig.Types.All((t) => t.Assembly == ormAssembly), Is.True); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidTypeRegistration3(bool useRoot) + { + // same assembly and namespace + var domainConfig = LoadDomainConfiguration("DomainWithInvalidRegistrations3", useRoot); + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.NestedNamespace.DummyNestedEntity1)), Is.True); + Assert.That(domainConfig.Types.Contains(typeof(TypesToUseInTests.NestedNamespace.DummyNestedEntity2)), Is.True); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidTypeRegistration4(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidRegistrations4", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidTypeRegistration5(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidRegistrations5", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidTypeRegistration6(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidRegistrations6", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void InvalidTypeRegistration7(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidRegistrations7", useRoot)); + } + + #endregion + + #region Sessions + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MultipleSessionsTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithMultipleSessionConfigurations", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(2)); + var session = sessions[0]; + Assert.That(session.Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(session, + ((s) => s.CacheType, SessionCacheType.Infinite), + ((s) => s.BatchSize, 20), + ((s) => s.EntityChangeRegistrySize, 255), + ((s) => s.Options, SessionOptions.AllowSwitching | SessionOptions.AutoActivation | SessionOptions.ReadRemovedObjects | SessionOptions.ValidateEntityVersions)); + + session = sessions[1]; + Assert.That(session.Name, Is.EqualTo(WellKnown.Sessions.System)); + ValidateAllDefaultExcept(session, + ((s) => s.CacheType, SessionCacheType.Infinite), + ((s) => s.BatchSize, 30), + ((s) => s.Options, SessionOptions.ServerProfile)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithEmptyNameTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionEmptyName", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.Options, SessionOptions.ClientProfile)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithCustomNameTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionCustomName", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.Not.EqualTo(WellKnown.Sessions.Default)); + Assert.That(sessions[0].Name, Is.EqualTo("UserCreated")); + + ValidateAllDefaultExcept(sessions[0], + ((s) => s.Options, SessionOptions.ClientProfile)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithCustomUserNameTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionCustomUser", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.UserName, "User"), + ((s) => s.Password, "126654")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithOptionsTest1(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionDefaultOptions", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.Options, SessionOptions.Default)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithOptionsTest2(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionServerProfile", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.Options, SessionOptions.ServerProfile)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithOptionsTest3(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionClientProfile", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.Options, SessionOptions.ClientProfile)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithOptionsTest4(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionCustomOptions", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.Options, SessionOptions.AllowSwitching | SessionOptions.AutoActivation | SessionOptions.ReadRemovedObjects | SessionOptions.ValidateEntityVersions)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithCollectionSizesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionWithCollectionSizes", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.CacheSize, 399), + ((s) => s.BatchSize, 20), + ((s) => s.EntityChangeRegistrySize, 255)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionCustomCacheTypeTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionCustomCacheType", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.CacheType, SessionCacheType.Infinite)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionCustomIsolationLevelTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionCustomIsolationLevel", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.DefaultIsolationLevel, IsolationLevel.ReadCommitted)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionCustomCommandTimeoutTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionCustomCommandTimeout", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.DefaultCommandTimeout, (int?)300)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionCustomPreloadingPolicyTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionCustomPreloadingPolicy", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.ReaderPreloading, ReaderPreloadingPolicy.Always)); + + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionCustomConnectionStringTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionCustomConnectionString", useRoot); + ValidateAllDefault(domainConfig); + + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.ConnectionInfo, new ConnectionInfo("_dummy_", "Data Source=localhost;Initial Catalog=DO-Tests;Integrated Security=True;MultipleActiveResultSets=True"))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionCustomConnectionUrlTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithSessionCustomConnectionUrl", useRoot); + ValidateAllDefault(domainConfig); + var sessions = domainConfig.Sessions; + Assert.That(sessions.Count, Is.EqualTo(1)); + Assert.That(sessions[0].Name, Is.EqualTo(WellKnown.Sessions.Default)); + ValidateAllDefaultExcept(sessions[0], + ((s) => s.ConnectionInfo, new ConnectionInfo("sqlserver://localhost/DO-Tests"))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithInvalidOptions(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithSessionInvalidOptions", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithInvalidCacheSizeTest1(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithSessionInvalidCacheSize1", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithInvalidCacheSizeTest2(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithSessionInvalidCacheSize2", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithInvalidCacheSizeTest3(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithSessionInvalidCacheSize3", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithInvalidBatchSizeTest1(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithSessionInvalidBatchSize1", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithInvalidBatchSizeTest2(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithSessionInvalidBatchSize2", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithInvalidEntityChangeRegistryTest1(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithSessionInvalidEntityChangeRegistry1", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithInvalidEntityChangeRegistryTest2(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithSessionInvalidEntityChangeRegistry2", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SessionWithInvalidCacheType1(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithSessionInvalidCacheType", useRoot)); + } + + #endregion + + #region Databases configuration + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DatabaseConfigurationOnlyAliasTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithDatabases1", useRoot); + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.Databases.Count, Is.EqualTo(2)); + var db1 = domainConfig.Databases[0]; + Assert.That(db1.Name, Is.EqualTo("main")); + Assert.That(db1.RealName, Is.EqualTo("DO-Tests-1")); + Assert.That(db1.MinTypeId, Is.EqualTo(Orm.Model.TypeInfo.MinTypeId)); + Assert.That(db1.MaxTypeId, Is.EqualTo(int.MaxValue)); + var db2 = domainConfig.Databases[1]; + Assert.That(db2.Name, Is.EqualTo("other")); + Assert.That(db2.RealName, Is.EqualTo("DO-Tests-2")); + Assert.That(db2.MinTypeId, Is.EqualTo(Orm.Model.TypeInfo.MinTypeId)); + Assert.That(db2.MaxTypeId, Is.EqualTo(int.MaxValue)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DatabaseConfigurationWithTypeIdsTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithDatabases2", useRoot); + ValidateAllDefault(domainConfig); + Assert.That(domainConfig.Databases.Count, Is.EqualTo(2)); + var db1 = domainConfig.Databases[0]; + Assert.That(db1.Name, Is.EqualTo("main")); + Assert.That(db1.RealName, Is.EqualTo("DO-Tests-1")); + Assert.That(db1.MinTypeId, Is.EqualTo(100)); + Assert.That(db1.MaxTypeId, Is.EqualTo(1000)); + var db2 = domainConfig.Databases[1]; + Assert.That(db2.Name, Is.EqualTo("other")); + Assert.That(db2.RealName, Is.EqualTo("DO-Tests-2")); + Assert.That(db2.MinTypeId, Is.EqualTo(2000)); + Assert.That(db2.MaxTypeId, Is.EqualTo(3000)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DatabaseConfigurationNegativeMinTypeIdTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidDatabases1", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DatabaseConfigurationInvalidMinTypeIdTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidDatabases2", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DatabaseConfigurationNegativeMaxTypeIdTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidDatabases3", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void DatabaseConfigurationInvalidMaxTypeIdTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidDatabases4", useRoot)); + } + + #endregion + + #region KeyGenerators + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SimpleKeyGeneratorTest(bool useRoot) + { + if (!NameAttributeUnique) + throw new IgnoreException(""); + var domainConfig = LoadDomainConfiguration("DomainWithCustomGenerator", useRoot); + ValidateAllDefault(domainConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void KeyGeneratorWithDatabaseNamesTest(bool useRoot) + { + if (!NameAttributeUnique) + throw new IgnoreException(""); + var domainConfig = LoadDomainConfiguration("DomainWithCustomGeneratorsWithDatabaseNames", useRoot); + ValidateAllDefault(domainConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void KeyGeneratorWithDatabaseNamesAllParamsTest(bool useRoot) + { + if (!NameAttributeUnique) + throw new IgnoreException(""); + var domainConfig = LoadDomainConfiguration("DomainWithCustomGeneratorsWithDatabaseNamesAndKeyParams", useRoot); + ValidateAllDefault(domainConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void KeyGeneratorsWithDatabaseAliasesTest(bool useRoot) + { + if (!NameAttributeUnique) + throw new IgnoreException(""); + var domainConfig = LoadDomainConfiguration("DomainWithCustomGeneratorsWithDatabasesAliases", useRoot); + ValidateAllDefault(domainConfig); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void KeyGeneratorsWithConflictByDatabaseTest1(bool useRoot) + { + if (!NameAttributeUnique) + throw new IgnoreException(""); + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithCustomGeneratorsConflict1", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void KeyGeneratorsWithConflictByDatabaseTest2(bool useRoot) + { + if (!NameAttributeUnique) + throw new IgnoreException(""); + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithCustomGeneratorsConflict2", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void KeyGeneratorWithNegativeSeedTest(bool useRoot) + { + if (!NameAttributeUnique) + throw new IgnoreException(""); + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithCustomGeneratorNegativeSeed", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void KeyGeneratorWithNegativeCacheSizeTest(bool useRoot) + { + if (!NameAttributeUnique) + throw new IgnoreException(""); + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithCustomGeneratorNegativeCache", useRoot)); + } + + #endregion + + #region IgnoreRules + + [Test] + [TestCase(true)] + [TestCase(false)] + public void IgnoreRulesTest(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithIgnoreRules", useRoot); + ValidateAllDefault(domainConfig); + ValidateIgnoreRules(domainConfig.IgnoreRules); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void IgnoreColumnAndIndexAtTheSameTimeTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidIgnoreRules1", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void IgnoreTableAndColumnAndIndexAtTheSameTimeTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidIgnoreRules2", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void IgnoreDatabaseOnlyTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidIgnoreRules3", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void IgnoreDatabaseAndSchemaOnlyTest(bool useRoot) + { + _ = Assert.Throws(() => LoadDomainConfiguration("DomainWithInvalidIgnoreRules4", useRoot)); + } + + private void ValidateIgnoreRules(IgnoreRuleCollection rules) + { + Assert.That(rules.Count, Is.EqualTo(18)); + + var rule = rules[0]; + Assert.That(rule.Database, Is.EqualTo("Other-DO40-Tests")); + Assert.That(rule.Schema, Is.EqualTo("some-schema1")); + Assert.That(rule.Table, Is.EqualTo("table1")); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[1]; + Assert.That(rule.Database, Is.EqualTo("some-database")); + Assert.That(rule.Schema, Is.EqualTo("some-schema2")); + Assert.That(rule.Table, Is.Null.Or.Empty); + Assert.That(rule.Column, Is.EqualTo("column2")); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[2]; + Assert.That(rule.Database, Is.EqualTo("some-database")); + Assert.That(rule.Schema, Is.EqualTo("some-schema2")); + Assert.That(rule.Table, Is.Null.Or.Empty); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.EqualTo("index2")); + + rule = rules[3]; + Assert.That(rule.Database, Is.EqualTo("some-database")); + Assert.That(rule.Schema, Is.EqualTo("some-schema3")); + Assert.That(rule.Table, Is.EqualTo("table2")); + Assert.That(rule.Column, Is.EqualTo("col3")); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[4]; + Assert.That(rule.Database, Is.EqualTo("some-database")); + Assert.That(rule.Schema, Is.EqualTo("some-schema3")); + Assert.That(rule.Table, Is.EqualTo("table2")); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.EqualTo("index3")); + + rule = rules[5]; + Assert.That(rule.Database, Is.EqualTo("another-some-database")); + Assert.That(rule.Schema, Is.Null.Or.Empty); + Assert.That(rule.Table, Is.EqualTo("some-table")); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[6]; + Assert.That(rule.Database, Is.EqualTo("database1")); + Assert.That(rule.Schema, Is.Null.Or.Empty); + Assert.That(rule.Table, Is.Null.Or.Empty); + Assert.That(rule.Column, Is.EqualTo("some-column")); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[7]; + Assert.That(rule.Database, Is.EqualTo("database1")); + Assert.That(rule.Schema, Is.Null.Or.Empty); + Assert.That(rule.Table, Is.Null.Or.Empty); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.EqualTo("some-index")); + + rule = rules[8]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.EqualTo("schema1")); + Assert.That(rule.Table, Is.EqualTo("table1")); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[9]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.EqualTo("schema1")); + Assert.That(rule.Table, Is.Null.Or.Empty); + Assert.That(rule.Column, Is.EqualTo("column2")); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[10]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.EqualTo("schema1")); + Assert.That(rule.Table, Is.Null.Or.Empty); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.EqualTo("index2")); + + rule = rules[11]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.EqualTo("schema1")); + Assert.That(rule.Table, Is.EqualTo("table2")); + Assert.That(rule.Column, Is.EqualTo("column3")); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[12]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.EqualTo("schema1")); + Assert.That(rule.Table, Is.EqualTo("table2")); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.EqualTo("index3")); + + rule = rules[13]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.Null.Or.Empty); + Assert.That(rule.Table, Is.EqualTo("table4")); + Assert.That(rule.Column, Is.EqualTo("column3")); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[14]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.Null.Or.Empty); + Assert.That(rule.Table, Is.EqualTo("table4")); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.EqualTo("index2")); + + rule = rules[15]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.Null.Or.Empty); + Assert.That(rule.Table, Is.EqualTo("single-table")); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[16]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.Null.Or.Empty); + Assert.That(rule.Table, Is.Null.Or.Empty); + Assert.That(rule.Column, Is.EqualTo("single-column")); + Assert.That(rule.Index, Is.Null.Or.Empty); + + rule = rules[17]; + Assert.That(rule.Database, Is.Null.Or.Empty); + Assert.That(rule.Schema, Is.Null.Or.Empty); + Assert.That(rule.Table, Is.Null.Or.Empty); + Assert.That(rule.Column, Is.Null.Or.Empty); + Assert.That(rule.Index, Is.EqualTo("single-index")); + } + + #endregion + + #region MappingRules + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesTest1(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithMappingRules1", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.DefaultDatabase, "main"), + ((d) => d.DefaultSchema, "dbo"), + ((d) => d.IsMultidatabase, true), + ((d) => d.IsMultischema, true)); + var rules = domainConfig.MappingRules; + + Assert.That(rules.Count, Is.EqualTo(1)); + var rule = rules[0]; + Assert.That(rule.Assembly, Is.EqualTo(typeof(JsonConfigurationTest).Assembly)); + Assert.That(rule.Namespace, Is.Null); + Assert.That(rule.Database, Is.EqualTo("DO-Tests-1")); + Assert.That(rule.Schema, Is.Null); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesTest2(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithMappingRules2", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.DefaultDatabase, "main"), + ((d) => d.DefaultSchema, "dbo"), + ((d) => d.IsMultidatabase, true), + ((d) => d.IsMultischema, true)); + var rules = domainConfig.MappingRules; + + Assert.That(rules.Count, Is.EqualTo(1)); + var rule = rules[0]; + Assert.That(rule.Assembly, Is.EqualTo(typeof(JsonConfigurationTest).Assembly)); + Assert.That(rule.Namespace, Is.Null); + Assert.That(rule.Database, Is.Null); + Assert.That(rule.Schema, Is.EqualTo("Model1")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesTest3(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithMappingRules3", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.DefaultDatabase, "main"), + ((d) => d.DefaultSchema, "dbo"), + ((d) => d.IsMultidatabase, true), + ((d) => d.IsMultischema, true)); + var rules = domainConfig.MappingRules; + + Assert.That(rules.Count, Is.EqualTo(1)); + var rule = rules[0]; + + Assert.That(rule.Assembly, Is.Null); + Assert.That(rule.Namespace, Is.EqualTo("Xtensive.Orm.Configuration.Options")); + Assert.That(rule.Database, Is.EqualTo("DO-Tests-2")); + Assert.That(rule.Schema, Is.Null); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesTest4(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithMappingRules4", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.DefaultDatabase, "main"), + ((d) => d.DefaultSchema, "dbo"), + ((d) => d.IsMultidatabase, true), + ((d) => d.IsMultischema, true)); + var rules = domainConfig.MappingRules; + + Assert.That(rules.Count, Is.EqualTo(1)); + var rule = rules[0]; + + Assert.That(rule.Assembly, Is.Null); + Assert.That(rule.Namespace, Is.EqualTo("Xtensive.Orm.Tests.Configuration")); + Assert.That(rule.Database, Is.Null); + Assert.That(rule.Schema, Is.EqualTo("Model2")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesTest5(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithMappingRules5", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.DefaultDatabase, "main"), + ((d) => d.DefaultSchema, "dbo"), + ((d) => d.IsMultidatabase, true), + ((d) => d.IsMultischema, true)); + var rules = domainConfig.MappingRules; + + Assert.That(rules.Count, Is.EqualTo(1)); + var rule = rules[0]; + + Assert.That(rule.Assembly, Is.EqualTo(typeof (JsonConfigurationTest).Assembly)); + Assert.That(rule.Namespace, Is.EqualTo("Xtensive.Orm.Tests.Configuration.TypesToUseInTests")); + Assert.That(rule.Database, Is.EqualTo("DO-Tests-3")); + Assert.That(rule.Schema, Is.Null); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesTest6(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithMappingRules6", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.DefaultDatabase, "main"), + ((d) => d.DefaultSchema, "dbo"), + ((d) => d.IsMultidatabase, true), + ((d) => d.IsMultischema, true)); + var rules = domainConfig.MappingRules; + + Assert.That(rules.Count, Is.EqualTo(1)); + var rule = rules[0]; + Assert.That(rule.Assembly, Is.EqualTo(typeof(JsonConfigurationTest).Assembly)); + Assert.That(rule.Namespace, Is.EqualTo("Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace")); + Assert.That(rule.Database, Is.Null); + Assert.That(rule.Schema, Is.EqualTo("Model3")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesTest7(bool useRoot) + { + var domainConfig = LoadDomainConfiguration("DomainWithMappingRules7", useRoot); + ValidateAllDefaultExcept(domainConfig, + ((d) => d.DefaultDatabase, "main"), + ((d) => d.DefaultSchema, "dbo"), + ((d) => d.IsMultidatabase, true), + ((d) => d.IsMultischema, true)); + var rules = domainConfig.MappingRules; + + Assert.That(rules.Count, Is.EqualTo(1)); + var rule = rules[0]; + Assert.That(rule.Assembly, Is.EqualTo(typeof(JsonConfigurationTest).Assembly)); + Assert.That(rule.Namespace, Is.EqualTo("Xtensive.Orm.Tests.Indexing")); + Assert.That(rule.Database, Is.EqualTo("main")); + Assert.That(rule.Schema, Is.EqualTo("Model4")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRuleWithConflictByAssemblyTest(bool useRoot) + { + var exception = Assert.Throws( + () => LoadDomainConfiguration("DomainWithConflictMappingRules1", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRuleWithConflictByNamespaceTest(bool useRoot) + { + var exception = Assert.Throws( + () => LoadDomainConfiguration("DomainWithConflictMappingRules2", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesInvalidTest1(bool useRoot) + { + var exception = Assert.Throws( + () => LoadDomainConfiguration("DomainWithInvalidMappingRules1", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesInvalidTest2(bool useRoot) + { + var exception = Assert.Throws( + () => LoadDomainConfiguration("DomainWithInvalidMappingRules2", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesInvalidTest3(bool useRoot) + { + var exception = Assert.Throws( + () => LoadDomainConfiguration("DomainWithInvalidMappingRules3", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesInvalidTest4(bool useRoot) + { + var exception = Assert.Throws( + () => LoadDomainConfiguration("DomainWithInvalidMappingRules4", useRoot)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void MappingRulesInvalidTest5(bool useRoot) + { + var exception = Assert.Throws( + () => LoadDomainConfiguration("DomainWithInvalidMappingRules5", useRoot)); + } + + #endregion + + #region Logging + + [Test] + public void LoggingConfigurationTest() + { + var configuration = LoadLoggingConfiguration(); + ValidateLoggingConfiguration(configuration); + } + + [Test] + public void LoggingEmptyLoggingSectionTest() + { + var section = configuration.GetSection($"Xtensive.Orm.EmptyLogging.{Postfix}"); + _ = Assert.Throws(() => LoadLoggingConfiguration(section)); + } + + [Test] + public void LoggingEmptyLogsTest() + { + if (Postfix == "AppConfig") + throw new IgnoreException(""); + + var section = configuration.GetSection($"Xtensive.Orm.EmptyLogs.{Postfix}"); + _ = Assert.Throws(() => LoadLoggingConfiguration(section)); + } + + [Test] + public void LoggingOnlyProviderDeclaredTest() + { + var section = configuration.GetSection($"Xtensive.Orm.OnlyLogProvider.{Postfix}"); + var loggingConfig = LoadLoggingConfiguration(section); + Assert.That(loggingConfig.Logs.Count, Is.EqualTo(0)); + Assert.That(loggingConfig.Provider, Is.EqualTo("Xtensive.Orm.Logging.log4net.LogProvider")); + } + + [Test] + public void LoggingProviderAndEmptyLogsTest() + { + if (Postfix == "AppConfig") + throw new IgnoreException(""); + + var section = configuration.GetSection($"Xtensive.Orm.LogProviderAndEmptyLogs.{Postfix}"); + var loggingConfig = LoadLoggingConfiguration(section); + Assert.That(loggingConfig.Logs.Count, Is.EqualTo(0)); + Assert.That(loggingConfig.Provider, Is.EqualTo("Xtensive.Orm.Logging.log4net.LogProvider")); + } + + #endregion + + private void ValidateAllDefault(DomainConfiguration domainConfiguration) + { + Assert.That(domainConfiguration.AllowCyclicDatabaseDependencies, Is.EqualTo(DomainConfiguration.DefaultAllowCyclicDatabaseDependencies)); + Assert.That(domainConfiguration.BuildInParallel, Is.EqualTo(DomainConfiguration.DefaultBuildInParallel)); + Assert.That(domainConfiguration.Collation, Is.EqualTo(string.Empty)); + Assert.That(domainConfiguration.ConnectionInitializationSql, Is.EqualTo(string.Empty)); + Assert.That(domainConfiguration.DefaultDatabase, Is.EqualTo(string.Empty)); + Assert.That(domainConfiguration.DefaultSchema, Is.EqualTo(string.Empty)); + Assert.That(domainConfiguration.EnsureConnectionIsAlive, Is.EqualTo(DomainConfiguration.DefaultEnsureConnectionIsAlive)); + Assert.That(domainConfiguration.ForcedServerVersion, Is.EqualTo(string.Empty)); + Assert.That(domainConfiguration.ForeignKeyMode, Is.EqualTo(DomainConfiguration.DefaultForeignKeyMode)); + Assert.That(domainConfiguration.FullTextChangeTrackingMode, Is.EqualTo(DomainConfiguration.DefaultFullTextChangeTrackingMode)); + Assert.That(domainConfiguration.IncludeSqlInExceptions, Is.EqualTo(DomainConfiguration.DefaultIncludeSqlInExceptions)); + Assert.That(domainConfiguration.IsMultidatabase, Is.False); + Assert.That(domainConfiguration.IsMultischema, Is.False); + Assert.That(domainConfiguration.KeyCacheSize, Is.EqualTo(DomainConfiguration.DefaultKeyCacheSize)); + Assert.That(domainConfiguration.KeyGeneratorCacheSize, Is.EqualTo(DomainConfiguration.DefaultKeyGeneratorCacheSize)); + Assert.That(domainConfiguration.MultidatabaseKeys, Is.EqualTo(DomainConfiguration.DefaultMultidatabaseKeys)); + Assert.That(domainConfiguration.Options, Is.EqualTo(DomainConfiguration.DefaultDomainOptions)); + Assert.That(domainConfiguration.PreferTypeIdsAsQueryParameters, Is.EqualTo(DomainConfiguration.DefaultPreferTypeIdsAsQueryParameters)); + Assert.That(domainConfiguration.QueryCacheSize, Is.EqualTo(DomainConfiguration.DefaultQueryCacheSize)); + Assert.That(domainConfiguration.RecordSetMappingCacheSize, Is.EqualTo(DomainConfiguration.DefaultRecordSetMappingCacheSize)); + Assert.That(domainConfiguration.SchemaSyncExceptionFormat, Is.EqualTo(DomainConfiguration.DefaultSchemaSyncExceptionFormat)); + Assert.That(domainConfiguration.ShareStorageSchemaOverNodes, Is.EqualTo(DomainConfiguration.DefaultShareStorageSchemaOverNodes)); + Assert.That(domainConfiguration.TagsLocation, Is.EqualTo(DomainConfiguration.DefaultTagLocation)); + Assert.That(domainConfiguration.UpgradeMode, Is.EqualTo(DomainConfiguration.DefaultUpgradeMode)); + } + + private void ValidateAllDefaultExcept(TConfiguration configuration, + (Expression> expression, T1 expectedValue) property) + { + Assert.That(configuration, Is.Not.Null); + var excludedProperties = new List(); + + + if (!TryExtractPropertyFormLambda(property.expression, out var prop)) + throw new InconclusiveException(""); + + Assert.That(property.expression.Compile()(configuration), + Is.EqualTo(property.expectedValue)); + + excludedProperties.Add(prop.Name); + + if (configuration is DomainConfiguration domainConfiguration) + ValidateAllPropertiesExcept(domainConfiguration, excludedProperties); + else if (configuration is SessionConfiguration sessionConfiguration) + ValidateAllPropertiesExcept(sessionConfiguration, excludedProperties); + else + throw new ArgumentOutOfRangeException(nameof(configuration)); + + } + + + private void ValidateAllDefaultExcept(TConfiguration configuration, + (Expression> expression, T1 expectedValue) property1, + (Expression> expression, T2 expectedValue) property2) + { + Assert.That(configuration, Is.Not.Null); + var excludedProperties = new List(); + + if (!TryExtractPropertyFormLambda(property1.expression, out var prop)) + throw new InconclusiveException(""); + + Assert.That(property1.expression.Compile()(configuration), + Is.EqualTo(property1.expectedValue)); + + excludedProperties.Add(prop.Name); + + if (!TryExtractPropertyFormLambda(property2.expression, out prop)) + throw new InconclusiveException(""); + + Assert.That(property2.expression.Compile()(configuration), + Is.EqualTo(property2.expectedValue)); + + excludedProperties.Add(prop.Name); + + if(configuration is DomainConfiguration domainConfiguration) + ValidateAllPropertiesExcept(domainConfiguration, excludedProperties); + else if (configuration is SessionConfiguration sessionConfiguration) + ValidateAllPropertiesExcept(sessionConfiguration, excludedProperties); + else + throw new ArgumentOutOfRangeException(nameof(configuration)); + } + + private void ValidateAllDefaultExcept(TConfiguration configuration, + (Expression> expression, T1 expectedValue) property1, + (Expression> expression, T2 expectedValue) property2, + (Expression> expression, T3 expectedValue) property3) + { + Assert.That(configuration, Is.Not.Null); + var excludedProperties = new List(); + + if (!TryExtractPropertyFormLambda(property1.expression, out var prop)) + throw new InconclusiveException(""); + + Assert.That(property1.expression.Compile()(configuration), + Is.EqualTo(property1.expectedValue)); + + excludedProperties.Add(prop.Name); + + if (!TryExtractPropertyFormLambda(property2.expression, out prop)) + throw new InconclusiveException(""); + + Assert.That(property2.expression.Compile()(configuration), + Is.EqualTo(property2.expectedValue)); + + excludedProperties.Add(prop.Name); + + if (!TryExtractPropertyFormLambda(property3.expression, out prop)) + throw new InconclusiveException(""); + + Assert.That(property3.expression.Compile()(configuration), + Is.EqualTo(property3.expectedValue)); + + excludedProperties.Add(prop.Name); + + if (configuration is DomainConfiguration domainConfiguration) + ValidateAllPropertiesExcept(domainConfiguration, excludedProperties); + else if (configuration is SessionConfiguration sessionConfiguration) + ValidateAllPropertiesExcept(sessionConfiguration, excludedProperties); + else + throw new ArgumentOutOfRangeException(nameof(configuration)); + } + + private void ValidateAllDefaultExcept(TConfiguration configuration, + (Expression> expression, T1 expectedValue) property1, + (Expression> expression, T2 expectedValue) property2, + (Expression> expression, T3 expectedValue) property3, + (Expression> expression, T4 expectedValue) property4) + { + Assert.That(configuration, Is.Not.Null); + var excludedProperties = new List(); + + if (!TryExtractPropertyFormLambda(property1.expression, out var prop)) + throw new InconclusiveException(""); + + Assert.That(property1.expression.Compile()(configuration), + Is.EqualTo(property1.expectedValue)); + + excludedProperties.Add(prop.Name); + + if (!TryExtractPropertyFormLambda(property2.expression, out prop)) + throw new InconclusiveException(""); + + Assert.That(property2.expression.Compile()(configuration), + Is.EqualTo(property2.expectedValue)); + + excludedProperties.Add(prop.Name); + + if (!TryExtractPropertyFormLambda(property3.expression, out prop)) + throw new InconclusiveException(""); + + Assert.That(property3.expression.Compile()(configuration), + Is.EqualTo(property3.expectedValue)); + + excludedProperties.Add(prop.Name); + + if (!TryExtractPropertyFormLambda(property4.expression, out prop)) + throw new InconclusiveException(""); + + Assert.That(property4.expression.Compile()(configuration), + Is.EqualTo(property4.expectedValue)); + + excludedProperties.Add(prop.Name); + + if (configuration is DomainConfiguration domainConfiguration) + ValidateAllPropertiesExcept(domainConfiguration, excludedProperties); + else if (configuration is SessionConfiguration sessionConfiguration) + ValidateAllPropertiesExcept(sessionConfiguration, excludedProperties); + else + throw new ArgumentOutOfRangeException(nameof(configuration)); + } + + private bool TryExtractPropertyFormLambda(Expression> lambda, + out System.Reflection.PropertyInfo property) + { + if (lambda.Body.StripCasts() is MemberExpression mExpression && mExpression.Member is System.Reflection.PropertyInfo prop) { + property = prop; + return true; + } + property = null; + return false; + } + + private void ValidateAllPropertiesExcept(DomainConfiguration domainConfiguration, List excludedProperties) + { + if (!nameof(domainConfiguration.AllowCyclicDatabaseDependencies).In(excludedProperties)) + Assert.That(domainConfiguration.AllowCyclicDatabaseDependencies, Is.EqualTo(DomainConfiguration.DefaultAllowCyclicDatabaseDependencies)); + + if (!nameof(domainConfiguration.BuildInParallel).In(excludedProperties)) + Assert.That(domainConfiguration.BuildInParallel, Is.EqualTo(DomainConfiguration.DefaultBuildInParallel)); + + if (!nameof(domainConfiguration.Collation).In(excludedProperties)) + Assert.That(domainConfiguration.Collation, Is.EqualTo(string.Empty)); + + if (!nameof(domainConfiguration.ConnectionInitializationSql).In(excludedProperties)) + Assert.That(domainConfiguration.ConnectionInitializationSql, Is.EqualTo(string.Empty)); + + if (!nameof(domainConfiguration.DefaultDatabase).In(excludedProperties)) + Assert.That(domainConfiguration.DefaultDatabase, Is.EqualTo(string.Empty)); + + if (!nameof(domainConfiguration.DefaultSchema).In(excludedProperties)) + Assert.That(domainConfiguration.DefaultSchema, Is.EqualTo(string.Empty)); + + if (!nameof(domainConfiguration.EnsureConnectionIsAlive).In(excludedProperties)) + Assert.That(domainConfiguration.EnsureConnectionIsAlive, Is.EqualTo(DomainConfiguration.DefaultEnsureConnectionIsAlive)); + + if (!nameof(domainConfiguration.ForcedServerVersion).In(excludedProperties)) + Assert.That(domainConfiguration.ForcedServerVersion, Is.EqualTo(string.Empty)); + + if (!nameof(domainConfiguration.ForeignKeyMode).In(excludedProperties)) + Assert.That(domainConfiguration.ForeignKeyMode, Is.EqualTo(DomainConfiguration.DefaultForeignKeyMode)); + + if (!nameof(domainConfiguration.FullTextChangeTrackingMode).In(excludedProperties)) + Assert.That(domainConfiguration.FullTextChangeTrackingMode, Is.EqualTo(DomainConfiguration.DefaultFullTextChangeTrackingMode)); + + if (!nameof(domainConfiguration.IncludeSqlInExceptions).In(excludedProperties)) + Assert.That(domainConfiguration.IncludeSqlInExceptions, Is.EqualTo(DomainConfiguration.DefaultIncludeSqlInExceptions)); + + if (!nameof(domainConfiguration.IsMultidatabase).In(excludedProperties)) + Assert.That(domainConfiguration.IsMultidatabase, Is.False); + + if (!nameof(domainConfiguration.IsMultischema).In(excludedProperties)) + Assert.That(domainConfiguration.IsMultischema, Is.False); + + if (!nameof(domainConfiguration.KeyCacheSize).In(excludedProperties)) + Assert.That(domainConfiguration.KeyCacheSize, Is.EqualTo(DomainConfiguration.DefaultKeyCacheSize)); + + if (!nameof(domainConfiguration.KeyGeneratorCacheSize).In(excludedProperties)) + Assert.That(domainConfiguration.KeyGeneratorCacheSize, Is.EqualTo(DomainConfiguration.DefaultKeyGeneratorCacheSize)); + + if (!nameof(domainConfiguration.MultidatabaseKeys).In(excludedProperties)) + Assert.That(domainConfiguration.MultidatabaseKeys, Is.EqualTo(DomainConfiguration.DefaultMultidatabaseKeys)); + + if (!nameof(domainConfiguration.Options).In(excludedProperties)) + Assert.That(domainConfiguration.Options, Is.EqualTo(DomainConfiguration.DefaultDomainOptions)); + + if (!nameof(domainConfiguration.PreferTypeIdsAsQueryParameters).In(excludedProperties)) + Assert.That(domainConfiguration.PreferTypeIdsAsQueryParameters, Is.EqualTo(DomainConfiguration.DefaultPreferTypeIdsAsQueryParameters)); + + if (!nameof(domainConfiguration.QueryCacheSize).In(excludedProperties)) + Assert.That(domainConfiguration.QueryCacheSize, Is.EqualTo(DomainConfiguration.DefaultQueryCacheSize)); + + if (!nameof(domainConfiguration.RecordSetMappingCacheSize).In(excludedProperties)) + Assert.That(domainConfiguration.RecordSetMappingCacheSize, Is.EqualTo(DomainConfiguration.DefaultRecordSetMappingCacheSize)); + + if (!nameof(domainConfiguration.SchemaSyncExceptionFormat).In(excludedProperties)) + Assert.That(domainConfiguration.SchemaSyncExceptionFormat, Is.EqualTo(DomainConfiguration.DefaultSchemaSyncExceptionFormat)); + + if (!nameof(domainConfiguration.ShareStorageSchemaOverNodes).In(excludedProperties)) + Assert.That(domainConfiguration.ShareStorageSchemaOverNodes, Is.EqualTo(DomainConfiguration.DefaultShareStorageSchemaOverNodes)); + + if (!nameof(domainConfiguration.TagsLocation).In(excludedProperties)) + Assert.That(domainConfiguration.TagsLocation, Is.EqualTo(DomainConfiguration.DefaultTagLocation)); + + if (!nameof(domainConfiguration.UpgradeMode).In(excludedProperties)) + Assert.That(domainConfiguration.UpgradeMode, Is.EqualTo(DomainConfiguration.DefaultUpgradeMode)); + } + + private void ValidateAllPropertiesExcept(SessionConfiguration sessionConfiguration, List excludedProperties) + { + if (!nameof(sessionConfiguration.UserName).In(excludedProperties)) + Assert.That(sessionConfiguration.UserName, Is.Empty); + + if (!nameof(sessionConfiguration.Password).In(excludedProperties)) + Assert.That(sessionConfiguration.Password, Is.Empty); + + if (!nameof(sessionConfiguration.Options).In(excludedProperties)) + Assert.That(sessionConfiguration.Options, Is.EqualTo(SessionConfiguration.DefaultSessionOptions)); + + if (!nameof(sessionConfiguration.CacheSize).In(excludedProperties)) + Assert.That(sessionConfiguration.CacheSize, Is.EqualTo(SessionConfiguration.DefaultCacheSize)); + + if (!nameof(sessionConfiguration.CacheType).In(excludedProperties)) + Assert.That(sessionConfiguration.CacheType, Is.EqualTo(SessionConfiguration.DefaultCacheType)); + + if (!nameof(sessionConfiguration.DefaultIsolationLevel).In(excludedProperties)) + Assert.That(sessionConfiguration.DefaultIsolationLevel, Is.EqualTo(SessionConfiguration.DefaultDefaultIsolationLevel)); + + if (!nameof(sessionConfiguration.DefaultCommandTimeout).In(excludedProperties)) + Assert.That(sessionConfiguration.DefaultCommandTimeout, Is.Null); + + if (!nameof(sessionConfiguration.BatchSize).In(excludedProperties)) + Assert.That(sessionConfiguration.BatchSize, Is.EqualTo(SessionConfiguration.DefaultBatchSize)); + + if (!nameof(sessionConfiguration.EntityChangeRegistrySize).In(excludedProperties)) + Assert.That(sessionConfiguration.EntityChangeRegistrySize, Is.EqualTo(SessionConfiguration.DefaultEntityChangeRegistrySize)); + + if (!nameof(sessionConfiguration.ReaderPreloading).In(excludedProperties)) + Assert.That(sessionConfiguration.ReaderPreloading, Is.EqualTo(SessionConfiguration.DefaultReaderPreloadingPolicy)); + + if (!nameof(sessionConfiguration.ServiceContainerType).In(excludedProperties)) + Assert.That(sessionConfiguration.ServiceContainerType, Is.Null); + + if (!nameof(sessionConfiguration.ConnectionInfo).In(excludedProperties)) + Assert.That(sessionConfiguration.ConnectionInfo, Is.Null); + } + + private void ValidateLoggingConfiguration(LoggingConfiguration configuration) + { + Assert.That(configuration.Provider, Is.Not.Null.Or.Empty); + Assert.That(configuration.Provider, Is.EqualTo("Xtensive.Orm.Logging.log4net.LogProvider")); + + Assert.That(configuration.Logs[0].Source, Is.EqualTo("*")); + Assert.That(configuration.Logs[0].Target, Is.EqualTo("Console")); + + Assert.That(configuration.Logs[1].Source, Is.EqualTo("SomeLogName")); + Assert.That(configuration.Logs[1].Target, Is.EqualTo("DebugOnlyConsole")); + + Assert.That(configuration.Logs[2].Source, Is.EqualTo("FirstLogName,SecondLogName")); + Assert.That(configuration.Logs[2].Target, Is.EqualTo(@"d:\log.txt")); + + Assert.That(configuration.Logs[3].Source, Is.EqualTo("LogName, AnotherLogName")); + Assert.That(configuration.Logs[3].Target, Is.EqualTo("Console")); + + Assert.That(configuration.Logs[4].Source, Is.EqualTo("FileLog")); + Assert.That(configuration.Logs[4].Target, Is.EqualTo("log.txt")); + + Assert.That(configuration.Logs[5].Source, Is.EqualTo("NullLog")); + Assert.That(configuration.Logs[5].Target, Is.EqualTo("None")); + + Assert.That(configuration.Logs[6].Source, Is.EqualTo("Trash")); + Assert.That(configuration.Logs[6].Target, Is.EqualTo("skjdhfjsdf sdfsdfksjdghj fgdfg")); + } + } +} diff --git a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj index ed55956c3b..f048eb412e 100644 --- a/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj +++ b/Orm/Xtensive.Orm.Tests/Xtensive.Orm.Tests.csproj @@ -19,7 +19,6 @@ - @@ -28,13 +27,21 @@ - + + + + + PreserveNewest + + + PreserveNewest + Always @@ -96,4 +103,5 @@ TwoPartsModel.tt + \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/domainSettings.config b/Orm/Xtensive.Orm.Tests/domainSettings.config new file mode 100644 index 0000000000..7b6c54deb4 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/domainSettings.config @@ -0,0 +1,2453 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sqlite + Data Source=DO-Testsaaa.db3 + + + sqlite:///DO-Tests.db3 + + + + sqlserver://localhost/DO40-Tests + 192 + + + sqlserver://localhost/DO40-Tests + -256 + + + + sqlserver://localhost/DO40-Tests + 192 + + + sqlserver://localhost/DO40-Tests + -256 + + + + sqlserver://localhost/DO40-Tests + 192 + + + sqlserver://localhost/DO40-Tests + -256 + + + + sqlserver://localhost/DO40-Tests + 192 + + + sqlserver://localhost/DO40-Tests + -256 + + + + sqlserver://localhost/DO40-Tests + MyFancyDatabase + + + + sqlserver://localhost/DO40-Tests + MyFancySchema + + + + + + + + sqlserver://localhost/DO40-Tests + Default + + + sqlserver://localhost/DO40-Tests + Recreate + + + sqlserver://localhost/DO40-Tests + Perform + + + sqlserver://localhost/DO40-Tests + PerformSafely + + + sqlserver://localhost/DO40-Tests + Validate + + + sqlserver://localhost/DO40-Tests + LegacyValidate + + + sqlserver://localhost/DO40-Tests + Skip + + + sqlserver://localhost/DO40-Tests + LegacySkip + + + + sqlserver://localhost/DO40-Tests + Defailt + + + + sqlserver://localhost/DO40-Tests + None + + + sqlserver://localhost/DO40-Tests + Hierarchy + + + sqlserver://localhost/DO40-Tests + Reference + + + sqlserver://localhost/DO40-Tests + All + + + sqlserver://localhost/DO40-Tests + Default + + + sqlserver://localhost/DO40-Tests + Hierarchy,Reference + + + + sqlserver://localhost/DO40-Tests + Defailt + + + + sqlserver://localhost/ + Off + + + sqlserver://localhost/ + Auto + + + sqlserver://localhost/ + Manual + + + sqlserver://localhost/ + OffWithNoPopulation + + + sqlserver://localhost/ + Default + + + + sqlserver://localhost/ + Defailt + + + + sqlserver://localhost/ + Default + + + sqlserver://localhost/ + None + + + + sqlserver://localhost/ + Defailt + + + + sqlserver://localhost/ + generalci + + + + sqlserver://localhost/ + Brief + + + sqlserver://localhost/ + Detailed + + + sqlserver://localhost/ + Default + + + sqlserver://localhost/ + Defailt + + + + sqlserver://localhost/ + Nowhere + + + sqlserver://localhost/ + BeforeStatement + + + sqlserver://localhost/ + WithinStatement + + + sqlserver://localhost/ + AfterStatement + + + sqlserver://localhost/ + Default + + + sqlserver://localhost/ + Defailt + + + + sqlserver://localhost/ + 10.0.0.0 + + + + sqlserver://localhost/ + use [OtherDb] + + + + sqlserver://localhost/ + true + + + sqlserver://localhost/ + false + + + sqlserver://localhost/ + true + + + sqlserver://localhost/ + false + + + + sqlserver://localhost/ + true + + + sqlserver://localhost/ + false + + + + sqlserver://localhost/ + true + + + sqlserver://localhost/ + false + + + + sqlserver://localhost/ + true + + + sqlserver://localhost/ + false + + + + sqlserver://localhost/ + true + + + sqlserver://localhost/ + false + + + + sqlserver://localhost/ + true + + + sqlserver://localhost/ + false + + + + sqlserver://localhost/ + + Uppercase + Synonymize + UnderscoreHyphens,RemoveDots + + + Xtensive.Orm + system + + + Xtensive.Orm.Tests + theRest + + + + + + + sqlserver://localhost/ + + Lowercase + Synonymize + UnderscoreHyphens,RemoveDots + + + Xtensive.Orm + system + + + Xtensive.Orm.Tests + theRest + + + + + + + sqlserver://localhost/ + + AsIs + Synonymize + UnderscoreHyphens,RemoveDots + + + Xtensive.Orm + system + + + Xtensive.Orm.Tests + theRest + + + + + + + sqlserver://localhost/ + + Default + Synonymize + UnderscoreHyphens,RemoveDots + + + Xtensive.Orm + system + + + Xtensive.Orm.Tests + theRest + + + + + + + sqlserver://localhost/ + + Uppercase + AsIs + UnderscoreHyphens,RemoveDots + + + + + sqlserver://localhost/ + + Uppercase + Hash + UnderscoreHyphens,RemoveDots + + + + + sqlserver://localhost/ + + Uppercase + Omit + UnderscoreHyphens,RemoveDots + + + + + sqlserver://localhost/ + + Uppercase + Default + UnderscoreHyphens,RemoveDots + + + + + sqlserver://localhost/ + + Uppercase + Hash + UnderscoreDots,RemoveHyphens + + + + + sqlserver://localhost/ + + Uppercase + Hash + None + + + + + sqlserver://localhost/ + + Uppercase + Hash + Default + + + + + sqlserver://localhost/ + + Defailt + Default + Default + + + + + sqlserver://localhost/ + + Default + Defailt + Default + + + + + sqlserver://localhost/ + + Default + Default + Defailt + + + + + + sqlserver://localhost/ + + Pessimistic + + + + + sqlserver://localhost/ + + Optimistic + + + + + sqlserver://localhost/ + + Default + + + + + sqlserver://localhost/ + + Optimistic + true + + + + + sqlserver://localhost/ + + Optimistic + false + + + + + sqlserver://localhost/ + + Defauit + + + + + sqlserver://localhost/ + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity2, Xtensive.Orm.Tests + + + + + + sqlserver://localhost/ + + + Xtensive.Orm + + + + + + sqlserver://localhost/ + + + Xtensive.Orm.Tests + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace + + + + + + sqlserver://localhost/ + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity2, Xtensive.Orm.Tests + + + Xtensive.Orm + + + Xtensive.Orm.Tests + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace + + + + + + sqlserver://localhost/ + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests + + + + + + sqlserver://localhost/ + + + Xtensive.Orm + + + Xtensive.Orm + + + + + + sqlserver://localhost/ + + + Xtensive.Orm.Tests + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace + + + Xtensive.Orm.Tests + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace + + + + + + sqlserver://localhost/ + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests + Xtensive.Orm + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace + + + + + + sqlserver://localhost/ + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests + Xtensive.Orm + + + + + + sqlserver://localhost/ + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace + + + + + + sqlserver://localhost/ + + + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace + + + + + + + sqlserver://localhost/ + + + main + DO-Tests-1 + + + other + DO-Tests-2 + + + + + sqlserver://localhost/ + + + main + DO-Tests-1 + 100 + 1000 + + + other + DO-Tests-2 + 2000 + 3000 + + + + + + + sqlserver://localhost/ + + + main + DO-Tests-1 + -10 + + + + + + sqlserver://localhost/ + + + main + DO-Tests-1 + 50 + + + + + + sqlserver://localhost/ + + + main + DO-Tests-1 + -10 + + + + + + sqlserver://localhost/ + + + main + DO-Tests-1 + 50 + + + + + + sqlserver://localhost/ + + + Int32 + 12 + 127 + + + + + + sqlserver://localhost/ + + + Int32 + DO-Tests-1 + + + Int32 + DO-Tests-2 + + + + + + sqlserver://localhost/ + + + Int32 + DO-Tests-1 + 12 + 127 + + + Int32 + DO-Tests-2 + 13 + 129 + + + + + + sqlserver://localhost/ + + + Int32 + main + 12 + 127 + + + Int32 + other + 13 + 129 + + + + + main + DO-Tests-1 + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + + + Int32 + DO-Tests-1 + + + Int32 + DO-Tests-1 + + + + + sqlserver://localhost/ + + + Int32 + DO-Tests-1 + + + Int32 + main + + + + + main + DO-Tests-1 + + + other + DO-Tests-2 + + + + + sqlserver://localhost/ + + + Int32 + DO-Tests-1 + -12 + + + + + sqlserver://localhost/ + + + Int32 + DO-Tests-1 + -127 + + + + + + + sqlserver://localhost/ + + + Other-DO40-Tests + some-schema1 + table1
+
+ + some-database + some-schema2 + column2 + + + some-database + some-schema2 + index2 + + + some-database + some-schema3 + table2
+ col3 +
+ + some-database + some-schema3 + table2
+ index3 +
+ + another-some-database + some-table
+
+ + database1 + some-column + + + database1 + some-index + + + schema1 + table1
+
+ + schema1 + column2 + + + schema1 + index2 + + + schema1 + table2
+ column3 +
+ + schema1 + table2
+ index3 +
+ + table4
+ column3 +
+ + table4
+ index2 +
+ + single-table
+
+ + single-column + + + single-index + +
+
+ + + sqlserver://localhost/ + + + column1 + index1 + + + + + sqlserver://localhost/ + + + table1
+ column1 + index1 +
+
+
+ + sqlserver://localhost/ + + + database1 + + + + + sqlserver://localhost/ + + + Other-DO40-Tests + some-schema1 + + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests + DO-Tests-1 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests + Model1 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Configuration.Options + DO-Tests-2 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests.Configuration + Model2 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests + Xtensive.Orm.Tests.Configuration.TypesToUseInTests + DO-Tests-3 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests + Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace + Model3 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests + Xtensive.Orm.Tests.Indexing + main + Model4 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests + DO-Tests-1 + + + Xtensive.Orm.Tests + Model1 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests.Configuration + DO-Tests-1 + + + Xtensive.Orm.Tests.Configuration + Model2 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + DO-Tests-1 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Model4 + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests + Xtensive.Orm.Tests.Configuration + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + sqlserver://localhost/ + main + dbo + + + Xtensive.Orm.Tests.Indexing + + + + + main + DO-Tests + + + other + DO-Tests-2 + + + + + + + + sqlserver://localhost/ + + + ClientProfile + + + + + + sqlserver://localhost/ + + + ClientProfile + + + + + + sqlserver://localhost/ + + + User + 126654 + + + + + + sqlserver://localhost/ + + + Default + + + + + sqlserver://localhost/ + + + ServerProfile + + + + + sqlserver://localhost/ + + + + + + sqlserver://localhost/ + + + AllowSwitching, AutoActivation, ReadRemovedObjects, ValidateEntityVersions + + + + + + sqlserver://localhost/ + + + 399 + 20 + 255 + + + + + sqlserver://localhost/ + + + Infinite + + + + + sqlserver://localhost/ + + + ReadCommitted + + + + + sqlserver://localhost/ + + + 300 + + + + + sqlserver://localhost/ + + + Always + + + + + + sqlserver://localhost/ + + + Data Source=localhost;Initial Catalog=DO-Tests;Integrated Security=True;MultipleActiveResultSets=True + + + + + + sqlserver://localhost/ + + + sqlserver://localhost/DO-Tests + + + + + + + + sqlserver://localhost/ + + + Infinite + 20 + 255 + AllowSwitching, AutoActivation, ReadRemovedObjects, ValidateEntityVersions + + + Infinite + 30 + ServerProfile + + + + + + sqlserver://localhost/ + + + Defailt + + + + + + sqlserver://localhost/ + + + -5 + + + + + + sqlserver://localhost/ + + + 0 + + + + + + sqlserver://localhost/ + + + 1 + + + + + + sqlserver://localhost/ + + + -5 + + + + + + sqlserver://localhost/ + + + 0 + + + + + + sqlserver://localhost/ + + + -5 + + + + + + sqlserver://localhost/ + + + 0 + + + + + + sqlserver://localhost/ + + + Defailt + + + +
+ + Xtensive.Orm.Logging.log4net.LogProvider + + + * + Console + + + SomeLogName + DebugOnlyConsole + + + FirstLogName,SecondLogName + d:\log.txt + + + LogName, AnotherLogName + Console + + + FileLog + log.txt + + + NullLog + None + + + Trash + skjdhfjsdf sdfsdfksjdghj fgdfg + + + +
+ + + + + + + + + + + + + Xtensive.Orm.Logging.log4net.LogProvider + + + + + Xtensive.Orm.Logging.log4net.LogProvider + + + + + \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/domainSettings.json b/Orm/Xtensive.Orm.Tests/domainSettings.json new file mode 100644 index 0000000000..7dde27cf13 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/domainSettings.json @@ -0,0 +1,1468 @@ +{ + "Xtensive.Orm.Json": { + "Domains": { + "DomainWithProviderAndConnectionString": { + "Provider": "sqlite", + "ConnectionString": "Data Source=DO-Testsaaa.db3" + }, + + "DomainWithConnectionUrl": { + "ConnectionUrl": "sqlite:///DO-Tests.db3" + }, + + "DomainWithCustomValidKeyCacheSize": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "KeyCacheSize": 192 + }, + "DomainWithCustomInvalidKeyCacheSize": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "KeyCacheSize": -256 + }, + + "DomainWithCustomValidKeyGeneratorCacheSize": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "KeyGeneratorCacheSize": 192 + }, + "DomainWithCustomInvalidKeyGeneratorCacheSize": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "KeyGeneratorCacheSize": -256 + }, + + "DomainWithCustomValidQueryCacheSize": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "QueryCacheSize": 192 + }, + "DomainWithCustomInvalidQueryCacheSize": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "QueryCacheSize": -256 + }, + + "DomainWithCustomValidRecordSetMappingCacheSize": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "RecordSetMappingCacheSize": 192 + }, + "DomainWithCustomInvalidRecordSetMappingCacheSize": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "RecordSetMappingCacheSize": -256 + }, + + "DomainWithCustomDatabase": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "DefaultDatabase": "MyFancyDatabase" + }, + + "DomainWithCustomSchema": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "DefaultSchema": "MyFancySchema" + }, + + "DomainWithWrongConnectionInfo": { + "UpgradeMode": "Recreate", + "ConnectionString": "Data Source=localhost;Initial Catalog=DO40-Tests;Integrated Security=True;MultipleActiveResultSets=True" + }, + + "DomainWithUpgradeMode1": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "UpgradeMode": "Default" + }, + "DomainWithUpgradeMode2": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "UpgradeMode": "Recreate" + }, + "DomainWithUpgradeMode3": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "UpgradeMode": "Perform" + }, + "DomainWithUpgradeMode4": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "UpgradeMode": "PerformSafely" + }, + "DomainWithUpgradeMode5": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "UpgradeMode": "Validate" + }, + "DomainWithUpgradeMode6": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "UpgradeMode": "LegacyValidate" + }, + "DomainWithUpgradeMode7": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "UpgradeMode": "Skip" + }, + "DomainWithUpgradeMode8": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "UpgradeMode": "LegacySkip" + }, + + "DomainWithWrongUpgradeMode": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "UpgradeMode": "Defailt" + }, + + "DomainWithForeignKeyMode1": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "ForeignKeyMode": "None" + }, + "DomainWithForeignKeyMode2": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "ForeignKeyMode": "Hierarchy" + }, + "DomainWithForeignKeyMode3": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "ForeignKeyMode": "Reference" + }, + "DomainWithForeignKeyMode4": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "ForeignKeyMode": "All" + }, + "DomainWithForeignKeyMode5": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "ForeignKeyMode": "Default" + }, + "DomainWithForeignKeyMode6": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "ForeignKeyMode": "Hierarchy,Reference" + }, + "DomainWithInvalidForeignKeyMode": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "ForeignKeyMode": "Defauit" + }, + + "DomainWithChangeTrackingMode1": { + "ConnectionUrl": "sqlserver://localhost/", + "FullTextChangeTrackingMode": "Off" + }, + "DomainWithChangeTrackingMode2": { + "ConnectionUrl": "sqlserver://localhost/", + "FullTextChangeTrackingMode": "Auto" + }, + "DomainWithChangeTrackingMode3": { + "ConnectionUrl": "sqlserver://localhost/", + "FullTextChangeTrackingMode": "Manual" + }, + "DomainWithChangeTrackingMode4": { + "ConnectionUrl": "sqlserver://localhost/", + "FullTextChangeTrackingMode": "OffWithNoPopulation" + }, + "DomainWithChangeTrackingMode5": { + "ConnectionUrl": "sqlserver://localhost/", + "FullTextChangeTrackingMode": "Default" + }, + "DomainWithInvalidChangeTrackingMode": { + "ConnectionUrl": "sqlserver://localhost/", + "FullTextChangeTrackingMode": "Defauit" + }, + + "DomainWithDomainOptionsValid1": { + "ConnectionUrl": "sqlserver://localhost/", + "Options": "Default" + }, + "DomainWithDomainOptionsValid2": { + "ConnectionUrl": "sqlserver://localhost/", + "Options": "None" + }, + "DomainWithDomainOptionsInvalid": { + "ConnectionUrl": "sqlserver://localhost/", + "Options": "Defauit" + }, + + "DomainWithColation": { + "ConnectionUrl": "sqlserver://localhost/", + "Collation": "generalci" + }, + + "DomainWithBriefSchemaSyncExceptions": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "SchemaSyncExceptionFormat": "Brief" + }, + "DomainWithDetailedSchemaSyncExceptions": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "SchemaSyncExceptionFormat": "Detailed" + }, + "DomainWithDefaultSchemaSyncExceptions": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "SchemaSyncExceptionFormat": "Default" + }, + "DomainWithInvalidSchemaSyncExceptions": { + "ConnectionUrl": "sqlserver://localhost/DO40-Tests", + "SchemaSyncExceptionFormat": "Defailt" + }, + + "DomainWithTagsLocationNowhere": { + "ConnectionUrl": "sqlserver://localhost/", + "TagsLocation": "Nowhere" + }, + "DomainWithTagsLocationBefore": { + "ConnectionUrl": "sqlserver://localhost/", + "TagsLocation": "BeforeStatement" + }, + "DomainWithTagsLocationWithin": { + "ConnectionUrl": "sqlserver://localhost/", + "TagsLocation": "WithinStatement" + }, + "DomainWithTagsLocationAfter": { + "ConnectionUrl": "sqlserver://localhost/", + "TagsLocation": "AfterStatement" + }, + "DomainWithTagsLocationDefault": { + "ConnectionUrl": "sqlserver://localhost/", + "TagsLocation": "Default" + }, + "DomainWithTagsLocationInvalid": { + "ConnectionUrl": "sqlserver://localhost/", + "TagsLocation": "Defauit" + }, + + "DomainWithForcedServerVersion": { + "ConnectionUrl": "sqlserver://localhost/", + "ForcedServerVersion": "10.0.0.0" + }, + "DomainWithInitSql": { + "ConnectionUrl": "sqlserver://localhost/", + "ConnectionInitializationSql": "use [OtherDb]" + }, + + "IncludeSqlInExceptionsTrue": { + "ConnectionUrl": "sqlserver://localhost/", + "IncludeSqlInExceptions": true + }, + "IncludeSqlInExceptionsFalse": { + "ConnectionUrl": "sqlserver://localhost/", + "IncludeSqlInExceptions": false + }, + + "AllowCyclicDatabaseDependenciesTrue": { + "ConnectionUrl": "sqlserver://localhost/", + "AllowCyclicDatabaseDependencies": true + }, + "AllowCyclicDatabaseDependenciesFalse": { + "ConnectionUrl": "sqlserver://localhost/", + "AllowCyclicDatabaseDependencies": false + }, + + "BuildInParallelTrue": { + "ConnectionUrl": "sqlserver://localhost/", + "BuildInParallel": true + }, + "BuildInParallelFalse": { + "ConnectionUrl": "sqlserver://localhost/", + "BuildInParallel": false + }, + + "MultidatabaseKeysTrue": { + "ConnectionUrl": "sqlserver://localhost/", + "MultidatabaseKeys": true + }, + "MultidatabaseKeysFalse": { + "ConnectionUrl": "sqlserver://localhost/", + "MultidatabaseKeys": false + }, + + "SharedStorageSchemaOn": { + "ConnectionUrl": "sqlserver://localhost/", + "ShareStorageSchemaOverNodes": true + }, + "SharedStorageSchemaOff": { + "ConnectionUrl": "sqlserver://localhost/", + "ShareStorageSchemaOverNodes": false + }, + + "EnableConnectionIsAliveTrue": { + "ConnectionUrl": "sqlserver://localhost/", + "EnsureConnectionIsAlive": true + }, + "EnableConnectionIsAliveFalse": { + "ConnectionUrl": "sqlserver://localhost/", + "EnsureConnectionIsAlive": false + }, + + "PreferTypeIdsAsQueryParametersTrue": { + "ConnectionUrl": "sqlserver://localhost/", + "PreferTypeIdsAsQueryParameters": true + }, + "PreferTypeIdsAsQueryParametersFalse": { + "ConnectionUrl": "sqlserver://localhost/", + "PreferTypeIdsAsQueryParameters": false + }, + + "DomainWithNamingConvention1": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Uppercase", + "NamespacePolicy": "Synonymize", + "NamingRules": "UnderscoreHyphens,RemoveDots", + "NamespaceSynonyms": [ + { + "Namespace": "Xtensive.Orm", + "Synonym": "system" + }, + { + "Namespace": "Xtensive.Orm.Tests", + "Synonym": "theRest" + } + ] + } + }, + "DomainWithNamingConvention2": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Lowercase", + "NamespacePolicy": "Synonymize", + "NamingRules": "UnderscoreHyphens,RemoveDots", + "NamespaceSynonyms": [ + { + "Namespace": "Xtensive.Orm", + "Synonym": "system" + }, + { + "Namespace": "Xtensive.Orm.Tests", + "Synonym": "theRest" + } + ] + } + }, + "DomainWithNamingConvention3": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "AsIs", + "NamespacePolicy": "Synonymize", + "NamingRules": "UnderscoreHyphens,RemoveDots", + "NamespaceSynonyms": [ + { + "Namespace": "Xtensive.Orm", + "Synonym": "system" + }, + { + "Namespace": "Xtensive.Orm.Tests", + "Synonym": "theRest" + } + ] + } + }, + "DomainWithNamingConvention4": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Default", + "NamespacePolicy": "Synonymize", + "NamingRules": "UnderscoreHyphens,RemoveDots", + "NamespaceSynonyms": [ + { + "Namespace": "Xtensive.Orm", + "Synonym": "system" + }, + { + "Namespace": "Xtensive.Orm.Tests", + "Synonym": "theRest" + } + ] + } + }, + "DomainWithNamingConvention5": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Uppercase", + "NamespacePolicy": "AsIs", + "NamingRules": "UnderscoreHyphens,RemoveDots" + } + }, + "DomainWithNamingConvention6": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Uppercase", + "NamespacePolicy": "Hash", + "NamingRules": "UnderscoreHyphens,RemoveDots" + } + }, + "DomainWithNamingConvention7": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Uppercase", + "NamespacePolicy": "Omit", + "NamingRules": "UnderscoreHyphens,RemoveDots" + } + }, + "DomainWithNamingConvention8": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Uppercase", + "NamespacePolicy": "Default", + "NamingRules": "UnderscoreHyphens,RemoveDots" + } + }, + "DomainWithNamingConvention9": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Uppercase", + "NamespacePolicy": "Hash", + "NamingRules": "UnderscoreDots,RemoveHyphens" + } + }, + "DomainWithNamingConvention10": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Uppercase", + "NamespacePolicy": "Hash", + "NamingRules": "None" + } + }, + "DomainWithNamingConvention11": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Uppercase", + "NamespacePolicy": "Hash", + "NamingRules": "Default" + } + }, + + "DomainWithInvalidNamingConvention1": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Defailt", + "NamespacePolicy": "Default", + "NamingRules": "Default" + } + }, + "DomainWithInvalidNamingConvention2": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Default", + "NamespacePolicy": "Defailt", + "NamingRules": "Default" + } + }, + "DomainWithInvalidNamingConvention3": { + "ConnectionUrl": "sqlserver://localhost/", + "NamingConvention": { + "LetterCasePolicy": "Default", + "NamespacePolicy": "Default", + "NamingRules": "Defailt" + } + }, + + "DomainWithVersioningConvention1": { + "ConnectionUrl": "sqlserver://localhost/", + "VersioningConvention": { + "EntityVersioningPolicy": "Pessimistic" + } + }, + "DomainWithVersioningConvention2": { + "ConnectionUrl": "sqlserver://localhost/", + "VersioningConvention": { + "EntityVersioningPolicy": "Optimistic" + } + }, + "DomainWithVersioningConvention3": { + "ConnectionUrl": "sqlserver://localhost/", + "VersioningConvention": { + "EntityVersioningPolicy": "Default" + } + }, + "DomainWithVersioningConvention4": { + "ConnectionUrl": "sqlserver://localhost/", + "VersioningConvention": { + "EntityVersioningPolicy": "Optimistic", + "DenyEntitySetOwnerVersionChange": true + } + }, + "DomainWithVersioningConvention5": { + "ConnectionUrl": "sqlserver://localhost/", + "VersioningConvention": { + "EntityVersioningPolicy": "Optimistic", + "DenyEntitySetOwnerVersionChange": false + } + }, + + "DomainWithInvalidVersioningConvention1": { + "ConnectionUrl": "sqlserver://localhost/", + "VersioningConvention": { + "EntityVersioningPolicy": "Defauit" + } + }, + + "DomainWithTypes": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Type": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests" + }, + { + "Type": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity2, Xtensive.Orm.Tests" + } + ] + }, + "DomainWithAssemblies": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Assembly": "Xtensive.Orm" + } + ] + }, + "DomainWithAssembliesAndNamespaces": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Assembly": "Xtensive.Orm.Tests", + "Namespace": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace" + } + ] + }, + + "DomainWithMixedRegistrations": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Type": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests" + }, + { + "Type": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity2, Xtensive.Orm.Tests" + }, + { + "Assembly": "Xtensive.Orm" + }, + { + "Assembly": "Xtensive.Orm.Tests", + "Namespace": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace" + } + ] + }, + + "DomainWithInvalidRegistrations1": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Type": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests" + }, + { + "Type": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests" + } + ] + }, + "DomainWithInvalidRegistrations2": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Assembly": "Xtensive.Orm" + }, + { + "Assembly": "Xtensive.Orm" + } + ] + }, + "DomainWithInvalidRegistrations3": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Assembly": "Xtensive.Orm.Tests", + "Namespace": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace" + }, + { + "Assembly": "Xtensive.Orm.Tests", + "Namespace": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace" + } + ] + }, + "DomainWithInvalidRegistrations4": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Type": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests", + "Assembly": "Xtensive.Orm", + "Namespace": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace" + } + ] + }, + "DomainWithInvalidRegistrations5": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Type": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests", + "Assembly": "Xtensive.Orm" + } + ] + }, + "DomainWithInvalidRegistrations6": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Type": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.DummyEntity1, Xtensive.Orm.Tests", + "Namespace": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace" + } + ] + }, + "DomainWithInvalidRegistrations7": { + "ConnectionUrl": "sqlserver://localhost/", + "Types": [ + { + "Namespace": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace" + } + ] + }, + + "DomainWithDatabases1": { + "ConnectionUrl": "sqlserver://localhost/", + "Databases": { + "main": { + "RealName": "DO-Tests-1" + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithDatabases2": { + "ConnectionUrl": "sqlserver://localhost/", + "Databases": { + "main": { + "RealName": "DO-Tests-1", + "MinTypeId": 100, + "MaxTypeId": 1000 + + }, + "other": { + "RealName": "DO-Tests-2", + "MinTypeId": 2000, + "MaxTypeId": 3000 + } + } + }, + + "DomainWithInvalidDatabases1": { + "ConnectionUrl": "sqlserver://localhost/", + "Databases": { + "main": { + "RealName": "DO-Tests-1", + "MinTypeId": -10 + } + } + }, + "DomainWithInvalidDatabases2": { + "ConnectionUrl": "sqlserver://localhost/", + "Databases": { + "main": { + "RealName": "DO-Tests-1", + "MinTypeId": 50 + } + } + }, + "DomainWithInvalidDatabases3": { + "ConnectionUrl": "sqlserver://localhost/", + "Databases": { + "main": { + "RealName": "DO-Tests-1", + "MaxTypeId": -10 + } + } + }, + "DomainWithInvalidDatabases4": { + "ConnectionUrl": "sqlserver://localhost/", + "Databases": { + "main": { + "RealName": "DO-Tests-1", + "MaxTypeId": 50 + } + } + }, + + "DomainWithCustomGenerator": { + "ConnectionUrl": "sqlserver://localhost/", + "KeyGenerators": [ + { + "Name": "Int32", + "Seed": 12, + "CacheSize": 127 + } + ] + }, + "DomainWithCustomGeneratorsWithDatabaseNames": { + "ConnectionUrl": "sqlserver://localhost/", + "KeyGenerators": [ + { + "Name": "Int32", + "Database": "DO-Tests-1" + }, + { + "Name": "Int32", + "Database": "DO-Tests-2" + } + ] + }, + "DomainWithCustomGeneratorsWithDatabaseNamesAndKeyParams": { + "ConnectionUrl": "sqlserver://localhost/", + "KeyGenerators": [ + { + "Name": "Int32", + "Database": "DO-Tests-1", + "Seed": 12, + "CacheSize": 127 + }, + { + "Name": "Int32", + "Database": "DO-Tests-2", + "Seed": 13, + "CacheSize": 129 + } + ] + }, + "DomainWithCustomGeneratorsWithDatabasesAliases": { + "ConnectionUrl": "sqlserver://localhost/", + "KeyGenerators": [ + { + "Name": "Int32", + "Database": "main", + "Seed": 12, + "CacheSize": 127 + }, + { + "Name": "Int32", + "Database": "Other", + "Seed": 13, + "CacheSize": 129 + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests-1" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + + "DomainWithCustomGeneratorsConflict1": { + "ConnectionUrl": "sqlserver://localhost/", + "KeyGenerators": [ + { + "Name": "Int32", + "Database": "DO-Tests-1" + }, + { + "Name": "Int32", + "Database": "DO-Tests-1" + } + ] + }, + "DomainWithCustomGeneratorsConflict2": { + "ConnectionUrl": "sqlserver://localhost/", + "KeyGenerators": [ + { + "Name": "Int32", + "Database": "DO-Tests-1" + }, + { + "Name": "Int32", + "Database": "main" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests-1" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithCustomGeneratorNegativeSeed": { + "ConnectionUrl": "sqlserver://localhost/", + "KeyGenerators": [ + { + "Name": "Int32", + "Database": "DO-Tests-1", + "Seed": -12 + } + ] + }, + "DomainWithCustomGeneratorNegativeCache": { + "ConnectionUrl": "sqlserver://localhost/", + "KeyGenerators": [ + { + "Name": "Int32", + "Database": "DO-Tests-1", + "CacheSize": -127 + } + ] + }, + + "DomainWithIgnoreRules": { + "ConnectionUrl": "sqlserver://localhost/", + "IgnoreRules": [ + { + "Database": "Other-DO40-Tests", + "Schema": "some-schema1", + "Table": "table1" + }, + { + "Database": "some-database", + "Schema": "some-schema2", + "Column": "column2" + }, + { + "Database": "some-database", + "Schema": "some-schema2", + "Index": "index2" + }, + { + "Database": "some-database", + "Schema": "some-schema3", + "Table": "table2", + "Column": "col3" + }, + { + "Database": "some-database", + "Schema": "some-schema3", + "Table": "table2", + "Index": "index3" + }, + { + "Database": "another-some-database", + "Table": "some-table" + }, + { + "Database": "database1", + "Column": "some-column" + }, + { + "Database": "database1", + "Index": "some-index" + }, + { + "Schema": "schema1", + "Table": "table1" + }, + { + "Schema": "schema1", + "Column": "column2" + }, + { + "Schema": "schema1", + "Index": "index2" + }, + { + "Schema": "schema1", + "Table": "table2", + "Column": "column3" + }, + { + "Schema": "schema1", + "Table": "table2", + "Index": "index3" + }, + { + "Table": "table4", + "Column": "column3" + }, + { + "Table": "table4", + "Index": "index2" + }, + { + "Table": "single-table" + }, + { + "Column": "single-column" + }, + { + "Index": "single-index" + } + ] + }, + "DomainWithInvalidIgnoreRules1": { + "ConnectionUrl": "sqlserver://localhost/", + "IgnoreRules": [ + { + "Column": "column1", + "Index": "index1" + } + ] + }, + "DomainWithInvalidIgnoreRules2": { + "ConnectionUrl": "sqlserver://localhost/", + "IgnoreRules": [ + { + "Table": "table1", + "Column": "column1", + "Index": "index1" + } + ] + }, + "DomainWithInvalidIgnoreRules3": { + "ConnectionUrl": "sqlserver://localhost/", + "IgnoreRules": [ + { + "Database": "database1" + } + ] + }, + "DomainWithInvalidIgnoreRules4": { + "ConnectionUrl": "sqlserver://localhost/", + "IgnoreRules": [ + { + "Database": "database1", + "Schema": "schema1" + } + ] + }, + + "DomainWithMappingRules1": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Assembly": "Xtensive.Orm.Tests", + "Database": "DO-Tests-1" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithMappingRules2": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Assembly": "Xtensive.Orm.Tests", + "Schema": "Model1" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithMappingRules3": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Namespace": "Xtensive.Orm.Configuration.Options", + "Database": "DO-Tests-2" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithMappingRules4": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Namespace": "Xtensive.Orm.Tests.Configuration", + "Schema": "Model2" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithMappingRules5": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Assembly": "Xtensive.Orm.Tests", + "Namespace": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests", + "Database": "DO-Tests-3" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithMappingRules6": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Assembly": "Xtensive.Orm.Tests", + "Namespace": "Xtensive.Orm.Tests.Configuration.TypesToUseInTests.NestedNamespace", + "Schema": "Model3" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithMappingRules7": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Assembly": "Xtensive.Orm.Tests", + "Namespace": "Xtensive.Orm.Tests.Indexing", + "Database": "main", + "Schema": "Model4" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + + "DomainWithConflictMappingRules1": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Assembly": "Xtensive.Orm.Tests", + "Database": "DO-Tests-1" + }, + { + "Assembly": "Xtensive.Orm.Tests", + "Schema": "Model1" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithConflictMappingRules2": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Namespace": "Xtensive.Orm.Tests.Configuration", + "Database": "DO-Tests-2" + }, + { + "Namespace": "Xtensive.Orm.Tests.Configuration", + "Schema": "Model2" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + + "DomainWithInvalidMappingRules1": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Database": "main" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithInvalidMappingRules2": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Schema": "Model4" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithInvalidMappingRules3": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Assembly": "Xtensive.Orm.Tests", + "Namespace": "Xtensive.Orm.Tests.Indexing" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithInvalidMappingRules4": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Assembly": "Xtensive.Orm.Tests" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + "DomainWithInvalidMappingRules5": { + "ConnectionUrl": "sqlserver://localhost/", + "DefaultDatabase": "main", + "DefaultSchema": "dbo", + "MappingRules": [ + { + "Namespace": "Xtensive.Orm.Tests.Indexing" + } + ], + "Databases": { + "main": { + "RealName": "DO-Tests" + + }, + "other": { + "RealName": "DO-Tests-2" + } + } + }, + + "DomainWithSessionEmptyName": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "": { + "Options": "ClientProfile" + } + } + }, + "DomainWithSessionCustomName": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "UserCreated": { + "Options": "ClientProfile" + } + } + }, + "DomainWithSessionCustomUser": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "UserName": "User", + "Password": "126654" + } + } + }, + "DomainWithSessionDefaultOptions": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "Options": "Default" + } + } + }, + "DomainWithSessionServerProfile": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "Options": "ServerProfile" + } + } + }, + "DomainWithSessionClientProfile": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "Options": "ClientProfile" + } + } + }, + "DomainWithSessionCustomOptions": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "Options": "AllowSwitching, AutoActivation, ReadRemovedObjects, ValidateEntityVersions" + } + } + }, + + "DomainWithSessionWithCollectionSizes": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "CacheSize": 399, + "BatchSize": 20, + "EntityChangeRegistrySize": 255 + } + } + }, + + "DomainWithSessionCustomCacheType": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "CacheType": "Infinite" + } + } + }, + + "DomainWithSessionCustomIsolationLevel": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "DefaultIsolationLevel": "ReadCommitted" + } + } + }, + + "DomainWithSessionCustomCommandTimeout": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "DefaultCommandTimeout": 300 + } + } + }, + + "DomainWithSessionCustomPreloadingPolicy": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "ReaderPreloading": "Always" + } + } + }, + + "DomainWithSessionCustomConnectionString": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "ConnectionString": "Data Source=localhost;Initial Catalog=DO-Tests;Integrated Security=True;MultipleActiveResultSets=True" + } + } + }, + "DomainWithSessionCustomConnectionUrl": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "ConnectionUrl": "sqlserver://localhost/DO-Tests" + } + } + }, + + "DomainWithMultipleSessionConfigurations": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "CacheType": "Infinite", + "BatchSize": 20, + "EntityChangeRegistrySize": 255, + "Options": "AllowSwitching, AutoActivation, ReadRemovedObjects, ValidateEntityVersions" + }, + "System": { + "CacheType": "Infinite", + "BatchSize": 30, + "Options": "ServerProfile" + } + } + }, + + "DomainWithSessionInvalidOptions": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "Options": "Defailt" + } + } + }, + "DomainWithSessionInvalidCacheSize1": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "CacheSize": -5 + } + } + }, + "DomainWithSessionInvalidCacheSize2": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "CacheSize": 0 + } + } + }, + "DomainWithSessionInvalidCacheSize3": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "CacheSize": 1 + } + } + }, + "DomainWithSessionInvalidBatchSize1": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "BatchSize": -5 + } + } + }, + "DomainWithSessionInvalidBatchSize2": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "BatchSize": 0 + } + } + }, + "DomainWithSessionInvalidEntityChangeRegistry1": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "EntityChangeRegistrySize": -5 + } + } + }, + "DomainWithSessionInvalidEntityChangeRegistry2": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "EntityChangeRegistrySize": 0 + } + } + }, + "DomainWithSessionInvalidCacheType": { + "ConnectionUrl": "sqlserver://localhost/", + "Sessions": { + "Default": { + "CacheType": "Defailt" + } + } + } + }, + "Logging": { + "Provider": "Xtensive.Orm.Logging.log4net.LogProvider", + "Logs": [ + { + "Source": "*", + "Target": "Console" + }, + { + "Source": "SomeLogName", + "Target": "DebugOnlyConsole" + }, + { + "Source": "FirstLogName,SecondLogName", + "Target": "d:\\log.txt" + }, + { + "Source": "LogName, AnotherLogName", + "Target": "Console" + }, + { + "Source": "FileLog", + "Target": "log.txt" + }, + { + "Source": "NullLog", + "Target": "None" + }, + { + "Source": "Trash", + "Target": "skjdhfjsdf sdfsdfksjdghj fgdfg" + } + ] + } + }, + "Xtensive.Orm.EmptyLogging.Json": { + "Logging": { + + } + }, + "Xtensive.Orm.EmptyLogs.Json": { + "Logging": { + "Logs": [] + } + }, + "Xtensive.Orm.OnlyLogProvider.Json": { + "Logging": { + "Provider": "Xtensive.Orm.Logging.log4net.LogProvider" + } + }, + "Xtensive.Orm.LogProviderAndEmptyLogs.Json": { + "Logging": { + "Provider": "Xtensive.Orm.Logging.log4net.LogProvider", + "Logs": [] + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/ConfigurationBase.cs b/Orm/Xtensive.Orm/Orm/Configuration/ConfigurationBase.cs index 30aed0ce16..8b10852968 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/ConfigurationBase.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/ConfigurationBase.cs @@ -35,7 +35,7 @@ public override void Lock(bool recursive) /// public virtual object Clone() { - ConfigurationBase clone = CreateClone(); + var clone = CreateClone(); clone.CopyFrom(this); return clone; } diff --git a/Orm/Xtensive.Orm/Orm/Configuration/DatabaseConfiguration.cs b/Orm/Xtensive.Orm/Orm/Configuration/DatabaseConfiguration.cs index 15deb1db86..83e0368494 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/DatabaseConfiguration.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/DatabaseConfiguration.cs @@ -52,7 +52,7 @@ public string RealName /// /// Gets or sets type ID minimal value /// for types mapped to this database. - /// Default value is 100. + /// Default value is . /// public int MinTypeId { diff --git a/Orm/Xtensive.Orm/Orm/Configuration/DomainConfiguration.cs b/Orm/Xtensive.Orm/Orm/Configuration/DomainConfiguration.cs index f4bd9984c8..573c530650 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/DomainConfiguration.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/DomainConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2007-2022 Xtensive LLC. +// Copyright (C) 2007-2024 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Dmitri Maximov @@ -8,16 +8,16 @@ using System.Configuration; using System.Linq; using JetBrains.Annotations; +using Microsoft.Extensions.Configuration; using Xtensive.Core; -using Xtensive.Orm.Configuration.Elements; using Xtensive.Orm.Internals; -using ConfigurationSection=Xtensive.Orm.Configuration.Elements.ConfigurationSection; +using ConfigurationSection = Xtensive.Orm.Configuration.Elements.ConfigurationSection; namespace Xtensive.Orm.Configuration { /// /// The configuration of the . - /// + ///
[Serializable] public class DomainConfiguration : ConfigurationBase { @@ -67,12 +67,12 @@ public class DomainConfiguration : ConfigurationBase /// /// Default value: - /// + /// . /// public const bool DefaultShareStorageSchemaOverNodes = false; /// - /// Default value: + /// Default value: . /// public const bool DefaultAllowCyclicDatabaseDependencies = false; @@ -95,8 +95,14 @@ public class DomainConfiguration : ConfigurationBase /// /// Default value. /// + [Obsolete ("User DefaultForeignKeyMode")] public const ForeignKeyMode DefauktForeignKeyMode = ForeignKeyMode.Default; + /// + /// Default value. + /// + public const ForeignKeyMode DefaultForeignKeyMode = ForeignKeyMode.Default; + /// /// Default value. /// @@ -138,6 +144,7 @@ public class DomainConfiguration : ConfigurationBase private DatabaseConfigurationCollection databases = new(); private KeyGeneratorConfigurationCollection keyGenerators = new(); private IgnoreRuleCollection ignoreRules = new(); + private ExtensionConfigurationCollection extensionConfigurations = new(); private NamingConvention namingConvention = new(); private VersioningConvention versioningConvention = new(); private int keyCacheSize = DefaultKeyCacheSize; @@ -154,7 +161,7 @@ public class DomainConfiguration : ConfigurationBase private bool ensureConnectionIsAlive = DefaultEnsureConnectionIsAlive; private bool preferTypeIdsAsQueryParameters = DefaultPreferTypeIdsAsQueryParameters; private DomainUpgradeMode upgradeMode = DefaultUpgradeMode; - private ForeignKeyMode foreignKeyMode = DefauktForeignKeyMode; + private ForeignKeyMode foreignKeyMode = DefaultForeignKeyMode; private FullTextChangeTrackingMode fullTextChangeTrackingMode = DefaultFullTextChangeTrackingMode; private DomainOptions options = DefaultDomainOptions; private SchemaSyncExceptionFormat schemaSyncExceptionFormat = DefaultSchemaSyncExceptionFormat; @@ -347,6 +354,7 @@ public ForeignKeyMode ForeignKeyMode /// /// Gets or sets a value indicating change tracking mode of full-text indexes. /// The property may have no effect for certain storages where there is no support for such option. + /// Default value is . /// public FullTextChangeTrackingMode FullTextChangeTrackingMode { @@ -436,6 +444,18 @@ public Type ServiceContainerType } } + /// + /// Gets collection of additional configurations (configurations of extensions) + /// that might be required during domain build process. + /// + public ExtensionConfigurationCollection ExtensionConfigurations { + get => extensionConfigurations; + set { + EnsureNotLocked(); + extensionConfigurations = value; + } + } + /// /// Gets or sets value indicating whether SQL text of a query /// that caused error should be included in exception message. @@ -583,7 +603,7 @@ public bool ShareStorageSchemaOverNodes } /// - /// Gets or sets versioning convention. + /// Gets or sets rules of entity versioning. /// public VersioningConvention VersioningConvention { @@ -596,6 +616,13 @@ public VersioningConvention VersioningConvention /// /// Enables extra check if connection is not broken on its opening. + /// For some RDBMSs physical connection may become broken but connection pool, which + /// is in charge of logical connections, is not aware of it. + /// In such cases connection pool provides connection + /// which is dead on arrival and causes exception on first use of it. + /// Such connection should be re-created and re-opened. The extra check + /// tests connection by making "first use" of it, if check failed connection + /// will be restored automatically. /// public bool EnsureConnectionIsAlive { @@ -640,6 +667,9 @@ public TagsLocation TagsLocation /// public bool IsMultischema => isMultischema ?? GetIsMultischema(); + internal bool Supports(DomainOptions optionsToCheck) => (options & optionsToCheck) == optionsToCheck; + + internal bool Supports(ForeignKeyMode modeToCheck) => (foreignKeyMode & modeToCheck) == modeToCheck; private bool GetIsMultidatabase() { @@ -676,6 +706,7 @@ public override void Lock(bool recursive) keyGenerators.Lock(true); ignoreRules.Lock(true); versioningConvention.Lock(true); + extensionConfigurations.Lock(true); base.Lock(recursive); @@ -754,6 +785,7 @@ protected override void CopyFrom(ConfigurationBase source) shareStorageSchemaOverNodes = configuration.ShareStorageSchemaOverNodes; versioningConvention = (VersioningConvention) configuration.VersioningConvention.Clone(); preferTypeIdsAsQueryParameters = configuration.PreferTypeIdsAsQueryParameters; + ExtensionConfigurations = (ExtensionConfigurationCollection) configuration.ExtensionConfigurations.Clone(); } /// @@ -762,6 +794,8 @@ protected override void CopyFrom(ConfigurationBase source) /// The clone of this configuration. public new DomainConfiguration Clone() => (DomainConfiguration) base.Clone(); + #region Static DomainConfiguration.Load() methods + /// /// Loads the for /// with the specified @@ -841,11 +875,6 @@ public static DomainConfiguration Load(System.Configuration.Configuration config return LoadConfigurationFromSection(section, name); } - internal bool Supports(DomainOptions optionsToCheck) => (options & optionsToCheck) == optionsToCheck; - - internal bool Supports(ForeignKeyMode modeToCheck) => (foreignKeyMode & modeToCheck) == modeToCheck; - - private static DomainConfiguration LoadConfigurationFromSection(ConfigurationSection section, string name) { var domainElement = section.Domains[name]; @@ -856,6 +885,70 @@ private static DomainConfiguration LoadConfigurationFromSection(ConfigurationSec return domainElement.ToNative(); } + /// + /// Loads the for + /// with the specified . + /// + /// Root configuration section where domain configurations are placed + /// Name of the . + /// Domain configuration. + /// The "domains" section is not found or domain with requested name is not found. + public static DomainConfiguration Load(IConfigurationSection configurationSection, string name) + { + ArgumentValidator.EnsureArgumentNotNull(configurationSection, nameof(configurationSection)); + + var jsonParser = new JsonToDomainConfigurationReader(); + var xmlParser = new XmlToDomainConfigurationReader(); + + var parseResult = jsonParser.Read(configurationSection, name); + if (parseResult != null) + return parseResult; + parseResult = xmlParser.Read(configurationSection, name); + if (parseResult != null) + return parseResult; + + throw new InvalidOperationException(string.Format( + Strings.ExConfigurationForDomainIsNotFoundInApplicationConfigurationFile, name, sectionName)); + } + + public static DomainConfiguration Load(IConfigurationRoot configurationRoot, string name) + { + ArgumentValidator.EnsureArgumentNotNull(configurationRoot, nameof(configurationRoot)); + + var jsonParser = new JsonToDomainConfigurationReader(); + var xmlParser = new XmlToDomainConfigurationReader(); + + var parseResult = jsonParser.Read(configurationRoot, name); + if (parseResult != null) + return parseResult; + parseResult = xmlParser.Read(configurationRoot, name); + if (parseResult != null) + return parseResult; + + throw new InvalidOperationException(string.Format( + Strings.ExConfigurationForDomainIsNotFoundInApplicationConfigurationFile, name, sectionName)); + } + + public static DomainConfiguration Load(IConfigurationRoot configurationRoot, string sectionName, string name) + { + ArgumentValidator.EnsureArgumentNotNull(configurationRoot, nameof(configurationRoot)); + + var jsonParser = new JsonToDomainConfigurationReader(); + var xmlParser = new XmlToDomainConfigurationReader(); + + var parseResult = jsonParser.Read(configurationRoot, sectionName, name); + if (parseResult != null) + return parseResult; + parseResult = xmlParser.Read(configurationRoot, sectionName, name); + if (parseResult != null) + return parseResult; + + throw new InvalidOperationException(string.Format( + Strings.ExConfigurationForDomainIsNotFoundInApplicationConfigurationFile, name, sectionName)); + } + + #endregion + // Constructors /// @@ -907,4 +1000,4 @@ public DomainConfiguration() types.Register(WellKnownOrmTypes.Persistent.Assembly, WellKnownOrmTypes.Persistent.Namespace); } } -} +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Elements/DatabaseConfigurationElement.cs b/Orm/Xtensive.Orm/Orm/Configuration/Elements/DatabaseConfigurationElement.cs index 0e6fdafef0..487b8450a1 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/Elements/DatabaseConfigurationElement.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/Elements/DatabaseConfigurationElement.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2012 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2012-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov // Created: 2012.02.08 @@ -24,7 +24,7 @@ public class DatabaseConfigurationElement : ConfigurationCollectionElementBase public override object Identifier { get { return Name; } } /// - /// + /// /// [ConfigurationProperty(NameElementName, IsKey = true)] public string Name @@ -34,7 +34,7 @@ public string Name } /// - /// + /// /// [ConfigurationProperty(RealNameElementName)] public string RealName @@ -44,7 +44,7 @@ public string RealName } /// - /// + /// /// [ConfigurationProperty(MinTypeIdElementName, DefaultValue = TypeInfo.MinTypeId)] public int MinTypeId @@ -54,7 +54,7 @@ public int MinTypeId } /// - /// + /// /// [ConfigurationProperty(MaxTypeIdElementName, DefaultValue = int.MaxValue)] public int MaxTypeId diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Elements/DomainConfigurationElement.cs b/Orm/Xtensive.Orm/Orm/Configuration/Elements/DomainConfigurationElement.cs index d81753afa0..7837360145 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/Elements/DomainConfigurationElement.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/Elements/DomainConfigurationElement.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2008-2023 Xtensive LLC. +// Copyright (C) 2008-2024 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Aleksey Gamzov @@ -58,7 +58,7 @@ public class DomainConfigurationElement : ConfigurationCollectionElementBase public override object Identifier { get { return Name; } } /// - /// + /// /// [ConfigurationProperty(NameElementName, IsKey = true, DefaultValue = "")] public string Name @@ -68,7 +68,7 @@ public string Name } /// - /// + /// /// [ConfigurationProperty(ConnectionUrlElementName, DefaultValue = null)] public string ConnectionUrl @@ -78,7 +78,7 @@ public string ConnectionUrl } /// - /// + /// /// [ConfigurationProperty(ConnectionStringElementName, DefaultValue = null)] public string ConnectionString @@ -88,7 +88,7 @@ public string ConnectionString } /// - /// + /// /// [ConfigurationProperty(ProviderElementName, DefaultValue = WellKnown.Provider.SqlServer)] public string Provider @@ -98,7 +98,7 @@ public string Provider } /// - /// + /// /// [ConfigurationProperty(TypesElementName, IsDefaultCollection = false)] [ConfigurationCollection(typeof (ConfigurationCollection), AddItemName = "add")] @@ -108,7 +108,7 @@ public ConfigurationCollection Types } /// - /// + /// /// [ConfigurationProperty(NamingConventionElementName)] public NamingConventionElement NamingConvention @@ -118,7 +118,7 @@ public NamingConventionElement NamingConvention } /// - /// + /// /// [ConfigurationProperty(KeyCacheSizeElementName, DefaultValue = DomainConfiguration.DefaultKeyCacheSize)] [IntegerValidator(MinValue = 1, MaxValue = int.MaxValue)] @@ -129,7 +129,7 @@ public int KeyCacheSize } /// - /// + /// /// [ConfigurationProperty(KeyGeneratorCacheSizeElementName, DefaultValue = DomainConfiguration.DefaultKeyGeneratorCacheSize)] [IntegerValidator(MinValue = 1, MaxValue = int.MaxValue)] @@ -140,7 +140,7 @@ public int KeyGeneratorCacheSize } /// - /// + /// /// [ConfigurationProperty(QueryCacheSizeElementName, DefaultValue = DomainConfiguration.DefaultQueryCacheSize)] [IntegerValidator(MinValue = 1, MaxValue = int.MaxValue)] @@ -151,7 +151,7 @@ public int QueryCacheSize } /// - /// + /// /// [ConfigurationProperty(RecordSetMappingCacheSizeElementName, DefaultValue = DomainConfiguration.DefaultRecordSetMappingCacheSize)] [IntegerValidator(MinValue = 1, MaxValue = int.MaxValue)] @@ -162,7 +162,7 @@ public int RecordSetMappingCacheSize } /// - /// + /// /// [ConfigurationProperty(UpgradeModeElementName, DefaultValue = "Default")] public string UpgradeMode @@ -172,7 +172,7 @@ public string UpgradeMode } /// - /// + /// /// [ConfigurationProperty(SchemaSyncExceptionFormatElementName, DefaultValue = "Default")] public string SchemaSyncExceptionFormat @@ -182,7 +182,7 @@ public string SchemaSyncExceptionFormat } /// - /// + /// /// [ConfigurationProperty(ForeignKeyModeElementName, DefaultValue = "Default")] public string ForeignKeyMode @@ -192,7 +192,7 @@ public string ForeignKeyMode } /// - /// + /// /// [ConfigurationProperty(FullTextChangeTrackingModeElementName, DefaultValue = "Default")] public string FullTextChangeTrackingMode @@ -202,7 +202,7 @@ public string FullTextChangeTrackingMode } /// - /// + /// /// [ConfigurationProperty(CollationElementName)] public string Collation @@ -212,7 +212,7 @@ public string Collation } /// - /// + /// /// [ConfigurationProperty(SessionsElementName, IsDefaultCollection = false)] [ConfigurationCollection(typeof (ConfigurationCollection), AddItemName = "session")] @@ -222,7 +222,7 @@ public ConfigurationCollection Sessions } /// - /// + /// /// [ConfigurationProperty(MappingRulesElementName, IsDefaultCollection = false)] [ConfigurationCollection(typeof (ConfigurationCollection), AddItemName = "rule")] @@ -232,7 +232,7 @@ public ConfigurationCollection MappingRules } /// - /// + /// /// [ConfigurationProperty(DatabasesElementName, IsDefaultCollection = false)] [ConfigurationCollection(typeof (ConfigurationCollection), AddItemName = "database")] @@ -242,17 +242,17 @@ public ConfigurationCollection Databases } /// - /// + /// /// [ConfigurationProperty(KeyGeneratorsElementName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof (ConfigurationCollection), AddItemName = "keyGenerator")] + [ConfigurationCollection(typeof (ConfigurationCollection), AddItemName = "keyGenerator")] public ConfigurationCollection KeyGenerators { get { return (ConfigurationCollection) this[KeyGeneratorsElementName]; } } /// - /// + /// /// [ConfigurationProperty(ServiceContainerTypeElementName, DefaultValue = null)] public string ServiceContainerType @@ -262,7 +262,7 @@ public string ServiceContainerType } /// - /// + /// /// [ConfigurationProperty(DefaultSchemaElementName)] public string DefaultSchema @@ -272,7 +272,7 @@ public string DefaultSchema } /// - /// + /// /// [ConfigurationProperty(DefaultDatabaseElementName)] public string DefaultDatabase @@ -282,7 +282,7 @@ public string DefaultDatabase } /// - /// + /// /// [ConfigurationProperty(IncludeSqlInExceptionsElementName, DefaultValue = DomainConfiguration.DefaultIncludeSqlInExceptions)] @@ -293,9 +293,10 @@ public bool IncludeSqlInExceptions } /// - /// + /// /// - [ConfigurationProperty(AllowCyclicDatabaseDependenciesElementName, DefaultValue = false)] + [ConfigurationProperty(AllowCyclicDatabaseDependenciesElementName, + DefaultValue = DomainConfiguration.DefaultAllowCyclicDatabaseDependencies)] public bool AllowCyclicDatabaseDependencies { get { return (bool) this[AllowCyclicDatabaseDependenciesElementName]; } @@ -303,7 +304,7 @@ public bool AllowCyclicDatabaseDependencies } /// - /// + /// /// [ConfigurationProperty(BuildInParallelElementName, DefaultValue = DomainConfiguration.DefaultBuildInParallel)] @@ -314,7 +315,7 @@ public bool BuildInParallel } /// - /// + /// /// [ConfigurationProperty(ForcedServerVersionElementName)] public string ForcedServerVersion @@ -324,7 +325,7 @@ public string ForcedServerVersion } /// - /// + /// /// [ConfigurationProperty(ConnectionInitializationSqlElementName)] public string ConnectionInitializationSql @@ -334,7 +335,7 @@ public string ConnectionInitializationSql } /// - /// + /// /// [ConfigurationProperty(IgnoreRulesElementName, IsDefaultCollection = false)] [ConfigurationCollection(typeof (ConfigurationCollection), AddItemName = "rule")] @@ -344,7 +345,7 @@ public ConfigurationCollection IgnoreRules } /// - /// + /// /// [ConfigurationProperty(MultidatabaseKeysElementName, DefaultValue = DomainConfiguration.DefaultMultidatabaseKeys)] @@ -355,7 +356,7 @@ public bool MultidatabaseKeys } /// - /// + /// /// [ConfigurationProperty(OptionsElementName, DefaultValue = "Default")] public string Options @@ -365,7 +366,7 @@ public string Options } /// - /// + /// /// [ConfigurationProperty(ShareStorageSchemaOverNodesElementName, DefaultValue = false)] public bool ShareStorageSchemaOverNodes @@ -375,7 +376,7 @@ public bool ShareStorageSchemaOverNodes } /// - /// + /// /// [ConfigurationProperty(VersioningConventionElementName)] public VersioningConventionElement VersioningConvention @@ -385,7 +386,7 @@ public VersioningConventionElement VersioningConvention } /// - /// + /// /// [ConfigurationProperty(EnsureConnectionIsAliveElementName, DefaultValue = true)] public bool EnsureConnectionIsAlive @@ -395,7 +396,7 @@ public bool EnsureConnectionIsAlive } /// - /// . + /// . /// [ConfigurationProperty(TagsLocationElementName, DefaultValue = "Default")] public string TagsLocation @@ -405,7 +406,7 @@ public string TagsLocation } /// - /// + /// /// [ConfigurationProperty(PreferTypeIdsAsQueryParametersElementName, DefaultValue = true)] public bool PreferTypeIdsAsQueryParameters diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Elements/IgnoreRuleElement.cs b/Orm/Xtensive.Orm/Orm/Configuration/Elements/IgnoreRuleElement.cs index 583c2f1bb8..de43975349 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/Elements/IgnoreRuleElement.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/Elements/IgnoreRuleElement.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2013 Xtensive LLC. +// Copyright (C) 2013-2024 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Alexey Kulakov @@ -31,7 +31,7 @@ public class IgnoreRuleElement : ConfigurationCollectionElementBase Index ?? string.Empty); /// - /// + /// /// [ConfigurationProperty(DatabaseElementName)] public string Database @@ -41,7 +41,7 @@ public string Database } /// - /// + /// /// [ConfigurationProperty(SchemaElementName)] public string Schema @@ -51,7 +51,7 @@ public string Schema } /// - /// + /// /// [ConfigurationProperty(TableElementName)] public string Table diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Elements/KeyGeneratorConfigurationElement.cs b/Orm/Xtensive.Orm/Orm/Configuration/Elements/KeyGeneratorConfigurationElement.cs index c05b08e5a3..9c256bf689 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/Elements/KeyGeneratorConfigurationElement.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/Elements/KeyGeneratorConfigurationElement.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2012 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2012-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov // Created: 2012.03.28 @@ -25,7 +25,7 @@ public override object Identifier { } /// - /// + /// /// [ConfigurationProperty(NameElementName, IsKey = true)] public string Name @@ -35,7 +35,7 @@ public string Name } /// - /// + /// /// [ConfigurationProperty(DatabaseElementName, IsKey = true)] public string Database @@ -45,7 +45,7 @@ public string Database } /// - /// + /// /// [ConfigurationProperty(SeedElementName, DefaultValue = 0L)] public long Seed @@ -55,7 +55,7 @@ public long Seed } /// - /// + /// /// [ConfigurationProperty(CacheSizeElementName, DefaultValue = (long) DomainConfiguration.DefaultKeyGeneratorCacheSize)] public long CacheSize diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Elements/MappingRuleElement.cs b/Orm/Xtensive.Orm/Orm/Configuration/Elements/MappingRuleElement.cs index 22cc92471d..a2c00732cf 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/Elements/MappingRuleElement.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/Elements/MappingRuleElement.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2012 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2012-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov // Created: 2012.02.07 @@ -25,7 +25,7 @@ public override object Identifier { } /// - /// + /// /// [ConfigurationProperty(AssemblyElementName, IsKey = true)] public string Assembly @@ -35,7 +35,7 @@ public string Assembly } /// - /// + /// /// [ConfigurationProperty(NamespaceElementName, IsKey = true)] public string Namespace @@ -45,7 +45,7 @@ public string Namespace } /// - /// + /// /// [ConfigurationProperty(DatabaseElementName)] public string Database @@ -55,7 +55,7 @@ public string Database } /// - /// + /// /// [ConfigurationProperty(SchemaElementName)] public string Schema diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Elements/NamingConventionElement.cs b/Orm/Xtensive.Orm/Orm/Configuration/Elements/NamingConventionElement.cs index 4e891a8f42..3c3c84670e 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/Elements/NamingConventionElement.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/Elements/NamingConventionElement.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2008-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Alexey Gamzov // Created: 2008.08.07 @@ -21,7 +21,7 @@ public class NamingConventionElement : ConfigurationElement private const string NamespaceSynonymsElementName = "namespaceSynonyms"; /// - /// + /// /// [ConfigurationProperty(LetterCasePolicyElementName, IsRequired = false, IsKey = false, DefaultValue = "Default")] public string LetterCasePolicy @@ -31,7 +31,7 @@ public string LetterCasePolicy } /// - /// + /// /// [ConfigurationProperty(NamespacePolicyElementName, IsRequired = false, IsKey = false, DefaultValue = "Default")] public string NamespacePolicy @@ -41,7 +41,7 @@ public string NamespacePolicy } /// - /// + /// /// [ConfigurationProperty(NamingRulesElementName, IsRequired = false, IsKey = false, DefaultValue = "Default")] public string NamingRules @@ -51,7 +51,7 @@ public string NamingRules } /// - /// + /// /// [ConfigurationProperty(NamespaceSynonymsElementName, IsRequired = false, IsKey = false)] [ConfigurationCollection(typeof (ConfigurationCollection), AddItemName = "synonym")] diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Elements/SessionConfigurationElement.cs b/Orm/Xtensive.Orm/Orm/Configuration/Elements/SessionConfigurationElement.cs index 3bab8025ec..1aaa1b7c05 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/Elements/SessionConfigurationElement.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/Elements/SessionConfigurationElement.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2008-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Aleksey Gamzov // Created: 2008.08.11 @@ -37,7 +37,7 @@ public class SessionConfigurationElement : ConfigurationCollectionElementBase public override object Identifier { get { return Name; } } /// - /// + /// /// [ConfigurationProperty(NameElementName, IsKey = true, DefaultValue = "Default")] public string Name { @@ -46,7 +46,7 @@ public string Name { } /// - /// + /// /// [ConfigurationProperty(UserNameElementName)] public string UserName { @@ -55,7 +55,7 @@ public string UserName { } /// - /// + /// /// [ConfigurationProperty(PasswordElementName)] public string Password { @@ -64,17 +64,18 @@ public string Password { } /// - /// + /// /// [ConfigurationProperty(CacheSizeElementName, DefaultValue = SessionConfiguration.DefaultCacheSize)] + [IntegerValidator(MinValue = 1, MaxValue = int.MaxValue)] public int CacheSize { get { return (int) this[CacheSizeElementName]; } set { this[CacheSizeElementName] = value; } } /// - /// + /// /// [ConfigurationProperty(CacheTypeElementName, DefaultValue = "Default")] public string CacheType { @@ -83,7 +84,7 @@ public string CacheType { } /// - /// + /// /// [ConfigurationProperty(OptionsElementName, DefaultValue = "Default")] public string Options { @@ -92,7 +93,7 @@ public string Options { } /// - /// + /// /// [ConfigurationProperty(IsolationLevelElementName, DefaultValue = "RepeatableRead")] public string DefaultIsolationLevel { @@ -101,7 +102,7 @@ public string DefaultIsolationLevel { } /// - /// + /// /// [ConfigurationProperty(CommandTimeoutElementName, DefaultValue = null)] public int? DefaultCommandTimeout { @@ -110,17 +111,18 @@ public int? DefaultCommandTimeout { } /// - /// + /// /// [ConfigurationProperty(BatchSizeElementName, DefaultValue = SessionConfiguration.DefaultBatchSize)] + [IntegerValidator(MinValue = 1, MaxValue = int.MaxValue)] public int BatchSize { get { return (int) this[BatchSizeElementName]; } set { this[BatchSizeElementName] = value; } } /// - /// + /// /// [ConfigurationProperty(ReaderPreloadingElementName, DefaultValue = "Default")] public string ReaderPreloading { @@ -129,7 +131,7 @@ public string ReaderPreloading { } /// - /// + /// /// [ConfigurationProperty(ServiceContainerTypeElementName, DefaultValue = null)] public string ServiceContainerType { @@ -138,10 +140,11 @@ public string ServiceContainerType { } /// - /// . + /// . /// [ConfigurationProperty(EntityChangeRegistrySizeElementName, DefaultValue = SessionConfiguration.DefaultEntityChangeRegistrySize)] + [IntegerValidator(MinValue = 1, MaxValue = int.MaxValue)] public int EntityChangeRegistrySize { get { return (int) this[EntityChangeRegistrySizeElementName]; } set { this[EntityChangeRegistrySizeElementName] = value; } diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Elements/VersioningConventionElement.cs b/Orm/Xtensive.Orm/Orm/Configuration/Elements/VersioningConventionElement.cs index 7cb63e06c3..ca4021dd89 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/Elements/VersioningConventionElement.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/Elements/VersioningConventionElement.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2018 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2018-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Alexey Kulakov // Created: 2018.03.14 @@ -15,7 +15,7 @@ public sealed class VersioningConventionElement : ConfigurationElement private const string DenyEntitySetOwnerVersionChangeElementName = "denyEntitySetOwnerVersionChange"; /// - /// + /// /// [ConfigurationProperty(EntityVersioningPolicyElementName, IsRequired = false, IsKey = false, DefaultValue = "Default")] public string EntityVersioningPolicy @@ -25,7 +25,7 @@ public string EntityVersioningPolicy } /// - /// + /// /// [ConfigurationProperty(DenyEntitySetOwnerVersionChangeElementName, IsRequired = false, IsKey = false, DefaultValue = false)] public bool DenyEntitySetOwnerVersionChange diff --git a/Orm/Xtensive.Orm/Orm/Configuration/ExtensionConfigurationCollection.cs b/Orm/Xtensive.Orm/Orm/Configuration/ExtensionConfigurationCollection.cs new file mode 100644 index 0000000000..ec6fee1c60 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/ExtensionConfigurationCollection.cs @@ -0,0 +1,135 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration +{ + /// + /// Collection of configurations of extensions. + /// + [Serializable] + public sealed class ExtensionConfigurationCollection : LockableBase, + IEnumerable + { + private Dictionary extensionConfigurations; + + /// + /// Number of configurations in collection. + /// + public long Count + { + [DebuggerStepThrough] + get => extensionConfigurations != null ? extensionConfigurations.Count : 0; + } + + /// + /// Gets configuration of certain type from collection. + /// + /// Type of configuration to get. + /// Found configuration or . + [DebuggerStepThrough] + public T Get() + where T : ConfigurationBase + { + return extensionConfigurations != null && extensionConfigurations.TryGetValue(typeof(T), out var result) + ? (T) result + : null; + } + + /// + /// Adds or replace configuration of certain type + /// + /// Type of configuration. + /// Configuration to add or to replace existing one. + [DebuggerStepThrough] + public void Set(T value) + where T : ConfigurationBase + { + EnsureNotLocked(); + ArgumentValidator.EnsureArgumentNotNull(value, nameof(value)); + + var extensionConfigurationType = typeof(T); + + if (extensionConfigurations == null) { + extensionConfigurations = new Dictionary(); + } + + extensionConfigurations[extensionConfigurationType] = value; + } + + /// + /// Clears the collection. + /// + public void Clear() + { + EnsureNotLocked(); + extensionConfigurations = null; + } + + /// + public override void Lock(bool recursive) + { + base.Lock(recursive); + if (extensionConfigurations != null) + foreach (var pair in extensionConfigurations) { + pair.Value.Lock(recursive); + } + } + + #region ICloneable methods + + /// + public object Clone() + { + return new ExtensionConfigurationCollection(this); + } + + #endregion + + #region IEnumerable methods + + /// + [DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator GetEnumerator() + { + return extensionConfigurations != null + ? extensionConfigurations.Values.GetEnumerator() + : Enumerable.Empty().GetEnumerator(); + } + + #endregion + + + // Constructors + + /// + /// Initializes new instance of this type. + /// + public ExtensionConfigurationCollection() + { + } + + /// + /// Initializes new instance of this type. + /// + /// The source to copy into this collection. + public ExtensionConfigurationCollection(ExtensionConfigurationCollection source) + : this() + { + ArgumentValidator.EnsureArgumentNotNull(source, nameof(source)); + if (source.Count == 0) + return; + extensionConfigurations = new Dictionary(source.extensionConfigurations); + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Interfaces/IConfigurationSectionReader{T}.cs b/Orm/Xtensive.Orm/Orm/Configuration/Interfaces/IConfigurationSectionReader{T}.cs new file mode 100644 index 0000000000..9227e82b2f --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Interfaces/IConfigurationSectionReader{T}.cs @@ -0,0 +1,55 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + + +using Microsoft.Extensions.Configuration; + +namespace Xtensive.Orm.Configuration +{ + /// + /// Reads certain type of configuration (DomainConfiguration, LoggingConfiguration, etc.) from IConfigurationSection API + /// + /// + public interface IConfigurationSectionReader + { + /// + /// Reads configuration from given configuration section. + /// + /// Root configuration section where specific configuration is placed (for domain configuration - where all domain configurations). + /// Instance of configuration. + TConfiguration Read(IConfigurationSection configurationSection); + + /// + /// Reads configuration from given configuration section with specified name (if named configuration supported e.g. doman configuration) + /// + /// Root configuration section where specific configuration is placed (for domain configuration - where all domain configurations). + /// Name of configuration. + /// Instance of configuration. + TConfiguration Read(IConfigurationSection configurationSection, string nameOfConfiguration); + + /// + /// Reads configuration (with default name if name is required) from default section (and with default name) from given configuration root. + /// + /// Root of all configuration sections. + /// Instance of configuration. + TConfiguration Read(IConfigurationRoot configurationRoot); + + /// + /// Reads configuration (with default name if name is required) from given section of given configuration root. + /// + /// Root of all configuration sections. + /// Specific section name from which configuration should be read. + /// Instance of configuration. + TConfiguration Read(IConfigurationRoot configurationRoot, string sectionName); + + /// + /// Reads configuration with given name from given section of given configuration root. + /// + /// Root of all configuration sections. + /// Specific section name from which configuration should be read. + /// Name of configuration. + /// Instance of configuration. + TConfiguration Read(IConfigurationRoot configurationRoot, string sectionName, string nameOfConfiguration); + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Internals/ConfigurationSectionExtensions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Internals/ConfigurationSectionExtensions.cs new file mode 100644 index 0000000000..35f4c1d3e5 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Internals/ConfigurationSectionExtensions.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Xtensive.Collections; + +namespace Xtensive.Orm.Configuration +{ + internal static class ConfigurationSectionExtensions + { + public static IEnumerable GetSelfOrChildren(this IConfigurationSection section, bool requiredName = false) + { + var children = section.GetChildren().ToList(); + if (children.Count > 0) { + if (requiredName) { + var anyItemWithName = children.Any(i => i["name"] != null); + if (anyItemWithName) { + return children; + } + } + var firstChild = children[0]; + var isIndexed = firstChild.Key == "0"; + if (isIndexed) + return children; + else + return EnumerableUtils.One(section); + } + return children; + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Internals/ConnectionInfoParser.cs b/Orm/Xtensive.Orm/Orm/Configuration/Internals/ConnectionInfoParser.cs index 452d684ab9..908bd3bbfc 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/Internals/ConnectionInfoParser.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/Internals/ConnectionInfoParser.cs @@ -5,17 +5,61 @@ // Created: 2013.09.27 using System; +using System.Collections; +using System.Collections.Generic; using System.Configuration; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Configuration; using Xtensive.Core; namespace Xtensive.Orm.Configuration { internal static class ConnectionInfoParser { + private readonly struct UnifiedAccessor + { + private readonly IDictionary connectionStringsAsDict; + private readonly ConnectionStringSettingsCollection connectionStrings; + + public string this[string key] { + get { + if (connectionStrings != null) + return connectionStrings[key]?.ConnectionString; + else if(connectionStringsAsDict!=null) + return connectionStringsAsDict[key]; + return null; + } + } + + public UnifiedAccessor(ConnectionStringSettingsCollection oldConnectionStrings) + { + connectionStringsAsDict = null; + connectionStrings = oldConnectionStrings; + } + public UnifiedAccessor(IDictionary connectionStrings) + { + connectionStringsAsDict = connectionStrings; + this.connectionStrings = null; + } + } + public static ConnectionInfo GetConnectionInfo(System.Configuration.Configuration configuration,string connectionUrl, string provider, string connectionString) { - bool connectionUrlSpecified = !string.IsNullOrEmpty(connectionUrl); - bool connectionStringSpecified = !string.IsNullOrEmpty(connectionString) && !string.IsNullOrEmpty(provider); + var accessor = new UnifiedAccessor(configuration.ConnectionStrings.ConnectionStrings); + return GetConnectionInfoInternal(accessor, connectionUrl, provider, connectionString); + } + + public static ConnectionInfo GetConnectionInfo(IDictionary connectionStrings, string connectionUrl, string provider, string connectionString) + { + var accessor = new UnifiedAccessor(connectionStrings); + return GetConnectionInfoInternal(accessor, connectionUrl, provider, connectionString); + } + + private static ConnectionInfo GetConnectionInfoInternal(in UnifiedAccessor connectionStringAccessor, + string connectionUrl, string provider, string connectionString) + { + var connectionUrlSpecified = !string.IsNullOrEmpty(connectionUrl); + var connectionStringSpecified = !string.IsNullOrEmpty(connectionString) && !string.IsNullOrEmpty(provider); if (connectionUrlSpecified && connectionStringSpecified) throw new InvalidOperationException(Strings.ExConnectionInfoIsWrongYouShouldSetEitherConnectionUrlElementOrProviderAndConnectionStringElements); @@ -24,30 +68,31 @@ public static ConnectionInfo GetConnectionInfo(System.Configuration.Configuratio return new ConnectionInfo(connectionUrl); if (connectionStringSpecified) - return new ConnectionInfo(provider, ExpandConnectionString(configuration, connectionString)); + return new ConnectionInfo(provider, + ExpandConnectionString(connectionStringAccessor, connectionString)); // Neither connection string, nor connection url specified. // Leave connection information undefined. return null; } - private static string ExpandConnectionString(System.Configuration.Configuration configuration, string connectionString) + private static string ExpandConnectionString(in UnifiedAccessor connectionStrings, string connectionString) { const string prefix = "#"; if (!connectionString.StartsWith(prefix, StringComparison.Ordinal)) return connectionString; - string connectionStringName = connectionString.Substring(prefix.Length); + var connectionStringName = connectionString[prefix.Length..]; - var connectionStringSetting = configuration.ConnectionStrings.ConnectionStrings[connectionStringName]; - if (connectionStringSetting==null) + var connectionStringSetting = connectionStrings[connectionStringName]; + if (connectionStringSetting == null) throw new InvalidOperationException(string.Format(Strings.ExConnectionStringWithNameXIsNotFound, connectionStringName)); - if (string.IsNullOrEmpty(connectionStringSetting.ConnectionString)) + if (string.IsNullOrEmpty(connectionStringSetting)) throw new InvalidOperationException(string.Format(Strings.ExConnectionStringWithNameXIsNullOrEmpty, connectionStringName)); - return connectionStringSetting.ConnectionString; + return connectionStringSetting; } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Internals/DomainConfigurationReaders.cs b/Orm/Xtensive.Orm/Orm/Configuration/Internals/DomainConfigurationReaders.cs new file mode 100644 index 0000000000..73af7f4711 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Internals/DomainConfigurationReaders.cs @@ -0,0 +1,399 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Xtensive.Core; +using Xtensive.Orm.Configuration.Options; + +namespace Xtensive.Orm.Configuration +{ + internal sealed class XmlToDomainConfigurationReader : DomainConfigurationReader + { + protected override bool ValidateCorrectFormat(IConfigurationSection allDomainsSection, string domainName) + { + return !allDomainsSection.GetSection(domainName).GetChildren().Any() + && allDomainsSection.GetSection(string.Format(NamedDomainTemplate, domainName)).GetChildren().Any(); + } + + protected override IConfigurationSection GetDomainSection(IConfigurationSection allDomainsSection, string domainName) => + allDomainsSection.GetSection(string.Format(NamedDomainTemplate, domainName)); + + protected override void ProcessNamingConvention(IConfigurationSection namingConventionSection, DomainConfigurationOptions domainConfiguratonOptions) + { + if (namingConventionSection == null) { + domainConfiguratonOptions.NamingConventionRaw = null; + return; + } + + if (namingConventionSection.GetChildren().Any()) { + var namingConvetion = namingConventionSection.Get(); + if (namingConvetion!= null) { + var synonymsSection = namingConventionSection.GetSection(NamespaceSynonymSectionName); + var synonyms = synonymsSection != null && synonymsSection.GetChildren().Any() + ? synonymsSection.GetSection(SynonymElementName) + .GetSelfOrChildren() + .Select(s => s.Get()) + .Where(ns => ns != null) + .ToArray() + : Array.Empty(); + + namingConvetion.NamespaceSynonyms = synonyms; + domainConfiguratonOptions.NamingConventionRaw = namingConvetion; + } + else { + domainConfiguratonOptions.NamingConventionRaw = null; + } + } + } + + protected override void ProcessVersioningConvention(IConfigurationSection versioningConventionSection, DomainConfigurationOptions domainConfiguratonOptions) + { + if (versioningConventionSection == null) { + domainConfiguratonOptions.VersioningConvention = null; + return; + } + + if (versioningConventionSection.GetChildren().Any()) { + var versioningConvention = versioningConventionSection.Get(); + + domainConfiguratonOptions.VersioningConvention = versioningConvention != null + ? versioningConvention + : null; + } + } + + protected override void ProcessTypes(IConfigurationSection typesSection, DomainConfigurationOptions domainConfigurationOptions) + { + if (typesSection == null) { + domainConfigurationOptions.Types = Array.Empty(); + return; + } + if (TryProcessTypeRegistrationsWithAttributes(typesSection, domainConfigurationOptions) + || TryProcessTypeRegistrationsWithNodes(typesSection, domainConfigurationOptions)) { + return; + } + + domainConfigurationOptions.Types = Array.Empty(); + } + + private bool TryProcessTypeRegistrationsWithAttributes(IConfigurationSection typesSection, + DomainConfigurationOptions domainConfigurationOptions) + { + var registrations = typesSection.GetSection(OldStyleTypeRegistrationElementName); + if (registrations != null && registrations.GetChildren().Any()) { + domainConfigurationOptions.Types = registrations + .GetSelfOrChildren() + .Select(s => s.Get()) + .Where(tr => tr != null) + .ToArray(); + return true; + } + return false; + } + + private bool TryProcessTypeRegistrationsWithNodes(IConfigurationSection typesSection, + DomainConfigurationOptions domainConfigurationOptions) + { + var registrations = typesSection.GetSection(TypeRegistrationElementName); + if (registrations == null) + return false; + + domainConfigurationOptions.Types = registrations + .GetSelfOrChildren() + .Select(s => s.Get()) + .Where(tr => tr != null) + .ToArray(); + return true; + } + + protected override void ProcessDatabases(IConfigurationSection databasesSection, DomainConfigurationOptions domainConfiguratonOptions) + { + domainConfiguratonOptions.Databases.Clear(); + if (databasesSection == null) + return; + var databaseElement = databasesSection.GetSection(DatabaseElementName); + if (databaseElement == null) + return; + foreach (var section in databaseElement.GetSelfOrChildren(true)) { + var dbItem = section.Get(); + if (dbItem == null) + continue; + domainConfiguratonOptions.Databases.Add(dbItem.Name, dbItem); + } + } + + protected override void ProcessKeyGenerators(IConfigurationSection keyGeneratorsSection, DomainConfigurationOptions domainConfiguratonOptions) + { + domainConfiguratonOptions.KeyGenerators.Clear(); + ProcessCollectionOfOptions(keyGeneratorsSection, KeyGeneratorElementName, domainConfiguratonOptions.KeyGenerators); + } + + protected override void ProcessIgnoreRules(IConfigurationSection ignoreRulesSection, Options.DomainConfigurationOptions domainConfiguratonOptions) + { + domainConfiguratonOptions.IgnoreRules.Clear(); + ProcessCollectionOfOptions(ignoreRulesSection, RuleElementName, domainConfiguratonOptions.IgnoreRules); + } + + protected override void ProcessMappingRules(IConfigurationSection mappingRulesSection, Options.DomainConfigurationOptions domainConfiguratonOptions) + { + domainConfiguratonOptions.MappingRules.Clear(); + ProcessCollectionOfOptions(mappingRulesSection, RuleElementName, domainConfiguratonOptions.MappingRules); + } + + protected override void ProcessSessions(IConfigurationSection sessionsSection, Options.DomainConfigurationOptions domainConfiguratonOptions) + { + domainConfiguratonOptions.Sessions.Clear(); + if (sessionsSection == null) + return; + var sessionElement = sessionsSection.GetSection(SessionElementName); + if (sessionElement == null) + return; + foreach (var section in sessionElement.GetSelfOrChildren(true)) { + var sessionItem = section.Get(); + if (sessionItem == null) + continue; + domainConfiguratonOptions.Sessions.Add(sessionItem.Name, sessionItem); + } + } + + private void ProcessCollectionOfOptions(IConfigurationSection collectionSection, string itemKey, OptionsCollection collection) + where TOption : class, IIdentifyableOptions + { + if (collectionSection == null) + return; + var collectionElement = collectionSection.GetSection(itemKey); + if (collectionElement == null) + return; + foreach (var item in collectionElement.GetSelfOrChildren()) { + var optItem = item.Get(); + collection.Add(optItem); + } + } + } + + internal sealed class JsonToDomainConfigurationReader : DomainConfigurationReader + { + protected override bool ValidateCorrectFormat(IConfigurationSection allDomainsSection, string domainName) + { + return allDomainsSection.GetSection(domainName).GetChildren().Any() + && !allDomainsSection.GetSection(string.Format(NamedDomainTemplate, domainName)).GetChildren().Any(); + } + + protected override IConfigurationSection GetDomainSection(IConfigurationSection allDomainsSection, string domainName) => + allDomainsSection.GetSection(domainName); + + protected override void ProcessNamingConvention(IConfigurationSection namingConventionSection, DomainConfigurationOptions domainConfiguratonOptions) + { + if (namingConventionSection == null || !namingConventionSection.GetChildren().Any()) + return; + var jsonListVariant = namingConventionSection.Get(); + if (jsonListVariant.NamespaceSynonyms == null) + jsonListVariant.NamespaceSynonyms = Array.Empty(); + domainConfiguratonOptions.NamingConventionRaw = jsonListVariant; + } + } + + internal abstract class DomainConfigurationReader : IConfigurationSectionReader + { + protected sealed class ConfigurationParserContext + { + public readonly IConfigurationRoot CurrentConfiguration; + + public readonly IConfigurationSection CurrentSection; + + public readonly string SectionName; + + public readonly IDictionary ConnectionStrings; + + public ConfigurationParserContext(IConfigurationRoot currentConfiguration, string sectionName) + { + CurrentConfiguration = currentConfiguration; + CurrentSection = currentConfiguration.GetSection(sectionName); + ConnectionStrings = currentConfiguration.Get>(); + } + + public ConfigurationParserContext(IConfigurationSection currentSection, IDictionary connectionStrings) + { + CurrentSection = currentSection; + ConnectionStrings = connectionStrings; + } + } + + protected const string RuleElementName = "Rule"; + protected const string KeyGeneratorElementName = "KeyGenerator"; + protected const string OldStyleTypeRegistrationElementName = "Add"; + protected const string TypeRegistrationElementName = "Registration"; + protected const string SynonymElementName = "Synonym"; + protected const string DatabaseElementName = "Database"; + protected const string SessionElementName = "Session"; + + protected const string NamingConventionSectionName = "NamingConvention"; + protected const string VersioningConventionSectionName = "VersioningConvention"; + protected const string TypesSectionName = "Types"; + protected const string DatabasesSectionName = "Databases"; + protected const string KeyGeneratorsSectionName = "KeyGenerators"; + protected const string IgnoreRulesSectionName = "IgnoreRules"; + protected const string MappingRulesSectionName = "MappingRules"; + protected const string SessionsSectionName = "Sessions"; + protected const string NamespaceSynonymSectionName = "NamespaceSynonyms"; + protected const string DomainsSectionName = "Domains"; + + protected const string NamedDomainTemplate = "Domain:{0}"; + + /// + /// Gets domain configuration with name "Default" from default section. + /// + /// Configration root. + /// DomainConfiguration instance if it was read successfully, otherwise, . + public DomainConfiguration Read(IConfigurationRoot configurationRoot) => + Read(configurationRoot, WellKnown.DefaultConfigurationSection, WellKnown.DefaultDomainConfigurationName); + + /// + /// Gets domain configuration with name "Default" from . + /// + /// Configration root + /// Custom section name where domains are placed. + /// DomainConfiguration instance if it was read successfully, otherwise, . + public DomainConfiguration Read(IConfigurationRoot configurationRoot, string sectionName) => + Read(configurationRoot, sectionName, WellKnown.DefaultDomainConfigurationName); + + /// + /// Gets domain configuration with given name from . + /// + /// Configration root. + /// Custom section name where domains are placed. + /// Name of domain configuration. + /// DomainConfiguration instance if it was read successfully, otherwise, . + public DomainConfiguration Read(IConfigurationRoot configurationRoot, string sectionName, string nameOfConfiguration) + { + var allDomainsSection = configurationRoot.GetSection(sectionName).GetSection(DomainsSectionName); + if (allDomainsSection == null || !allDomainsSection.GetChildren().Any()) + return null; + + var domainConfigurationSection = GetDomainSection(allDomainsSection, nameOfConfiguration); + if (domainConfigurationSection == null || !domainConfigurationSection.GetChildren().Any()) + return null; + + var connectionStrings = configurationRoot.GetSection("ConnectionStrings").Get>(); + var context = new ConfigurationParserContext(domainConfigurationSection, connectionStrings); + + return ReadInternal(context); + } + + /// + /// Gets domain configuration with name "Default" from . + /// + /// Root section where all domain configurations stored, by default + /// DomainConfiguration instance if it was read successfully, otherwise, . + public DomainConfiguration Read(IConfigurationSection rootSection) => + Parse(rootSection, WellKnown.DefaultDomainConfigurationName, null); + + /// + /// Gets domain configuration with given name from . + /// + /// Root section where all domain configurations stored, by default + /// Name of domain configuration. + /// DomainConfiguration instance if it was read successfully, otherwise, . + public DomainConfiguration Read(IConfigurationSection rootSection, string nameOfConfiguration) => + Parse(rootSection, nameOfConfiguration, null); + + /// + /// Gets domain configuration with name "Default" from . + /// + /// Root section where all domain configurations stored, by default + /// + /// Connection strings in form of dictionary of strings. Required if connection string alias is used in domain configuration connection settings + /// + /// DomainConfiguration instance if it was read successfully, otherwise, . + public DomainConfiguration Parse(IConfigurationSection rootSection, Dictionary connectionStrings) + => Parse(rootSection, WellKnown.DefaultDomainConfigurationName, connectionStrings); + + /// + /// Gets domain configuration with given name from . + /// + /// Root section where all domain configurations stored, by default + /// Name of domain configuration. + /// + /// Connection strings in form of dictionary of strings. Required if connection string alias is used in domain configuration connection settings + /// + /// DomainConfiguration instance if it was read successfully, otherwise, . + public DomainConfiguration Parse(IConfigurationSection rootSection, string nameOfConfiguration, Dictionary connectionStrings) + { + var allDomainsSection = rootSection.GetSection(DomainsSectionName); + if (allDomainsSection == null || !allDomainsSection.GetChildren().Any()) + return null; + + var domainConfigurationSection = GetDomainSection(allDomainsSection, nameOfConfiguration); + if (domainConfigurationSection == null || !domainConfigurationSection.GetChildren().Any()) + return null; + + var context = new ConfigurationParserContext(domainConfigurationSection, connectionStrings); + return ReadInternal(context); + } + + protected abstract bool ValidateCorrectFormat(IConfigurationSection allDomainsSection, string domainName); + + protected abstract IConfigurationSection GetDomainSection(IConfigurationSection allDomainsSection, string domainName); + + private DomainConfiguration ReadInternal(ConfigurationParserContext context) + { + var domainByNameSection = context.CurrentSection; + if (domainByNameSection == null || !domainByNameSection.GetChildren().Any()) { + return null; + } + // this handles only root properties of domain configuration + var domainConfigurationOptions = context.CurrentSection.Get(); + if (domainConfigurationOptions == null) { + return null; + } + + // all sub-items require manual reading; + ProcessNamingConvention(domainByNameSection.GetSection(NamingConventionSectionName), domainConfigurationOptions); + ProcessVersioningConvention(domainByNameSection.GetSection(VersioningConventionSectionName), domainConfigurationOptions); + ProcessTypes(domainByNameSection.GetSection(TypesSectionName), domainConfigurationOptions); + ProcessDatabases(domainByNameSection.GetSection(DatabasesSectionName), domainConfigurationOptions); + ProcessKeyGenerators(domainByNameSection.GetSection(KeyGeneratorsSectionName), domainConfigurationOptions); + ProcessIgnoreRules(domainByNameSection.GetSection(IgnoreRulesSectionName), domainConfigurationOptions); + ProcessMappingRules(domainByNameSection.GetSection(MappingRulesSectionName), domainConfigurationOptions); + ProcessSessions(domainByNameSection.GetSection(SessionsSectionName), domainConfigurationOptions); + + return domainConfigurationOptions.ToNative(context.ConnectionStrings); + } + + protected virtual void ProcessNamingConvention(IConfigurationSection namingConventionSection, DomainConfigurationOptions domainConfiguratonOptions) + { + } + + protected virtual void ProcessVersioningConvention(IConfigurationSection versioningConventionSection, DomainConfigurationOptions domainConfiguratonOptions) + { + } + + protected virtual void ProcessTypes(IConfigurationSection typesSection, DomainConfigurationOptions domainConfigurationOptions) + { + } + + protected virtual void ProcessDatabases(IConfigurationSection databasesSection, DomainConfigurationOptions domainConfiguratonOptions) + { + } + + protected virtual void ProcessKeyGenerators(IConfigurationSection keyGeneratorsSection, DomainConfigurationOptions domainConfiguratonOptions) + { + } + + protected virtual void ProcessIgnoreRules(IConfigurationSection ignoreRulesSection, DomainConfigurationOptions domainConfiguratonOptions) + { + } + + protected virtual void ProcessMappingRules(IConfigurationSection mappingRulesSection, DomainConfigurationOptions domainConfiguratonOptions) + { + } + + protected virtual void ProcessSessions(IConfigurationSection sessionsSection, DomainConfigurationOptions domainConfiguratonOptions) + { + } + } +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Internals/LoggingConfigurationReader.cs b/Orm/Xtensive.Orm/Orm/Configuration/Internals/LoggingConfigurationReader.cs new file mode 100644 index 0000000000..6308dc9fde --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Internals/LoggingConfigurationReader.cs @@ -0,0 +1,98 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + + +using System; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration +{ + internal sealed class LoggingConfigurationReader : IConfigurationSectionReader + { + private const string LoggingSectionName = "Logging"; + private const string LogsSectionName = "Logs"; + + private const string ProviderElementName = "Provider"; + private const string LogElementName = "Log"; + private const string LogSourceElementName = "Source"; + private const string LogTargerElementName = "Target"; + + /// + public LoggingConfiguration Read(IConfigurationRoot configurationRoot, string sectionName) + { + ArgumentValidator.EnsureArgumentNotNull(configurationRoot, nameof(configurationRoot)); + ArgumentValidator.EnsureArgumentNotNullOrEmpty(sectionName, nameof(sectionName)); + + return Read(configurationRoot.GetSection(sectionName)); + } + + /// + public LoggingConfiguration Read(IConfigurationRoot configurationRoot) + { + ArgumentValidator.EnsureArgumentNotNull(configurationRoot, nameof(configurationRoot)); + + return Read(configurationRoot.GetSection(WellKnown.DefaultConfigurationSection)); + } + + /// + public LoggingConfiguration Read(IConfigurationRoot configurationRoot, string sectionName, string nameOfConfiguration) + { + ArgumentValidator.EnsureArgumentNotNull(configurationRoot, nameof(configurationRoot)); + ArgumentValidator.EnsureArgumentNotNullOrEmpty(sectionName, nameof(sectionName)); + + return Read(configurationRoot.GetSection(sectionName)); + } + + /// + public LoggingConfiguration Read(IConfigurationSection configurationSection) + { + ArgumentValidator.EnsureArgumentNotNull(configurationSection, nameof(configurationSection)); + + var ormConfigurationSection = configurationSection; + + var loggingSection = ormConfigurationSection.GetSection(LoggingSectionName); + + if (loggingSection != null && loggingSection.GetChildren().Any()) { + var provider = loggingSection.GetSection(ProviderElementName)?.Value; + var logsSection = loggingSection.GetSection(LogsSectionName); + IConfigurationSection logElement; + + if (logsSection != null && logsSection.GetChildren().Any()) { + logElement = logsSection.GetSection(LogElementName); + if (logElement == null || !logElement.GetChildren().Any()) { + logElement = logsSection; + } + } + else { + logElement = loggingSection.GetSection(LogElementName); + } + + var configuration = new LoggingConfiguration(provider); + foreach (var logItem in logElement.GetSelfOrChildren()) { + var source = logItem.GetSection(LogSourceElementName).Value; + var target = logItem.GetSection(LogTargerElementName).Value; + if (source.IsNullOrEmpty() || target.IsNullOrEmpty()) + throw new InvalidOperationException(); + configuration.Logs.Add(new LogConfiguration(source, target)); + } + + if (configuration.Provider == null && configuration.Logs.Count==0) + throw new InvalidOperationException(); + + return configuration; + } + + throw new InvalidOperationException( + string.Format(Strings.ExSectionIsNotFoundInApplicationConfigurationFile, WellKnown.DefaultConfigurationSection)); + } + + /// + public LoggingConfiguration Read(IConfigurationSection configurationSection, string nameOfConfiguration) + { + throw new NotSupportedException(); + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/LogConfiguration.cs b/Orm/Xtensive.Orm/Orm/Configuration/LogConfiguration.cs index 79fdbea29e..8326636011 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/LogConfiguration.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/LogConfiguration.cs @@ -1,25 +1,42 @@ -// Copyright (C) 2013 Xtensive LLC. +// Copyright (C) 2013 Xtensive LLC. // All rights reserved. // For conditions of distribution and use, see license. // Created by: Alexey Kulakov // Created: 2013.09.17 +using Xtensive.Core; + namespace Xtensive.Orm.Configuration { /// /// Configuration of log. /// - public class LogConfiguration + public class LogConfiguration : LockableBase { + private string source; + private string target; + /// /// Gets or sets source or sources of log separated by comma. /// - public string Source { get; set; } + public string Source { + get => source; + set { + EnsureNotLocked(); + source = value; + } + } /// /// Gets or sets targer of log. /// - public string Target { get; set; } + public string Target { + get => target; + set { + EnsureNotLocked(); + target = value; + } + } /// /// Creates new instance of this class @@ -28,8 +45,8 @@ public class LogConfiguration /// Targer for new log public LogConfiguration(string source, string target) { - Source = source; - Target = target; + this.source = source; + this.target = target; } } } diff --git a/Orm/Xtensive.Orm/Orm/Configuration/LoggingConfiguration.cs b/Orm/Xtensive.Orm/Orm/Configuration/LoggingConfiguration.cs index 8975e4e86f..ed8e7cbaac 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/LoggingConfiguration.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/LoggingConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2020 Xtensive LLC. +// Copyright (C) 2013-2024 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Alexey Kulakov @@ -7,6 +7,8 @@ using System; using System.Collections.Generic; using System.Configuration; +using System.Linq; +using Microsoft.Extensions.Configuration; using Xtensive.Core; using ConfigurationSection = Xtensive.Orm.Configuration.Elements.ConfigurationSection; @@ -15,17 +17,59 @@ namespace Xtensive.Orm.Configuration /// /// Configuration of logging /// - public sealed class LoggingConfiguration + public sealed class LoggingConfiguration : ConfigurationBase { + private string provider; + private IList logs; + /// /// Gets or sets external provider. Provider's name specified as assembly qualified name. /// - public string Provider { get; set; } + public string Provider { + get => provider; + set { + EnsureNotLocked(); + provider = value; + } + } /// /// Gets or sets list of /// - public IList Logs { get; set; } + public IList Logs { + get => logs; + set { EnsureNotLocked(); logs = value; } + } + + public override void Lock(bool recursive) + { + if (logs is ListnativeList) { + logs = nativeList.AsReadOnly(); + } + else { + logs = logs.ToList().AsReadOnly(); + } + base.Lock(recursive); + + foreach (var log in logs) { + log.Lock(recursive); + } + } + + /// + protected override LoggingConfiguration CreateClone() => new LoggingConfiguration(); + + /// + protected override void CopyFrom(ConfigurationBase source) + { + base.CopyFrom(source); + var configuration = source as LoggingConfiguration; + Logs = new List(configuration.Logs); + Provider = configuration.Provider; + } + + /// + public override LoggingConfiguration Clone() => (LoggingConfiguration) base.Clone(); /// /// Loads logging configuration from the default configuration section. @@ -43,7 +87,7 @@ public static LoggingConfiguration Load() /// Loaded configuration. public static LoggingConfiguration Load(string sectionName) { - ArgumentValidator.EnsureArgumentNotNullOrEmpty(sectionName, "sectionName"); + ArgumentValidator.EnsureArgumentNotNullOrEmpty(sectionName, nameof(sectionName)); var section = (ConfigurationSection)ConfigurationManager.GetSection(sectionName); if (section==null) @@ -70,8 +114,8 @@ public static LoggingConfiguration Load(System.Configuration.Configuration confi /// Loaded configuration. public static LoggingConfiguration Load(System.Configuration.Configuration configuration, string sectionName) { - ArgumentValidator.EnsureArgumentNotNull(configuration, "configuration"); - ArgumentValidator.EnsureArgumentNotNullOrEmpty(sectionName, "sectionName"); + ArgumentValidator.EnsureArgumentNotNull(configuration, nameof(configuration)); + ArgumentValidator.EnsureArgumentNotNullOrEmpty(sectionName, nameof(sectionName)); var section = (ConfigurationSection) configuration.GetSection(sectionName); if (section==null) @@ -80,12 +124,46 @@ public static LoggingConfiguration Load(System.Configuration.Configuration confi return loggingConfiguration; } + /// + /// Loads logging configuration from the specified configuration section. + /// + /// Root configuration section where logging configuration is placed. + /// Logging configuration. + /// The logging section is not found. + public static LoggingConfiguration Load(IConfigurationSection configurationSection) + { + return new LoggingConfigurationReader().Read(configurationSection); + } + + /// + /// Loads logging configuration from default section. + /// + /// Root of configuration. + /// Read configuration. + /// The logging section is not found. + public static LoggingConfiguration Load(IConfigurationRoot configurationRoot) + { + return new LoggingConfigurationReader().Read(configurationRoot); + } + + /// + /// Loads logging configuration from specific section. + /// + /// Root of configuration. + /// Section name where logging configuration is defined. + /// Read configuration. + /// The logging section is not found. + public static LoggingConfiguration Load(IConfigurationRoot configurationRoot, string sectionName) + { + return new LoggingConfigurationReader().Read(configurationRoot, sectionName); + } + /// /// Creates instance of this class. /// public LoggingConfiguration() { - Logs = new List(); + logs = new List(); } /// @@ -94,8 +172,8 @@ public LoggingConfiguration() /// External provider for logging. Provider's name specified as assembly qualified name. public LoggingConfiguration(string provider) { - Provider = provider; - Logs = new List(); + this.provider = provider; + logs = new List(); } } } diff --git a/Orm/Xtensive.Orm/Orm/Configuration/NamingConvention.cs b/Orm/Xtensive.Orm/Orm/Configuration/NamingConvention.cs index 461b274273..5a71e0f53b 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/NamingConvention.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/NamingConvention.cs @@ -18,6 +18,10 @@ namespace Xtensive.Orm.Configuration public class NamingConvention : LockableBase, ICloneable { + public const LetterCasePolicy DefaultLetterCasePolicy = LetterCasePolicy.Default; + public const NamespacePolicy DefaultNamespacePolicy = NamespacePolicy.Default; + public const NamingRules DefaultNamingRules = NamingRules.Default; + private LetterCasePolicy letterCasePolicy; private NamespacePolicy namespacePolicy; private NamingRules namingRules; diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/DatabaseOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/DatabaseOptions.cs new file mode 100644 index 0000000000..e609e57754 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/DatabaseOptions.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using Xtensive.Core; +using Xtensive.Orm.Model; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class DatabaseOptions : IIdentifyableOptions, + IValidatableOptions, + IToNativeConvertable, + INamedOptionsCollectionElement + { + public object Identifier => Name; + + /// + /// Logical database name. + /// + public string Name { get; set; } + + /// + /// Physical database name. + /// + public string RealName { get; set; } + + /// + /// Type ID minimal value + /// for types mapped to this database. + /// Default value is . + /// + public int MinTypeId { get; set; } = TypeInfo.MinTypeId; + + /// + /// Type ID maximal value + /// for types mapped to this database. + /// Default value is . + /// + public int MaxTypeId { get; set; } = int.MaxValue; + + /// + /// is empty or null. + /// or is not in valid range. + public void Validate() + { + if (Name.IsNullOrEmpty()) + throw new ArgumentException(Strings.ExArgumentCannotBeEmptyString,"Name"); + if (MinTypeId < TypeInfo.MinTypeId) + throw new ArgumentOutOfRangeException("MinTypeId", MinTypeId, + string.Format(Strings.ExArgumentMustBeGreaterThatOrEqualX, TypeInfo.MinTypeId)); + if (MaxTypeId < TypeInfo.MinTypeId) + throw new ArgumentOutOfRangeException("MaxTypeId", MinTypeId, + string.Format(Strings.ExArgumentMustBeGreaterThatOrEqualX, TypeInfo.MinTypeId)); + } + + /// + public DatabaseConfiguration ToNative() + { + Validate(); + + return new DatabaseConfiguration(Name) { + RealName = RealName, + MinTypeId = MinTypeId, + MaxTypeId = MaxTypeId, + }; + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/DomainConfigurationOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/DomainConfigurationOptions.cs new file mode 100644 index 0000000000..fa29acef41 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/DomainConfigurationOptions.cs @@ -0,0 +1,311 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class DomainConfigurationOptions + { + /// + /// Domain configuration name. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Url that will be used in . + /// + public string ConnectionUrl { get; set; } = null; + + /// + /// Connection string that will be used in . + /// + public string ConnectionString { get; set; } = null; + + /// + /// Provider that will be used in . + /// + public string Provider { get; set; } = WellKnown.Provider.SqlServer; + + /// + /// Types tha are about to be registered in . + /// + public TypeRegistrationOptions[] Types { get; set; } = Array.Empty(); + + /// + /// Size of the key cache. Default value is + /// + public int KeyCacheSize { get; set; } = DomainConfiguration.DefaultKeyCacheSize; + + /// + /// Size of the key generator cache size. + /// Default value is + /// + public int KeyGeneratorCacheSize { get; set; } = DomainConfiguration.DefaultKeyGeneratorCacheSize; + + /// + /// Size of the query cache (see ). + /// Default value is . + /// + public int QueryCacheSize { get; set; } = DomainConfiguration.DefaultQueryCacheSize; + + /// + /// Size of the record set mapping cache. + /// Default value is . + /// + public int RecordSetMappingCacheSize { get; set; } = DomainConfiguration.DefaultRecordSetMappingCacheSize; + + /// + /// Domain upgrade behavior. + /// + public DomainUpgradeMode UpgradeMode { get; set; } = DomainConfiguration.DefaultUpgradeMode; + + /// + /// format. + /// + public SchemaSyncExceptionFormat SchemaSyncExceptionFormat { get; set; } = DomainConfiguration.DefaultSchemaSyncExceptionFormat; + + /// + /// Foreign key mode. + /// Default value is . + /// + public ForeignKeyMode ForeignKeyMode { get; set; } = DomainConfiguration.DefaultForeignKeyMode; + + /// + /// Change tracking mode of full-text indexes. + /// Default value is . + /// + public FullTextChangeTrackingMode FullTextChangeTrackingMode { get; set; } = DomainConfiguration.DefaultFullTextChangeTrackingMode; + + /// + /// Collation for all columns. See for details. + /// + public string Collation { get; set; } = string.Empty; + + /// + /// Session configurations. + /// + public SessionOptionsCollection Sessions { get; set; } = new SessionOptionsCollection(); + + /// + /// Registered mapping rules. + /// + public MappingRuleOptionsCollection MappingRules { get; set; } = new MappingRuleOptionsCollection(); + + /// + /// Registered ignore rules. + /// + public IgnoreRuleOptionsCollection IgnoreRules { get; set; } = new IgnoreRuleOptionsCollection(); + + /// + /// Registered database aliases. + /// + public DatabaseOptionsCollection Databases { get; set; } = new DatabaseOptionsCollection(); + + /// + /// Key generators. + /// + public KeyGeneratorOptionsCollection KeyGenerators {get; set;} = new KeyGeneratorOptionsCollection(); + + /// + /// Type of service container + /// + public string ServiceContainerType { get; set; } = null; + + /// + /// Default schema. + /// + public string DefaultSchema { get; set; } = string.Empty; + + /// + /// Default database. + /// + public string DefaultDatabase { get; set; } = string.Empty; + + + /// + /// Value indicating whether SQL should be included in exception messages. + /// Default value is + /// + public bool IncludeSqlInExceptions { get; set; } = DomainConfiguration.DefaultIncludeSqlInExceptions; + + /// + /// Value indicating whether cyclic database dependencied are allowed. + /// Default value is + /// + public bool AllowCyclicDatabaseDependencies { get; set; } = DomainConfiguration.DefaultAllowCyclicDatabaseDependencies; + + /// + /// Value indicating whether parallel build should be used where and if it is possible. + /// Default value is + /// + public bool BuildInParallel { get; set; } = DomainConfiguration.DefaultBuildInParallel; + + /// + /// Value indicating whether multidatabase keys should be used. + /// Default value is + /// + public bool MultidatabaseKeys { get; set; } = DomainConfiguration.DefaultMultidatabaseKeys; + + /// + /// Value indicating whether same storage schema is shared across s. + /// Default value is + /// + public bool ShareStorageSchemaOverNodes { get; set; } = DomainConfiguration.DefaultShareStorageSchemaOverNodes; + + /// + /// Enables extra check if connection is not broken on its opening. + /// + public bool EnsureConnectionIsAlive { get; set; } = DomainConfiguration.DefaultEnsureConnectionIsAlive; + + /// + /// Makes queries use parameters instead of constant values for persistent type identifiers. + /// + public bool PreferTypeIdsAsQueryParameters { get; set; } = DomainConfiguration.DefaultPreferTypeIdsAsQueryParameters; + + /// + /// Forced server version. + /// + public string ForcedServerVersion { get; set; } = string.Empty; + + /// + /// Connection initialization SQL script. + /// + public string ConnectionInitializationSql { get; set; } = string.Empty; + + /// + /// Domain options + /// + public DomainOptions Options { get; set; } = DomainConfiguration.DefaultDomainOptions; + + /// + /// Naming convention. + /// + public NamingConventionOptions NamingConventionRaw { get; internal set; } = null; + + /// + /// Versioning convention. + /// + public VersioningConventionOptions VersioningConvention { get; set; } = null; + + /// + /// Defines tags location within query or turn them off if is set. + /// + public TagsLocation TagsLocation { get; set; } = DomainConfiguration.DefaultTagLocation; + + /// + /// + /// + /// + /// + /// + /// + /// + public DomainConfiguration ToNative(IDictionary connectionStrings) + { + var config = new DomainConfiguration { + Name = Name, + ConnectionInfo = ConnectionInfoParser.GetConnectionInfo(connectionStrings, + ConnectionUrl, Provider, ConnectionString), + KeyCacheSize = KeyCacheSize, + KeyGeneratorCacheSize = KeyGeneratorCacheSize, + QueryCacheSize = QueryCacheSize, + RecordSetMappingCacheSize = RecordSetMappingCacheSize, + DefaultSchema = DefaultSchema, + DefaultDatabase = DefaultDatabase, + UpgradeMode = UpgradeMode, + ForeignKeyMode = ForeignKeyMode, + SchemaSyncExceptionFormat = SchemaSyncExceptionFormat, + Options = Options, + ServiceContainerType = !ServiceContainerType.IsNullOrEmpty() ? Type.GetType(ServiceContainerType) : null, + IncludeSqlInExceptions = IncludeSqlInExceptions, + BuildInParallel = BuildInParallel, + AllowCyclicDatabaseDependencies = AllowCyclicDatabaseDependencies, + ForcedServerVersion = ForcedServerVersion, + Collation = Collation, + ConnectionInitializationSql = ConnectionInitializationSql, + MultidatabaseKeys = MultidatabaseKeys, + ShareStorageSchemaOverNodes = ShareStorageSchemaOverNodes, + EnsureConnectionIsAlive = EnsureConnectionIsAlive, + PreferTypeIdsAsQueryParameters = PreferTypeIdsAsQueryParameters, + FullTextChangeTrackingMode = FullTextChangeTrackingMode, + TagsLocation = TagsLocation, + }; + + if (NamingConventionRaw != null) + config.NamingConvention = NamingConventionRaw.ToNative(); + if (VersioningConvention != null) + config.VersioningConvention = VersioningConvention.ToNative(); + + foreach (var element in Types) { + _ = config.Types.Register(element.ToNative()); + } + HashSet uniqueElements; + if (Databases.AnyExceptionOccur) { + var exceptions = Databases.Exceptions; + if (exceptions.Count == 1) + throw exceptions[0]; + throw new System.AggregateException(Strings.ExASetOfExceptionsIsCaught + " during reading 'Databases' section of configuration", exceptions.ToArray()); + } + else if (Databases.Count > 0) { + foreach (var element in Databases) { + config.Databases.Add(element.Value.ToNative()); + } + } + + if (KeyGenerators.AnyExceptionOccur) { + var exceptions = KeyGenerators.Exceptions; + if (exceptions.Count == 1) + throw exceptions[0]; + throw new System.AggregateException(Strings.ExASetOfExceptionsIsCaught + " during reading 'KeyGenerators' section of configuration", exceptions.ToArray()); + } + else if (KeyGenerators.Count > 0) { + uniqueElements = new HashSet(); + foreach (var element in KeyGenerators) { + //resolves cases when alias and database was used which provides different indetifiers but database conflict exists + var identifier = element.GetMappedIdentifier(Databases); + if (!uniqueElements.Add(identifier)) + throw new ArgumentException($"Key generator with name '{element.Name}' for database '{element.Database}' has already been declared."); + + config.KeyGenerators.Add(element.ToNative()); + } + } + if (IgnoreRules.AnyExceptionOccur) { + var exceptions = IgnoreRules.Exceptions; + if (exceptions.Count==1) + throw exceptions[0]; + throw new System.AggregateException("Set of exceptions occured during reading 'IgnoreRules' section of configuration", exceptions.ToArray()); + } + else if (IgnoreRules.Count > 0) { + uniqueElements = new HashSet(); + foreach (var element in IgnoreRules) { + var identifier = element.GetMappedIdentifier(Databases); + if (!uniqueElements.Add(identifier)) + throw new Exception("Ignore rule with same set of properties has already been declared."); + config.IgnoreRules.Add(element.ToNative()); + } + } + + if (MappingRules.AnyExceptionOccur) { + var exceptions = MappingRules.Exceptions; + if (exceptions.Count == 1) + throw exceptions[0]; + throw new System.AggregateException("Set of exceptions occured during reading 'MappingRules' section of configuration", exceptions.ToArray()); + } + else if (MappingRules.Count > 0) { + foreach (var element in MappingRules) { + config.MappingRules.Add(element.ToNative()); + } + } + + foreach (var element in Sessions) { + config.Sessions.Add(element.Value.ToNative(connectionStrings)); + } + + return config; + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/IgnoreRuleOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/IgnoreRuleOptions.cs new file mode 100644 index 0000000000..aa3b231dd0 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/IgnoreRuleOptions.cs @@ -0,0 +1,80 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class IgnoreRuleOptions : IIdentifyableOptions, + IHasDatabaseOption, + IValidatableOptions, + IToNativeConvertable + { + /// + public object Identifier => + (Database ?? string.Empty, + Schema ?? string.Empty, + Table ?? string.Empty, + Column ?? string.Empty, + Index ?? string.Empty); + + /// + /// Database part of the rule + /// + public string Database { get; set; } + + /// + /// Schema part of the rule. + /// + public string Schema { get; set; } + + /// + /// Table part of the rule. + /// + public string Table { get; set; } + + /// + /// Column part of the rule. + /// + public string Column { get; set; } + + /// + /// Index part of the rule. + /// + public string Index { get; set; } + + /// + /// Configuration of properties is not valid, e.g. + /// none of Table, Column and Index filled, or both Column and Index filled. + public void Validate() + { + if (Table.IsNullOrEmpty() && Column.IsNullOrEmpty() && Index.IsNullOrEmpty()) { + throw new ArgumentException("Ignore rule should be configured for at least column, index or table"); + } + if (!Column.IsNullOrEmpty() && !Index.IsNullOrEmpty()) { + throw new ArgumentException("Ignore rule can't be configured for column and index at the same time"); + } + } + + public object GetMappedIdentifier(IDictionary databaseMap) + { + if (!Database.IsNullOrEmpty() && databaseMap.TryGetValue(Database, out var map) && !map.RealName.IsNullOrEmpty()) { + return (map.RealName, Schema ?? string.Empty, Table ?? string.Empty, Column ?? string.Empty, Index ?? string.Empty); + } + return Identifier; + } + + /// + public IgnoreRule ToNative() + { + Validate(); + + return new IgnoreRule(Database, Schema, Table, Column, Index); + } + + + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IHasDatabaseOption.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IHasDatabaseOption.cs new file mode 100644 index 0000000000..0b6cb6bf2f --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IHasDatabaseOption.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System.Collections.Generic; + +namespace Xtensive.Orm.Configuration.Options +{ + internal interface IHasDatabaseOption : IIdentifyableOptions + { + /// + /// Database (real or alias). In cases when database is part of instance identifier + /// it might require to be mapped + /// + string Database { get; set; } + + /// + /// Tries to map database part of identifier from alias to real database names. + /// It might be required if declare + /// aliases and + /// + /// Map of databases + /// If alias is used then new identifier with substituted alias, otherwise, the same identifier. + object GetMappedIdentifier(IDictionary databaseMap); + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IIdentifyableOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IIdentifyableOptions.cs new file mode 100644 index 0000000000..4b71870d31 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IIdentifyableOptions.cs @@ -0,0 +1,15 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + + +namespace Xtensive.Orm.Configuration.Options +{ + internal interface IIdentifyableOptions + { + /// + /// Identifier of options instance in collections where uniqueness is required. + /// + object Identifier { get; } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/INamedOptionsCollectionElement.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/INamedOptionsCollectionElement.cs new file mode 100644 index 0000000000..4d42dddb01 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/INamedOptionsCollectionElement.cs @@ -0,0 +1,14 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +namespace Xtensive.Orm.Configuration.Options +{ + /// + /// Defines options where name is key of collection + /// + internal interface INamedOptionsCollectionElement + { + string Name { get; set; } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IToNativeConvertable.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IToNativeConvertable.cs new file mode 100644 index 0000000000..71a90ef825 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IToNativeConvertable.cs @@ -0,0 +1,15 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +namespace Xtensive.Orm.Configuration.Options +{ + internal interface IToNativeConvertable + { + /// + /// Converts element of options pattern to native configuration it corresponds to () + /// + /// instance. + TNative ToNative(); + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IValidatableOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IValidatableOptions.cs new file mode 100644 index 0000000000..707ca81d67 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/Interfaces/IValidatableOptions.cs @@ -0,0 +1,14 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +namespace Xtensive.Orm.Configuration.Options +{ + internal interface IValidatableOptions + { + /// + /// Performs validation of properties. + /// + void Validate(); + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/KeyGeneratorOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/KeyGeneratorOptions.cs new file mode 100644 index 0000000000..21c8e9a428 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/KeyGeneratorOptions.cs @@ -0,0 +1,75 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class KeyGeneratorOptions : IIdentifyableOptions, + IValidatableOptions, + IHasDatabaseOption, + IToNativeConvertable + { + public object Identifier => (Name ?? string.Empty, Database ?? string.Empty); + + /// + /// Key generator name. + /// + public string Name { get; set; } + + /// + /// Database for key generator + /// + public string Database { get; set; } + + /// + /// Seed (initial value) for key generator. + /// + public long Seed { get; set; } = 0L; + + /// + /// Cache size (increment) for cache generator. + /// + public long CacheSize { get; set; } = DomainConfiguration.DefaultKeyGeneratorCacheSize; + + public object GetMappedIdentifier(IDictionary databaseMap) + { + if (!Database.IsNullOrEmpty() && databaseMap.TryGetValue(Database, out var map) && !map.RealName.IsNullOrEmpty()) { + return(Name ?? string.Empty, map.RealName); + } + return Identifier; + } + + /// + /// Name is null or empty. + /// Seed or CacheSize value is out of valid range. + public void Validate() + { + if (Name.IsNullOrEmpty()) { + throw new ArgumentException("Key generator should have not empty name."); + } + if (Seed < 0) { + throw new ArgumentOutOfRangeException("Key generator seed must be non-negative value"); + } + if (CacheSize <= 0) { + throw new ArgumentOutOfRangeException("Key generator cache size must be positive value"); + } + } + + /// + public KeyGeneratorConfiguration ToNative() + { + Validate(); + + return new KeyGeneratorConfiguration(Name) { + CacheSize = CacheSize, + Seed = Seed, + Database = Database, + }; + } + + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/MappingRuleOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/MappingRuleOptions.cs new file mode 100644 index 0000000000..fe9cc1125f --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/MappingRuleOptions.cs @@ -0,0 +1,59 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class MappingRuleOptions : IIdentifyableOptions, + IValidatableOptions, + IToNativeConvertable + { + public object Identifier => (Assembly ?? string.Empty, Namespace ?? string.Empty); + + /// + /// Assembly condition. + /// See for details. + /// + public string Assembly { get; set; } + + /// + /// Namespace condition. + /// See for details. + /// + public string Namespace { get; set; } + + /// + /// Database that is assigned to mapped type when this rule is applied. + /// See for details. + /// + public string Database { get; set; } + + /// + /// Schema that is assigned to mapped type when this rule is applied. + /// See for details. + /// + public string Schema { get; set; } + + /// + /// Configuration of values in properties is not valid. + public void Validate() + { + if (Assembly.IsNullOrEmpty() && Namespace.IsNullOrEmpty()) + throw new ArgumentException("Mapping rule should declare at least either Assembly or Namespace"); + if (Database.IsNullOrEmpty() && Schema.IsNullOrEmpty()) + throw new ArgumentException("Mapping rule should map assembly and(or) namespace to database, schema or both"); + } + + /// + public MappingRule ToNative() + { + Validate(); + + var assembly = !string.IsNullOrEmpty(Assembly) ? System.Reflection.Assembly.Load(Assembly) : null; + return new MappingRule(assembly, Namespace, Database, Schema); + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/NamedOptionsCollection{T}.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/NamedOptionsCollection{T}.cs new file mode 100644 index 0000000000..342a601402 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/NamedOptionsCollection{T}.cs @@ -0,0 +1,128 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class SessionOptionsCollection : NamedOptionsCollection + { + protected override bool AllowEmptyNames => true; + } + + internal sealed class DatabaseOptionsCollection : NamedOptionsCollection + { + + } + + internal abstract class NamedOptionsCollection : IDictionary + where T: INamedOptionsCollectionElement + { + private readonly Dictionary internalDictionary = new(); + private readonly List exceptionsOccur = new(); + + public T this[string key] + { + get => internalDictionary[key]; + set { + + if (!internalDictionary.ContainsKey(key)) { + Add(key, value); + } + else { + ValidateName(key); + value.Name = key; + ValidateItemOnAdd(value); + internalDictionary[key] = value; + } + } + } + + public ICollection Keys => internalDictionary.Keys; + + public ICollection Values => internalDictionary.Values; + + public int Count => internalDictionary.Count; + + protected virtual bool AllowEmptyNames => false; + + internal bool AnyExceptionOccur => exceptionsOccur.Any(); + + // Section to object materialisation "swollows" exceptions, + // which means we will get partially populated collections + // without even knowing that something went wrong. + // This collection of exceptions helps to understand whether + // the collection populated without an issues or not. + internal List Exceptions => exceptionsOccur; + + public void Add(string name, T value) + { + ValidateName(name); + + if (value == null) { + throw new ArgumentNullException(nameof(value)); + } + value.Name = name; + + if (!internalDictionary.TryAdd(name, value)) { + throw new ArgumentException(string.Format(Strings.ExItemWithNameXAlreadyExists, name)); + } + } + + public void Clear() => internalDictionary.Clear(); + + public bool ContainsKey(string key) => internalDictionary.ContainsKey(key); + public IEnumerator> GetEnumerator() => ((ICollection>) internalDictionary).GetEnumerator(); + public bool Remove(string key) => internalDictionary.Remove(key); + public bool TryGetValue(string key, [MaybeNullWhen(false)] out T value) => internalDictionary.TryGetValue(key, out value); + + + protected virtual void ValidateItemOnAdd(T item) + { + if (item is IValidatableOptions validatableOptions) { + validatableOptions.Validate(); + } + } + + protected Exception RegisterValidationException(Exception exception) + { + exceptionsOccur.Add(exception); + return exception; + } + + private void ValidateName(string name) + { + if (!AllowEmptyNames && name.IsNullOrEmpty()) { + throw RegisterValidationException((name == null) + ? new ArgumentNullException(nameof(name)) + : new ArgumentException(Strings.ExArgumentCannotBeEmptyString, nameof(name))); + } + else { + if (name == null) { + throw RegisterValidationException(new ArgumentNullException(nameof(name))); + } + } + } + + bool ICollection>.IsReadOnly => ((ICollection>) internalDictionary).IsReadOnly; + void ICollection>.Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => + ((IDictionary) internalDictionary).CopyTo(array, arrayIndex); + bool ICollection>.Contains(KeyValuePair item) => + ((IDictionary) internalDictionary).Contains(item); + bool ICollection>.Remove(KeyValuePair item) => + ((IDictionary) internalDictionary).Remove(item); + + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/NamespaceSynonymOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/NamespaceSynonymOptions.cs new file mode 100644 index 0000000000..95917fb24b --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/NamespaceSynonymOptions.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class NamespaceSynonymOptions + { + /// + /// Namespace to synonymize. + /// + public string Namespace { get; set; } + + /// + /// Synonym of namespace. + /// + public string Synonym { get; set; } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/NamingConventionOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/NamingConventionOptions.cs new file mode 100644 index 0000000000..9aba88ebdd --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/NamingConventionOptions.cs @@ -0,0 +1,54 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class NamingConventionOptions : IToNativeConvertable + { + /// + /// Letter case policy. + /// + public LetterCasePolicy LetterCasePolicy { get; set; } = NamingConvention.DefaultLetterCasePolicy; + + /// + /// Namespace policy. + /// + public NamespacePolicy NamespacePolicy { get; set; } = NamingConvention.DefaultNamespacePolicy; + + /// + /// Rules of naming. + /// + public NamingRules NamingRules { get; set; } = NamingConvention.DefaultNamingRules; + + /// + /// Collection of namespace synonyms. + /// + public NamespaceSynonymOptions[] NamespaceSynonyms { get; set; } + + /// + public NamingConvention ToNative() + { + var result = new NamingConvention { + LetterCasePolicy = LetterCasePolicy, + NamespacePolicy = NamespacePolicy, + NamingRules = NamingRules, + }; + + foreach (var namespaceSynonym in NamespaceSynonyms) { + if (namespaceSynonym.Namespace.IsNullOrEmpty()) { + ArgumentValidator.EnsureArgumentNotNullOrEmpty(namespaceSynonym.Namespace, namespaceSynonym.Namespace); + } + if (!result.NamespaceSynonyms.TryAdd(namespaceSynonym.Namespace, namespaceSynonym.Synonym)) { + throw new Exception($"Synonym for namespace '{namespaceSynonym.Namespace}' has already been assigned."); + } + } + return result; + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/OptionsCollection{T}.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/OptionsCollection{T}.cs new file mode 100644 index 0000000000..a8bd0d8bc3 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/OptionsCollection{T}.cs @@ -0,0 +1,108 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class MappingRuleOptionsCollection : OptionsCollection + { + } + + internal sealed class IgnoreRuleOptionsCollection : OptionsCollection + { + } + + internal sealed class KeyGeneratorOptionsCollection : OptionsCollection + { + } + + internal abstract class OptionsCollection : ICollection + where T : class, IIdentifyableOptions + { + private readonly Dictionary map = new(); + private readonly List exceptionsOccur = new(); + + public int Count => map.Count; + + public bool IsReadOnly => false; + + internal bool AnyExceptionOccur => exceptionsOccur.Any(); + + // Section to object materialisation "swollows" exceptions, + // which means we will get partially populated collections + // without even knowing that something went wrong. + // This collection of exceptions helps to understand whether + // the collection populated without an issues or not. + internal List Exceptions => exceptionsOccur; + + public void Add(T item) + { + if (item == null) { + throw RegisterValidationException(new ArgumentNullException(nameof(item))); + } + + if (!map.TryAdd(item.Identifier, item)) { + throw RegisterValidationException( + new ArgumentException( + $"Item with the same identifier '{item.Identifier}' has already been declared.")); + } + } + + public bool Contains(T item) + { + if (item == null) + return false; + if (map.ContainsKey(item.Identifier)) + return true; + if (map.Values.Contains(item)) { + return true; + } + return false; + } + + public void Clear() => map.Clear(); + + public bool Remove(T item) + { + if (map.Remove(item.Identifier)) + return true; + var result = map.Where(kv => kv.Value == item).Select(kv => kv.Key).FirstOrDefault(); + if (result != null) { + return map.Remove(result); + } + return false; + } + + public void CopyTo(T[] array, int arrayIndex) + { + ArgumentValidator.EnsureArgumentNotNull(array, nameof(array)); + ArgumentValidator.EnsureArgumentIsGreaterThanOrEqual(arrayIndex, 0, nameof(arrayIndex)); + + if (array.Length - arrayIndex < map.Count) { + throw new ArgumentException( + "The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array"); + } + + var index = arrayIndex; + foreach (var item in map.Values) { + array[index] = item; + index++; + } + } + + protected Exception RegisterValidationException(Exception exception) + { + exceptionsOccur.Add(exception); + return exception; + } + + public IEnumerator GetEnumerator() => map.Values.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/SessionConfigurationOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/SessionConfigurationOptions.cs new file mode 100644 index 0000000000..1263ca5c67 --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/SessionConfigurationOptions.cs @@ -0,0 +1,145 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Transactions; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class SessionConfigurationOptions : + IIdentifyableOptions, + IValidatableOptions, + INamedOptionsCollectionElement + { + public object Identifier => Name; + + /// + /// Session name. + /// + /// + public string Name { get; set; } = WellKnown.Sessions.Default; + + /// + /// User name to authenticate. + /// Default value is . + /// + public string UserName { get; set; } = string.Empty; + + /// + /// Password to authenticate. + /// Default value is . + /// + public string Password { get; set; } = string.Empty; + + /// + /// session options. + /// Default value is . + /// + public SessionOptions Options { get; set; } = SessionOptions.Default; + + /// + /// Size of the session entity state cache. + /// Default value is . + /// + public int CacheSize { get; set; } = SessionConfiguration.DefaultCacheSize; + + /// + /// Type of session cache. + /// Default value is . + /// + public SessionCacheType CacheType { get; set; } = SessionConfiguration.DefaultCacheType; + + /// + /// Default isolation level. + /// Default value is . + /// + public IsolationLevel DefaultIsolationLevel { get; set; } = SessionConfiguration.DefaultDefaultIsolationLevel; + + /// + /// Default command timeout. + /// Default value is . + /// + public int? DefaultCommandTimeout { get; set; } = null; + + /// + /// Size of the batch. + /// This affects create, update, delete operations and future queries. + /// Default value is + /// + public int BatchSize { get; set; } = SessionConfiguration.DefaultBatchSize; + + /// + /// Size of the entity change registry. + /// Default value is + /// + public int EntityChangeRegistrySize { get; set; } = SessionConfiguration.DefaultEntityChangeRegistrySize; + + /// + /// Reader preloading policy. + /// It affects query results reading. + /// Default value is . + /// + public ReaderPreloadingPolicy ReaderPreloading { get; set; } = SessionConfiguration.DefaultReaderPreloadingPolicy; + + /// + /// Type of the service container + /// + public string ServiceContainerType { get; set; } = null; + + public string ConnectionString { get; set; } = null; + public string ConnectionUrl { get; set; } = null; + + /// + /// Name is null or empty. + /// CacheSize, BatchSize or EntityChangeRegistry value is out of valid range. + public void Validate() + { + if (Name.IsNullOrEmpty()) + throw new ArgumentException(Strings.ExNameMustBeNotNullOrEmpty); + if (CacheSize <= 1) + throw new ArgumentOutOfRangeException(nameof(CacheSize), CacheSize, string.Format(Strings.ExArgumentMustBeGreaterThanX, 1)); + if (BatchSize < 1) + throw new ArgumentOutOfRangeException(nameof(BatchSize), BatchSize, string.Format(Strings.ExArgumentMustBeGreaterThatOrEqualX, 1)); + if (EntityChangeRegistrySize < 1) + throw new ArgumentOutOfRangeException(nameof(EntityChangeRegistrySize), EntityChangeRegistrySize, string.Format(Strings.ExArgumentMustBeGreaterThatOrEqualX, 1)); + } + + /// + public SessionConfiguration ToNative(IDictionary connectionStrings) + { + // Minor hack: + // We should not require user to specify provider name. + // We actually know it when opening new session. + // However, we do not know it in this method + // We are going easy way and substituting a fake provider. + // SQL SessionHandler is aware of this and always uses correct provider. + + var connectionInfo = ConnectionInfoParser.GetConnectionInfo(connectionStrings, ConnectionUrl, "_dummy_", ConnectionString); + + if (Name.IsNullOrEmpty()) { + Name = WellKnown.Sessions.Default; + } + + Validate(); + + var result = new SessionConfiguration(Name) { + UserName = UserName, + Password = Password, + CacheSize = CacheSize, + BatchSize = BatchSize, + CacheType = CacheType, + Options = Options, + DefaultIsolationLevel = DefaultIsolationLevel, + ReaderPreloading = ReaderPreloading, + ServiceContainerType = (!ServiceContainerType.IsNullOrEmpty()) ? Type.GetType(ServiceContainerType) : null, + EntityChangeRegistrySize = EntityChangeRegistrySize, + DefaultCommandTimeout = DefaultCommandTimeout, + ConnectionInfo = connectionInfo, + }; + return result; + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/TypeRegistrationOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/TypeRegistrationOptions.cs new file mode 100644 index 0000000000..050b40741a --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/TypeRegistrationOptions.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using Xtensive.Collections; +using Xtensive.Core; + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class TypeRegistrationOptions : IIdentifyableOptions, + IValidatableOptions, + IToNativeConvertable + { + public object Identifier + { + get { + if (!Type.IsNullOrEmpty()) + return Type; + else + return (Assembly, Namespace.IsNullOrEmpty() ? null : Namespace); + } + } + + /// + /// Gets or sets the name of the type to register. + /// + public string Type { get; set; } = null; + + /// + /// Gets or sets the assembly where types to register are located. + /// + public string Assembly { get; set; } = null; + + /// + /// Gets or sets the namespace withing the , + /// where types to register are located. + /// If or , + /// all the persistent types from the will be registered. + /// + public string Namespace { get; set; } = null; + + /// + /// Combination of properties is not valid. + public void Validate() + { + if (!Type.IsNullOrEmpty() && (!Assembly.IsNullOrEmpty() || !Namespace.IsNullOrEmpty())) + throw new ArgumentException("Either type or assembly can be declared, not both at the same time."); + if (!Assembly.IsNullOrEmpty() && !Type.IsNullOrEmpty()) + throw new ArgumentException("Either type or assembly can be declared, not both at the same time."); + if (!Namespace.IsNullOrEmpty() && Assembly.IsNullOrEmpty()) + throw new ArgumentException("Namespace can only be declared with Assembly."); + } + + /// + public TypeRegistration ToNative() + { + Validate(); + + if (!Type.IsNullOrEmpty()) + return new TypeRegistration(System.Type.GetType(Type, true)); + else if (!Assembly.IsNullOrEmpty()) { + var assembly = System.Reflection.Assembly.Load(Assembly); + if (Namespace.IsNullOrEmpty()) + return new TypeRegistration(assembly); + else + return new TypeRegistration(assembly, Namespace); + } + throw new InvalidOperationException(); + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/Options/VersioningConventionOptions.cs b/Orm/Xtensive.Orm/Orm/Configuration/Options/VersioningConventionOptions.cs new file mode 100644 index 0000000000..7bcaf702ff --- /dev/null +++ b/Orm/Xtensive.Orm/Orm/Configuration/Options/VersioningConventionOptions.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +namespace Xtensive.Orm.Configuration.Options +{ + internal sealed class VersioningConventionOptions : IToNativeConvertable + { + /// + /// Versioning policy for entities. + /// Default value is + /// + public EntityVersioningPolicy EntityVersioningPolicy { get; set; } = VersioningConvention.DefaultVersioningPolicy; + + /// + /// Value indicating that change of an owner version should be denied where possible. + /// Default value is + /// + public bool DenyEntitySetOwnerVersionChange { get; set; } = false; + + /// + public VersioningConvention ToNative() + { + var result = new VersioningConvention { + EntityVersioningPolicy = EntityVersioningPolicy, + DenyEntitySetOwnerVersionChange = DenyEntitySetOwnerVersionChange + }; + return result; + } + } +} diff --git a/Orm/Xtensive.Orm/Orm/Configuration/SessionConfiguration.cs b/Orm/Xtensive.Orm/Orm/Configuration/SessionConfiguration.cs index 1107f23c39..e593097f5d 100644 --- a/Orm/Xtensive.Orm/Orm/Configuration/SessionConfiguration.cs +++ b/Orm/Xtensive.Orm/Orm/Configuration/SessionConfiguration.cs @@ -23,12 +23,27 @@ public class SessionConfiguration : ConfigurationBase /// Default cache size. /// public const int DefaultCacheSize = 16 * 1024; - + + /// + /// Default session options. + /// + public const SessionOptions DefaultSessionOptions = SessionOptions.Default; + /// /// Default isolation level. /// public const IsolationLevel DefaultDefaultIsolationLevel = IsolationLevel.RepeatableRead; + /// + /// Default cache type. + /// + public const SessionCacheType DefaultCacheType = SessionCacheType.Default; + + /// + /// Default reader preloading policy. + /// + public const ReaderPreloadingPolicy DefaultReaderPreloadingPolicy = ReaderPreloadingPolicy.Default; + /// /// Default batch size. /// @@ -46,13 +61,13 @@ public class SessionConfiguration : ConfigurationBase /// public static readonly SessionConfiguration Default = new SessionConfiguration(WellKnown.Sessions.Default); - private SessionOptions options = SessionOptions.Default; + private SessionOptions options = DefaultSessionOptions; private string userName = string.Empty; private string password = string.Empty; private int cacheSize = DefaultCacheSize; private int batchSize = DefaultBatchSize; private int entityChangeRegistrySize = DefaultEntityChangeRegistrySize; - private SessionCacheType cacheType = SessionCacheType.Default; + private SessionCacheType cacheType = DefaultCacheType; private IsolationLevel defaultIsolationLevel = DefaultDefaultIsolationLevel; // what a fancy name? private int? defaultCommandTimeout = null; private ReaderPreloadingPolicy readerPreloading = ReaderPreloadingPolicy.Default; @@ -103,6 +118,7 @@ public int CacheSize { /// /// Gets or sets the type of the session cache. + /// Default value is . /// public SessionCacheType CacheType { get { return cacheType; } @@ -145,6 +161,7 @@ public int? DefaultCommandTimeout { /// /// Gets or sets the size of the batch. /// This affects create, update, delete operations and future queries. + /// Default value is . /// public int BatchSize { get { return batchSize; } @@ -168,6 +185,8 @@ public SessionOptions Options { /// /// Gets or sets the reader preloading policy. + /// It affects query results reading. + /// Default value is . /// public ReaderPreloadingPolicy ReaderPreloading { @@ -180,6 +199,7 @@ public ReaderPreloadingPolicy ReaderPreloading /// /// Gets or sets the size of the entity change registry. + /// Default value is . /// public int EntityChangeRegistrySize { diff --git a/Orm/Xtensive.Orm/Orm/Upgrade/UpgradingDomainBuilder.cs b/Orm/Xtensive.Orm/Orm/Upgrade/UpgradingDomainBuilder.cs index 98e91c2910..a8e72e3e93 100644 --- a/Orm/Xtensive.Orm/Orm/Upgrade/UpgradingDomainBuilder.cs +++ b/Orm/Xtensive.Orm/Orm/Upgrade/UpgradingDomainBuilder.cs @@ -50,7 +50,11 @@ public static Domain Build(DomainConfiguration configuration) configuration.Lock(); } - LogManager.Default.AutoInitialize(); + var logConfiguration = configuration.ExtensionConfigurations.Get(); + if (logConfiguration != null) + LogManager.Default.Initialize(logConfiguration); + else + LogManager.Default.AutoInitialize(); var context = new UpgradeContext(configuration); @@ -72,7 +76,11 @@ public static async Task BuildAsync(DomainConfiguration configuration, C configuration.Lock(); } - LogManager.Default.AutoInitialize(); + var logConfiguration = configuration.ExtensionConfigurations.Get(); + if (logConfiguration != null) + LogManager.Default.Initialize(logConfiguration); + else + LogManager.Default.AutoInitialize(); var context = new UpgradeContext(configuration); diff --git a/Orm/Xtensive.Orm/Orm/WellKnown.cs b/Orm/Xtensive.Orm/Orm/WellKnown.cs index 1df1e4de33..0e9e7ef5b3 100644 --- a/Orm/Xtensive.Orm/Orm/WellKnown.cs +++ b/Orm/Xtensive.Orm/Orm/WellKnown.cs @@ -32,6 +32,11 @@ public static partial class WellKnown /// public const string DefaultConfigurationSection = "Xtensive.Orm"; + /// + /// Default name of domain. + /// + public const string DefaultDomainConfigurationName = "Default"; + /// /// Name of the field. /// diff --git a/Orm/Xtensive.Orm/Xtensive.Orm.csproj b/Orm/Xtensive.Orm/Xtensive.Orm.csproj index 316508f708..b6ed0b3f02 100644 --- a/Orm/Xtensive.Orm/Xtensive.Orm.csproj +++ b/Orm/Xtensive.Orm/Xtensive.Orm.csproj @@ -1,4 +1,4 @@ - + true $(OutputPath)$(TargetFramework)\$(AssemblyName).xml @@ -57,6 +57,8 @@ + +